mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-08 17:31:56 -10:00
fix(slice-22): skip noise dirs in spec library walk + prune stale rows at startup (OPR.0.3.2.22 Bug 4)
Closes the last paper-cut behind the openrig-comms hero-flow report:
operator on a fresh 0.3.1 install ran `rig up conveyor` and hit an
ambiguous-match error against a `conveyor` workflow spec that had
been cached from a path inside `.worktrees/release-0.3.0-...`. The
workflow scanner's stale-row cleanup only fires for source_path
values starting with the workspace `workflows/` folder prefix, so
rows from `.worktrees/`, `node_modules/`, etc. survived forever.
The fix has two coordinated parts:
(1) walkYamlFiles SKIP_DIRS guard
(packages/daemon/src/domain/spec-library-service.ts):
Mirrors progress-indexer.ts:81 + frontmatter-validator.ts:103.
The walker refuses to descend into the noise directories that
no shipped or operator-authored spec ever lives in:
.worktrees, node_modules, .git, dist, build, .turbo, .next.
When the SKIP_DIRS set grows in the future, update all three
locations to keep the behavior consistent (called out in the
inline comment).
(2) One-time startup prune of legacy noise-dir rows
(packages/daemon/src/domain/workflow-spec-cache.ts +
packages/daemon/src/startup.ts):
New WorkflowSpecCache.pruneNoiseDirRows() issues a single
bounded DELETE matching the same noise-directory patterns the
walker now refuses to enter. Called once during daemon startup
immediately after deps.workflowSpecCache is wired up, so
operators upgrading from a pre-Bug-4 daemon get their legacy
stale rows cleared on first boot. Idempotent — returns 0 when
no noise rows exist.
Optional `rig specs show/preview` cross-kind ambiguity cleanup at
specs.ts:230-246 (called out as taste-y in README §74) is NOT in
this commit. The stale-row class behind the ambiguity is now closed
upstream by the prune; the show/preview surface stays as-is for
scope discipline.
Test additions:
- packages/daemon/test/workflow-spec-cache.test.ts: pruneNoiseDirRows
with 4 cache rows (one canonical, three across .worktrees +
node_modules + dist) removes the 3 noise rows and preserves the
canonical one. Idempotent variant: 0 noise rows in → 0 removed.
- packages/daemon/test/spec-library-service.test.ts: SpecLibraryService.scan
with one canonical yaml plus a noise yaml inside each of the 7
SKIP_DIRS entries discovers only the canonical entry; the seven
noise paths are excluded by directory name, never read.
Verification:
- Full packages/daemon test/ suite: 272 files, 3995 PASS + 1 skipped,
ZERO failures.
- Touched suites isolated: workflow-spec-cache.test.ts 11/11 PASS,
spec-library-service.test.ts 9/9 PASS.
- tsc --noEmit on @openrig/daemon EXIT=0.
- Commit body verification grep clean per banked discipline.
- git diff --cached --stat: 5 files (+119 / -0).
Branch: slice-22-rig-up-paper-cut-fix-round off LOCAL main 0c9e63f1.
Chain: 50488427 (Bug 1 BONUS) → a29c7883 (Bug 2 first) → ad6a522b
(Bug 2 teardown follow-up) → 64f8aa8b (Bug 3) → this commit. LOCAL
only, NO push. This closes the four-bug slice.
This commit is contained in:
1 parent
64f8aa8bc6
commit
79d06f8db9
5 files changed
+119
No files matched your search
@@ -49,6 +49,22 @@ function isYamlFile(filename: string): boolean {
|
||||
return filename.endsWith(".yaml") || filename.endsWith(".yml");
|
||||
}
|
||||
|
||||
// OPR.0.3.2.22 Bug 4 — noise directories that should never enter the
|
||||
// spec library walk. Mirrors progress-indexer.ts:81 +
|
||||
// frontmatter-validator.ts:103 — when this list grows, update all three
|
||||
// for consistency. The `.worktrees` entry closes the stale-row class
|
||||
// where conveyor.yaml inside a worktree was sliding into the library
|
||||
// cache and producing the `rig specs show conveyor` ambiguous-match UX.
|
||||
const SKIP_DIRS = new Set([
|
||||
".worktrees",
|
||||
"node_modules",
|
||||
".git",
|
||||
"dist",
|
||||
"build",
|
||||
".turbo",
|
||||
".next",
|
||||
]);
|
||||
|
||||
function walkYamlFiles(rootPath: string): string[] {
|
||||
const files: string[] = [];
|
||||
const stack = [rootPath];
|
||||
@@ -65,6 +81,7 @@ function walkYamlFiles(rootPath: string): string[] {
|
||||
for (const entry of entries) {
|
||||
const absPath = join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (SKIP_DIRS.has(entry.name)) continue;
|
||||
stack.push(absPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,32 @@ export class WorkflowSpecCache {
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* OPR.0.3.2.22 Bug 4 — prune cached rows whose source_path lives in
|
||||
* noise directories that the post-Bug-4 walkYamlFiles SKIP_DIRS
|
||||
* guard now refuses to scan. Without this prune, rows that were
|
||||
* inserted before the SKIP_DIRS guard landed would survive forever
|
||||
* (the scanner cleanup at spec-library-workflow-scanner.ts only
|
||||
* fires for paths starting with the workspace `workflows/` folder
|
||||
* prefix). Called once at startup. Returns the number of rows
|
||||
* removed.
|
||||
*/
|
||||
pruneNoiseDirRows(): number {
|
||||
const result = this.db
|
||||
.prepare(
|
||||
`DELETE FROM workflow_specs WHERE
|
||||
source_path LIKE '%/.worktrees/%'
|
||||
OR source_path LIKE '%/node_modules/%'
|
||||
OR source_path LIKE '%/.git/%'
|
||||
OR source_path LIKE '%/dist/%'
|
||||
OR source_path LIKE '%/build/%'
|
||||
OR source_path LIKE '%/.turbo/%'
|
||||
OR source_path LIKE '%/.next/%'`,
|
||||
)
|
||||
.run();
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
getByIdOrThrow(specId: string): WorkflowSpecRow {
|
||||
const row = this.db
|
||||
.prepare(`SELECT * FROM workflow_specs WHERE spec_id = ?`)
|
||||
|
||||
@@ -907,6 +907,17 @@ export async function createDaemon(opts?: DaemonOptions): Promise<DaemonResult>
|
||||
// `<workspace.specs_root>/workflows`; SettingsStore resolves
|
||||
// workspaceSpecsRoot from env > config > workspace-default.
|
||||
deps.workflowSpecCache = workflowRuntime.specCache;
|
||||
|
||||
// OPR.0.3.2.22 Bug 4 — one-time prune of cache rows whose source_path
|
||||
// lives in noise directories (.worktrees, node_modules, etc.). The
|
||||
// post-Bug-4 walkYamlFiles SKIP_DIRS guard prevents NEW rows from
|
||||
// those locations, but legacy rows from prior daemon versions (or
|
||||
// operators who hand-imported a spec via path-form before SKIP_DIRS
|
||||
// shipped) would persist without this prune. Cheap (single DELETE
|
||||
// with bounded LIKE patterns) and safe (matches only the same
|
||||
// directories the walker now refuses to enter).
|
||||
workflowRuntime.specCache.pruneNoiseDirRows();
|
||||
|
||||
try {
|
||||
const settingsStore = new ContextPackSettingsStore();
|
||||
const cfg = settingsStore.resolveConfig();
|
||||
|
||||
@@ -169,4 +169,29 @@ describe("SpecLibraryService", () => {
|
||||
expect(rigs).toHaveLength(1);
|
||||
expect(rigs[0]!.relativePath).toBe("rigs/launch/demo/rig.yaml");
|
||||
});
|
||||
|
||||
// OPR.0.3.2.22 Bug 4 — walkYamlFiles now skips noise directories
|
||||
// (.worktrees, node_modules, .git, dist, build, .turbo, .next). The
|
||||
// conveyor.yaml under .worktrees/release-0.3.0-... is the load-bearing
|
||||
// case from the openrig-comms paper-cut report; this pins the
|
||||
// SKIP_DIRS guard at the SpecLibraryService.scan() seam.
|
||||
it("scan skips yaml files inside noise directories (SKIP_DIRS)", () => {
|
||||
writeFileSync(join(tmpDir, "canonical.yaml"), VALID_RIG_YAML);
|
||||
|
||||
const NOISE_DIRS = [".worktrees", "node_modules", ".git", "dist", "build", ".turbo", ".next"];
|
||||
for (const dir of NOISE_DIRS) {
|
||||
mkdirSync(join(tmpDir, dir, "deeper"), { recursive: true });
|
||||
writeFileSync(join(tmpDir, dir, "deeper", "noise.yaml"), VALID_RIG_YAML.replace("test-rig", `noise-${dir}`));
|
||||
}
|
||||
|
||||
const lib = createLibrary();
|
||||
lib.scan();
|
||||
|
||||
const entries = lib.list();
|
||||
expect(entries, `expected only canonical rig (got ${JSON.stringify(entries.map((e) => e.relativePath))})`).toHaveLength(1);
|
||||
expect(entries[0]!.sourcePath).toContain("canonical.yaml");
|
||||
for (const dir of NOISE_DIRS) {
|
||||
expect(entries.every((e) => !e.sourcePath.includes(`/${dir}/`)), `entry from ${dir} leaked through SKIP_DIRS`).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -157,4 +157,44 @@ describe("WorkflowSpecCache (PL-004 Phase D)", () => {
|
||||
const found = cache.getByNameVersion("test-three-step", "1");
|
||||
expect(found?.specId).toBe(cached.specId);
|
||||
});
|
||||
|
||||
// OPR.0.3.2.22 Bug 4 — startup prune removes legacy cache rows whose
|
||||
// source_path lives in noise directories that walkYamlFiles' new
|
||||
// SKIP_DIRS guard now refuses to scan. Without this prune, stale
|
||||
// rows from before SKIP_DIRS shipped would survive forever and keep
|
||||
// showing up in `rig specs show` / `rig specs preview` candidates.
|
||||
it("pruneNoiseDirRows removes cache rows from .worktrees/node_modules paths and preserves canonical-path rows", () => {
|
||||
const fixtures: Array<{ path: string; specName: string; noise: boolean }> = [
|
||||
{ path: join(tmp, "workflows", "canon.yaml"), specName: "canonical-spec", noise: false },
|
||||
{ path: join(tmp, ".worktrees", "feature-branch", "workflows", "stale.yaml"), specName: "stale-worktree-spec", noise: true },
|
||||
{ path: join(tmp, "node_modules", "@vendor", "spec", "stale.yaml"), specName: "stale-node-modules-spec", noise: true },
|
||||
{ path: join(tmp, "dist", "stale.yaml"), specName: "stale-dist-spec", noise: true },
|
||||
];
|
||||
const { mkdirSync } = require("node:fs") as typeof import("node:fs");
|
||||
for (const f of fixtures) {
|
||||
mkdirSync(join(f.path, ".."), { recursive: true });
|
||||
writeFileSync(f.path, SAMPLE_SPEC.replace("test-three-step", f.specName));
|
||||
cache.readThrough(f.path);
|
||||
}
|
||||
|
||||
expect(cache.listAll()).toHaveLength(4);
|
||||
|
||||
const removed = cache.pruneNoiseDirRows();
|
||||
expect(removed, "expected 3 noise rows removed (.worktrees + node_modules + dist)").toBe(3);
|
||||
|
||||
const remaining = cache.listAll();
|
||||
expect(remaining).toHaveLength(1);
|
||||
expect(remaining[0]!.sourcePath).toBe(fixtures[0]!.path);
|
||||
});
|
||||
|
||||
it("pruneNoiseDirRows returns 0 when there are no noise rows", () => {
|
||||
const canonicalPath = join(tmp, "workflows", "canon.yaml");
|
||||
const { mkdirSync } = require("node:fs") as typeof import("node:fs");
|
||||
mkdirSync(join(canonicalPath, ".."), { recursive: true });
|
||||
writeFileSync(canonicalPath, SAMPLE_SPEC);
|
||||
cache.readThrough(canonicalPath);
|
||||
|
||||
expect(cache.pruneNoiseDirRows()).toBe(0);
|
||||
expect(cache.listAll()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user