Compare commits

..

1 Commits

Author SHA1 Message Date
xiaoju eda7482e6d chore: update @ocas/core 0.4.0 → 0.4.1
CI / check (pull_request) Failing after 2m43s
2026-06-07 16:07:42 +00:00
13 changed files with 46 additions and 200 deletions
-10
View File
@@ -1,10 +0,0 @@
---
"@united-workforce/cli": patch
---
fix: stop parent traversal at .git boundary
`findWorkflowInParents()` and `discoverProjectWorkflows()` now stop traversing
parent directories when they encounter a `.git` directory or file (git worktree).
This prevents picking up unrelated `.workflow/` directories above the repository
root in monorepo setups.
+1 -1
View File
@@ -21,7 +21,7 @@
"test:ci": "vitest run __tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@united-workforce/util": "workspace:^",
"@united-workforce/util-agent": "workspace:^"
},
+1 -1
View File
@@ -21,7 +21,7 @@
"test:ci": "vitest run __tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
"@united-workforce/util-agent": "workspace:^"
+1 -1
View File
@@ -21,7 +21,7 @@
"test:ci": "vitest run __tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
"@united-workforce/util-agent": "workspace:^"
+1 -1
View File
@@ -21,7 +21,7 @@
"test:ci": "vitest run __tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
"@united-workforce/util-agent": "workspace:^",
+1 -1
View File
@@ -11,7 +11,7 @@
"uwf": "./dist/cli.js"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@ocas/fs": "^0.4.0",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
@@ -183,122 +183,6 @@ describe("discoverProjectWorkflows — parent traversal", () => {
});
});
// ── discoverProjectWorkflows — .git boundary ─────────────────────────────────
describe("discoverProjectWorkflows — .git boundary", () => {
test("G1: .git directory stops traversal", async () => {
// Setup: tmpDir/repo/.git/ (dir), tmpDir/.workflow/leak.yaml, start from tmpDir/repo/sub/deep/
const repoDir = join(tmpDir, "repo");
const gitDir = join(repoDir, ".git");
await mkdir(gitDir, { recursive: true });
// Workflow above repo root — should NOT be reachable
const leakDir = join(tmpDir, ".workflow");
await mkdir(leakDir, { recursive: true });
await writeFile(join(leakDir, "leak.yaml"), await createWorkflowYaml("leak"));
const startFrom = join(repoDir, "sub", "deep");
await mkdir(startFrom, { recursive: true });
const entries = await discoverProjectWorkflows(startFrom);
expect(entries).toEqual([]);
});
test("G2: .git file (worktree) stops traversal", async () => {
// Setup: tmpDir/repo/.git as a FILE, tmpDir/.workflow/leak.yaml, start from tmpDir/repo/pkg/
const repoDir = join(tmpDir, "repo");
await mkdir(repoDir, { recursive: true });
await writeFile(join(repoDir, ".git"), "gitdir: /some/other/path/.git/worktrees/repo");
const leakDir = join(tmpDir, ".workflow");
await mkdir(leakDir, { recursive: true });
await writeFile(join(leakDir, "leak.yaml"), await createWorkflowYaml("leak"));
const startFrom = join(repoDir, "pkg");
await mkdir(startFrom, { recursive: true });
const entries = await discoverProjectWorkflows(startFrom);
expect(entries).toEqual([]);
});
test("G3: workflow at .git boundary IS found", async () => {
// Setup: tmpDir/repo/.git/ (dir), tmpDir/repo/.workflow/local.yaml, start from tmpDir/repo/sub/
const repoDir = join(tmpDir, "repo");
const gitDir = join(repoDir, ".git");
await mkdir(gitDir, { recursive: true });
const wfDir = join(repoDir, ".workflow");
await mkdir(wfDir, { recursive: true });
await writeFile(join(wfDir, "local.yaml"), await createWorkflowYaml("local"));
const startFrom = join(repoDir, "sub");
await mkdir(startFrom, { recursive: true });
const entries = await discoverProjectWorkflows(startFrom);
expect(entries.map((e) => e.name)).toContain("local");
});
test("G4: workflow below .git is found, above is not", async () => {
// Setup: tmpDir/repo/.git/ + tmpDir/repo/.workflow/local.yaml + tmpDir/.workflow/leak.yaml
const repoDir = join(tmpDir, "repo");
const gitDir = join(repoDir, ".git");
await mkdir(gitDir, { recursive: true });
const localWfDir = join(repoDir, ".workflow");
await mkdir(localWfDir, { recursive: true });
await writeFile(join(localWfDir, "local.yaml"), await createWorkflowYaml("local"));
const leakDir = join(tmpDir, ".workflow");
await mkdir(leakDir, { recursive: true });
await writeFile(join(leakDir, "leak.yaml"), await createWorkflowYaml("leak"));
const startFrom = join(repoDir, "sub");
await mkdir(startFrom, { recursive: true });
const entries = await discoverProjectWorkflows(startFrom);
expect(entries.map((e) => e.name)).toEqual(["local"]);
});
});
// ── findWorkflowInParents (via cmdThreadStart) — .git boundary ───────────────
describe("findWorkflowInParents via cmdThreadStart — .git boundary", () => {
test("G5: .git stops traversal — workflow above boundary is not found", async () => {
await makeUwfStore(storageRoot);
const repoDir = join(tmpDir, "repo");
const gitDir = join(repoDir, ".git");
await mkdir(gitDir, { recursive: true });
// Workflow above .git boundary
const leakDir = join(tmpDir, ".workflow");
await mkdir(leakDir, { recursive: true });
await writeFile(join(leakDir, "leak.yaml"), await createWorkflowYaml("leak"));
const startFrom = join(repoDir, "sub");
await mkdir(startFrom, { recursive: true });
// cmdThreadStart should fail — "leak" is above the .git boundary
await expect(cmdThreadStart(storageRoot, "leak", "prompt", startFrom)).rejects.toThrow();
});
test("G6: workflow at .git boundary IS found via cmdThreadStart", async () => {
await makeUwfStore(storageRoot);
const repoDir = join(tmpDir, "repo");
const gitDir = join(repoDir, ".git");
await mkdir(gitDir, { recursive: true });
const wfDir = join(repoDir, ".workflow");
await mkdir(wfDir, { recursive: true });
await writeFile(join(wfDir, "local.yaml"), await createWorkflowYaml("local"));
const startFrom = join(repoDir, "sub");
await mkdir(startFrom, { recursive: true });
const result = await cmdThreadStart(storageRoot, "local", "prompt", startFrom);
expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
});
});
// ── cmdWorkflowList — parent traversal ───────────────────────────────────────
describe("cmdWorkflowList — parent traversal", () => {
+1 -16
View File
@@ -1,6 +1,6 @@
import { execFileSync, spawn } from "node:child_process";
import { access, readFile } from "node:fs/promises";
import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
import type { VarStore } from "@ocas/core";
import { validate } from "@ocas/core";
import type {
@@ -287,16 +287,6 @@ async function findWorkflowInDir(dir: string, name: string): Promise<string | nu
return null;
}
/** Check if a directory contains a .git marker (directory or file). */
async function hasGitMarker(dir: string): Promise<boolean> {
try {
await access(join(dir, ".git"));
return true;
} catch {
return false;
}
}
/**
* Traverse parent directories looking for `.workflow/<name>.yaml` or `.workflow/<name>.yml`.
* Returns the absolute path if found, otherwise null.
@@ -312,11 +302,6 @@ async function findWorkflowInParents(startDir: string, name: string): Promise<st
return found;
}
// Stop at .git boundary (repo root)
if (await hasGitMarker(currentDir)) {
break;
}
// Stop at filesystem root
if (currentDir === root) {
break;
+10 -34
View File
@@ -82,31 +82,6 @@ async function scanWorkflowDir(dir: string): Promise<ProjectWorkflowEntry[]> {
return result;
}
/** Merge primary (.workflow/) and legacy (.workflows/) entries, primary wins on name collision. */
function mergeWorkflowEntries(
primary: ProjectWorkflowEntry[],
legacy: ProjectWorkflowEntry[],
): ProjectWorkflowEntry[] {
const seen = new Set(primary.map((e) => e.name));
const merged = [...primary];
for (const entry of legacy) {
if (!seen.has(entry.name)) {
merged.push(entry);
}
}
return merged;
}
/** Check if a directory contains a .git marker (directory or file). */
async function hasGitMarker(dir: string): Promise<boolean> {
try {
await access(join(dir, ".git"));
return true;
} catch {
return false;
}
}
/**
* Discover project-local workflows by walking from `startDir` up through parent
* directories. The nearest directory that contains a `.workflow/` or `.workflows/`
@@ -121,9 +96,8 @@ async function hasGitMarker(dir: string): Promise<boolean> {
* `uwf thread start`, so `uwf workflow list` and `uwf thread start` agree on
* what's discoverable from any given subdirectory.
*
* Traversal stops at the first `.git` boundary (directory or file) or the
* filesystem root. Returns an empty array if no `.workflow/` or `.workflows/`
* directory exists within that range.
* Returns an empty array if no `.workflow/` or `.workflows/` directory exists
* anywhere from `startDir` up to the filesystem root.
*/
export async function discoverProjectWorkflows(startDir: string): Promise<ProjectWorkflowEntry[]> {
let currentDir = resolvePath(startDir);
@@ -134,12 +108,14 @@ export async function discoverProjectWorkflows(startDir: string): Promise<Projec
const legacy = await scanWorkflowDir(join(currentDir, ".workflows"));
if (primary.length > 0 || legacy.length > 0) {
return mergeWorkflowEntries(primary, legacy);
}
// Stop at .git boundary (repo root)
if (await hasGitMarker(currentDir)) {
return [];
const seen = new Set(primary.map((e) => e.name));
const merged = [...primary];
for (const entry of legacy) {
if (!seen.has(entry.name)) {
merged.push(entry);
}
}
return merged;
}
// Stop at filesystem root
+1 -1
View File
@@ -22,7 +22,7 @@
"test:ci": "vitest run __tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@ocas/fs": "^0.4.0",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
+1 -1
View File
@@ -18,7 +18,7 @@
"test:ci": "vitest run src/__tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@ocas/fs": "^0.4.0"
},
"devDependencies": {
+1 -1
View File
@@ -18,7 +18,7 @@
"test:ci": "vitest run __tests__/ src/__tests__/"
},
"dependencies": {
"@ocas/core": "^0.4.0",
"@ocas/core": "^0.4.1",
"@ocas/fs": "^0.4.0",
"@united-workforce/protocol": "workspace:^",
"@united-workforce/util": "workspace:^",
+27 -16
View File
@@ -45,8 +45,8 @@ importers:
packages/agent-builtin:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@united-workforce/util':
specifier: workspace:^
version: link:../util
@@ -61,8 +61,8 @@ importers:
packages/agent-claude-code:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@united-workforce/protocol':
specifier: workspace:^
version: link:../protocol
@@ -80,8 +80,8 @@ importers:
packages/agent-hermes:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@united-workforce/protocol':
specifier: workspace:^
version: link:../protocol
@@ -99,8 +99,8 @@ importers:
packages/agent-mock:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@united-workforce/protocol':
specifier: workspace:^
version: link:../protocol
@@ -121,8 +121,8 @@ importers:
packages/cli:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@ocas/fs':
specifier: ^0.4.0
version: 0.4.0
@@ -231,8 +231,8 @@ importers:
packages/eval:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@ocas/fs':
specifier: ^0.4.0
version: 0.4.0
@@ -256,8 +256,8 @@ importers:
packages/protocol:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@ocas/fs':
specifier: ^0.4.0
version: 0.4.0
@@ -275,8 +275,8 @@ importers:
packages/util-agent:
dependencies:
'@ocas/core':
specifier: ^0.4.0
version: 0.4.0
specifier: ^0.4.1
version: 0.4.1
'@ocas/fs':
specifier: ^0.4.0
version: 0.4.0
@@ -896,6 +896,10 @@ packages:
resolution: {integrity: sha512-6JvHd3nr5GncMOBNaZTf9ZTWou/txONTfZbkrblmgqL/H+YuRj1FfeFY+b1ndUlfwR7AuJ6bvoSxR5RP+AbC0w==}
engines: {node: '>=22.5.0'}
'@ocas/core@0.4.1':
resolution: {integrity: sha512-rmnfe1Q/J/4RXzvt+zn65FLLvzK+F8atuJB2+5Qe4tS8lusWV6s3wB1XErhphVlKpJAZY/sokVlNJylWmIgArQ==}
engines: {node: '>=22.5.0'}
'@ocas/fs@0.4.0':
resolution: {integrity: sha512-AQG6dk1YCL1qpSszUWUgEY+LQhYbTv5hXYrs3J2pHAi2/lY615O2cTgjwEeh6JTcrqHsFwiDsDdKIKMpADchZA==}
engines: {node: '>=22.5.0'}
@@ -3905,6 +3909,13 @@ snapshots:
liquidjs: 10.27.0
xxhash-wasm: 1.1.0
'@ocas/core@0.4.1':
dependencies:
ajv: 8.20.0
cborg: 4.5.8
liquidjs: 10.27.0
xxhash-wasm: 1.1.0
'@ocas/fs@0.4.0':
dependencies:
'@ocas/core': 0.4.0