@warpgogol/forge 5.3.3 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/bin/cli.ts +7 -2
- package/dist/bin/cli.js +7 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/adr/adr.module.d.ts.map +1 -1
- package/dist/os/adr/adr.module.js +2 -0
- package/dist/os/adr/adr.module.js.map +1 -1
- package/dist/os/audit/audit.module.d.ts.map +1 -1
- package/dist/os/audit/audit.module.js +2 -0
- package/dist/os/audit/audit.module.js.map +1 -1
- package/dist/os/compass/compass.module.d.ts.map +1 -1
- package/dist/os/compass/compass.module.js +48 -16
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.d.ts +2 -0
- package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.js +39 -9
- package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-docs-validate.d.ts +13 -0
- package/dist/os/compass/handlers/compass-docs-validate.d.ts.map +1 -0
- package/dist/os/compass/handlers/compass-docs-validate.js +326 -0
- package/dist/os/compass/handlers/compass-docs-validate.js.map +1 -0
- package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.js +5 -2
- package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
- package/dist/os/compass/handlers/summary-record.d.ts +17 -3
- package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
- package/dist/os/compass/handlers/summary-record.js +107 -10
- package/dist/os/compass/handlers/summary-record.js.map +1 -1
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.d.ts +18 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.d.ts.map +1 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.js +64 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.js.map +1 -0
- package/dist/os/compass/policy.d.ts +6 -1
- package/dist/os/compass/policy.d.ts.map +1 -1
- package/dist/os/compass/policy.js +8 -2
- package/dist/os/compass/policy.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +124 -73
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/exploration/exploration.module.d.ts.map +1 -1
- package/dist/os/exploration/exploration.module.js +3 -0
- package/dist/os/exploration/exploration.module.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +1 -2
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/naming/naming.module.d.ts.map +1 -1
- package/dist/os/naming/naming.module.js +9 -2
- package/dist/os/naming/naming.module.js.map +1 -1
- package/dist/os/notes/notes.module.d.ts.map +1 -1
- package/dist/os/notes/notes.module.js +17 -6
- package/dist/os/notes/notes.module.js.map +1 -1
- package/dist/os/plan/plan.module.d.ts.map +1 -1
- package/dist/os/plan/plan.module.js +2 -0
- package/dist/os/plan/plan.module.js.map +1 -1
- package/dist/os/plugin/plugin.module.d.ts.map +1 -1
- package/dist/os/plugin/plugin.module.js +9 -2
- package/dist/os/plugin/plugin.module.js.map +1 -1
- package/dist/os/program/program.module.d.ts.map +1 -1
- package/dist/os/program/program.module.js +19 -4
- package/dist/os/program/program.module.js.map +1 -1
- package/dist/os/queue/handlers/queue-validate.d.ts.map +1 -1
- package/dist/os/queue/handlers/queue-validate.js +154 -7
- package/dist/os/queue/handlers/queue-validate.js.map +1 -1
- package/dist/os/queue/manifest.d.ts +17 -1
- package/dist/os/queue/manifest.d.ts.map +1 -1
- package/dist/os/queue/manifest.js +127 -6
- package/dist/os/queue/manifest.js.map +1 -1
- package/dist/os/queue/queue.module.d.ts.map +1 -1
- package/dist/os/queue/queue.module.js +32 -4
- package/dist/os/queue/queue.module.js.map +1 -1
- package/dist/os/queue/types.d.ts +100 -1
- package/dist/os/queue/types.d.ts.map +1 -1
- package/dist/os/queue/types.js +44 -0
- package/dist/os/queue/types.js.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.js +28 -5
- package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
- package/dist/os/rfc/handlers/validate.js +1 -1
- package/dist/os/rfc/rfc.module.d.ts.map +1 -1
- package/dist/os/rfc/rfc.module.js +27 -3
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/rfc/types.d.ts +11 -0
- package/dist/os/rfc/types.d.ts.map +1 -1
- package/dist/os/rfc/types.js +1 -0
- package/dist/os/rfc/types.js.map +1 -1
- package/dist/os/rfc/verification-evidence.d.ts +21 -0
- package/dist/os/rfc/verification-evidence.d.ts.map +1 -1
- package/dist/os/rfc/verification-evidence.js +140 -23
- package/dist/os/rfc/verification-evidence.js.map +1 -1
- package/dist/os/rfc/verification-refresh.d.ts.map +1 -1
- package/dist/os/rfc/verification-refresh.js +72 -37
- package/dist/os/rfc/verification-refresh.js.map +1 -1
- package/dist/os/session/handlers/save.d.ts.map +1 -1
- package/dist/os/session/handlers/save.js +46 -5
- package/dist/os/session/handlers/save.js.map +1 -1
- package/dist/os/session/session.module.d.ts.map +1 -1
- package/dist/os/session/session.module.js +5 -0
- package/dist/os/session/session.module.js.map +1 -1
- package/dist/os/spec/live-spec-merge.d.ts.map +1 -1
- package/dist/os/spec/live-spec-merge.js +55 -134
- package/dist/os/spec/live-spec-merge.js.map +1 -1
- package/dist/os/spec/live-spec-rebuild.d.ts +32 -0
- package/dist/os/spec/live-spec-rebuild.d.ts.map +1 -0
- package/dist/os/spec/live-spec-rebuild.js +272 -0
- package/dist/os/spec/live-spec-rebuild.js.map +1 -0
- package/dist/os/spec/live-spec-shared.d.ts +62 -0
- package/dist/os/spec/live-spec-shared.d.ts.map +1 -0
- package/dist/os/spec/live-spec-shared.js +315 -0
- package/dist/os/spec/live-spec-shared.js.map +1 -0
- package/dist/os/spec/live-spec-types.d.ts +17 -1
- package/dist/os/spec/live-spec-types.d.ts.map +1 -1
- package/dist/os/spec/live-spec-types.js +3 -0
- package/dist/os/spec/live-spec-types.js.map +1 -1
- package/dist/os/spec/live-spec-validate.d.ts.map +1 -1
- package/dist/os/spec/live-spec-validate.js +105 -2
- package/dist/os/spec/live-spec-validate.js.map +1 -1
- package/dist/os/spec/spec-materialize.d.ts +2 -0
- package/dist/os/spec/spec-materialize.d.ts.map +1 -1
- package/dist/os/spec/spec-materialize.js +45 -5
- package/dist/os/spec/spec-materialize.js.map +1 -1
- package/dist/os/spec/spec-schema.d.ts +19 -0
- package/dist/os/spec/spec-schema.d.ts.map +1 -1
- package/dist/os/spec/spec-schema.js +30 -0
- package/dist/os/spec/spec-schema.js.map +1 -1
- package/dist/os/spec/spec-status.d.ts +4 -0
- package/dist/os/spec/spec-status.d.ts.map +1 -1
- package/dist/os/spec/spec-status.js +19 -1
- package/dist/os/spec/spec-status.js.map +1 -1
- package/dist/os/spec/spec-validate.d.ts.map +1 -1
- package/dist/os/spec/spec-validate.js +55 -8
- package/dist/os/spec/spec-validate.js.map +1 -1
- package/dist/os/spec/spec.module.d.ts.map +1 -1
- package/dist/os/spec/spec.module.js +84 -8
- package/dist/os/spec/spec.module.js.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.d.ts.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js +10 -4
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.js +13 -4
- package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
- package/dist/os/workflow/workflow.module.d.ts.map +1 -1
- package/dist/os/workflow/workflow.module.js +13 -5
- package/dist/os/workflow/workflow.module.js.map +1 -1
- package/dist/src/cli-flags.d.ts.map +1 -1
- package/dist/src/cli-flags.js +25 -5
- package/dist/src/cli-flags.js.map +1 -1
- package/dist/src/config/forge-config.d.ts +24 -3
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +38 -7
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +26 -10
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +49 -2
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +8 -3
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +24 -8
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/dist/src/pipeline-status.d.ts +14 -1
- package/dist/src/pipeline-status.d.ts.map +1 -1
- package/dist/src/pipeline-status.js +28 -8
- package/dist/src/pipeline-status.js.map +1 -1
- package/dist/src/types.d.ts +7 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/generated-marker.d.ts +8 -0
- package/dist/src/utils/generated-marker.d.ts.map +1 -1
- package/dist/src/utils/generated-marker.js +25 -0
- package/dist/src/utils/generated-marker.js.map +1 -1
- package/dist/src/validators/skill-validate.d.ts.map +1 -1
- package/dist/src/validators/skill-validate.js +42 -15
- package/dist/src/validators/skill-validate.js.map +1 -1
- package/os/adr/adr.module.ts +2 -0
- package/os/audit/audit.module.ts +2 -0
- package/os/compass/compass.module.ts +50 -16
- package/os/compass/handlers/compass-audit-handler.ts +50 -9
- package/os/compass/handlers/compass-docs-validate.ts +359 -0
- package/os/compass/handlers/compass-inventory.ts +6 -3
- package/os/compass/handlers/summary-record.ts +118 -10
- package/os/compass/handlers/tests/compass-audit-record.test.ts +2 -1
- package/os/compass/handlers/tests/compass-audit-validate.test.ts +92 -3
- package/os/compass/handlers/tests/compass-docs-validate.test.ts +203 -0
- package/os/compass/handlers/tests/compass-ledger-scope.test.ts +2 -1
- package/os/compass/handlers/tests/compass-policy.test.ts +15 -18
- package/os/compass/handlers/tests/compass-v2-contract.test.ts +38 -0
- package/os/compass/handlers/tests/forge-yaml-fixture.ts +69 -0
- package/os/compass/handlers/tests/summary-record.test.ts +152 -0
- package/os/compass/policy.ts +8 -2
- package/os/core/core.module.ts +147 -75
- package/os/core/docs-archive.test.ts +136 -0
- package/os/exploration/exploration.module.ts +3 -0
- package/os/mission/handlers/archive.ts +1 -2
- package/os/naming/naming.module.ts +9 -2
- package/os/notes/notes.module.ts +17 -6
- package/os/plan/plan-0000-template.md +150 -0
- package/os/plan/plan.module.ts +2 -0
- package/os/plugin/plugin.module.ts +9 -2
- package/os/program/program.module.ts +19 -4
- package/os/queue/decision-ledger.test.ts +163 -0
- package/os/queue/handlers/queue-validate.ts +190 -8
- package/os/queue/manifest.ts +201 -7
- package/os/queue/queue-validate.test.ts +303 -0
- package/os/queue/queue.module.ts +32 -4
- package/os/queue/types.ts +56 -1
- package/os/rfc/handlers/implement-stamp.ts +35 -5
- package/os/rfc/handlers/validate.ts +1 -1
- package/os/rfc/rfc.module.ts +29 -3
- package/os/rfc/types.ts +17 -1
- package/os/rfc/verification-emit.test.ts +305 -0
- package/os/rfc/verification-evidence.ts +155 -24
- package/os/rfc/verification-refresh.test.ts +222 -1
- package/os/rfc/verification-refresh.ts +80 -38
- package/os/session/handlers/save.ts +41 -5
- package/os/session/session.module.ts +5 -0
- package/os/spec/live-spec-idempotency.pbt.test.ts +222 -0
- package/os/spec/live-spec-list-show-validate.test.ts +10 -6
- package/os/spec/live-spec-merge.test.ts +187 -7
- package/os/spec/live-spec-merge.ts +71 -155
- package/os/spec/live-spec-rebuild.test.ts +317 -0
- package/os/spec/live-spec-rebuild.ts +356 -0
- package/os/spec/live-spec-shared.ts +378 -0
- package/os/spec/live-spec-types.ts +21 -1
- package/os/spec/live-spec-validate.test.ts +317 -0
- package/os/spec/live-spec-validate.ts +106 -2
- package/os/spec/spec-materialize.test.ts +192 -0
- package/os/spec/spec-materialize.ts +48 -4
- package/os/spec/spec-schema.ts +49 -0
- package/os/spec/spec-status.ts +24 -0
- package/os/spec/spec-validate.test.ts +66 -0
- package/os/spec/spec-validate.ts +62 -8
- package/os/spec/spec.module.ts +85 -8
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +10 -4
- package/os/werkstatt/werkstatt.module.ts +13 -4
- package/os/workflow/workflow.module.ts +13 -5
- package/package.json +1 -1
- package/skills/_shared/fo-pipeline-conventions.md +126 -1
- package/skills/fo/fo-compass-annotate/SKILL.md +1 -1
- package/skills/fo/fo-doc-audit/SKILL.md +4 -4
- package/skills/fo/fo-explore/SKILL.md +2 -1
- package/skills/fo/fo-fix/SKILL.md +11 -3
- package/skills/fo/fo-handoff/SKILL.md +6 -8
- package/skills/fo/fo-idea-audit/SKILL.md +1 -1
- package/skills/fo/fo-idea-create-rfc/SKILL.md +3 -2
- package/skills/fo/fo-idea-enhance/SKILL.md +23 -4
- package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +86 -22
- package/skills/fo/fo-idea-implement/SKILL.md +57 -13
- package/skills/fo/fo-idea-plan/SKILL.md +28 -6
- package/skills/fo/fo-review/SKILL.md +69 -8
- package/skills/fo/fo-session-retro/SKILL.md +4 -3
- package/skills/fo/fo-session-save/SKILL.md +1 -1
- package/skills/fo/fo-step-commit/SKILL.md +2 -0
- package/skills/shared/grilling/SKILL.md +12 -0
- package/skills/shared/writing-great-skills/GLOSSARY.md +2 -0
- package/skills/shared/writing-great-skills/SKILL.md +2 -1
- package/src/cli-flags.ts +23 -5
- package/src/config/forge-config.ts +106 -57
- package/src/onboarding/create.ts +25 -9
- package/src/onboarding/doctor.ts +51 -2
- package/src/onboarding/init.ts +8 -3
- package/src/onboarding/upgrade.ts +36 -6
- package/src/pipeline-status.ts +32 -8
- package/src/tests/bindings-schema.test.ts +53 -1
- package/src/tests/cli-flags.test.ts +20 -0
- package/src/tests/cli-output.test.ts +13 -7
- package/src/tests/doctor-bindings.test.ts +7 -2
- package/src/tests/doctor-skills-unmanaged.test.ts +145 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +7 -7
- package/src/tests/forge-config.test.ts +106 -2
- package/src/tests/implement-stamp.test.ts +38 -0
- package/src/tests/session-handlers.test.ts +33 -0
- package/src/tests/skill-validate.test.ts +180 -0
- package/src/tests/upgrade.test.ts +60 -0
- package/src/types.ts +13 -1
- package/src/utils/generated-marker.ts +28 -0
- package/src/validators/skill-validate.ts +43 -16
- package/skills/fo/fo-idea-implement/ADR-FLOW.md +0 -179
- package/skills/fo/fo-review/AXES.md +0 -70
|
@@ -16,6 +16,7 @@ import { join } from "node:path";
|
|
|
16
16
|
import { execFile } from "node:child_process";
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
18
|
import { runCompassAuditValidate } from "../compass-audit-handler.ts";
|
|
19
|
+
import { ambientIo } from "../../../../src/utils/io.ts";
|
|
19
20
|
import type { ForgeCommandInput, ForgeRuntimeContext } from "../../../../src/types.ts";
|
|
20
21
|
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
@@ -31,15 +32,15 @@ const logger = {
|
|
|
31
32
|
},
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
35
|
+
function makeContext(workspaceRoot: string, dryRun = false): ForgeRuntimeContext {
|
|
35
36
|
return {
|
|
36
37
|
workspaceRoot,
|
|
37
38
|
site: undefined,
|
|
38
39
|
siteExplicit: false,
|
|
39
40
|
logger: logger as never,
|
|
40
|
-
dryRun
|
|
41
|
+
dryRun,
|
|
41
42
|
outputFormat: "json",
|
|
42
|
-
io:
|
|
43
|
+
io: ambientIo,
|
|
43
44
|
actualState: undefined as never,
|
|
44
45
|
fileIntents: [],
|
|
45
46
|
} as unknown as ForgeRuntimeContext;
|
|
@@ -131,3 +132,91 @@ describe("compass.audit.validate — RFC-1143 ledger-ineligible skips", () => {
|
|
|
131
132
|
expect(data.skippedIneligible).toBeGreaterThan(0);
|
|
132
133
|
});
|
|
133
134
|
});
|
|
135
|
+
|
|
136
|
+
// RFC-1242 AC-5/AC-6: --prune drops ledger entries for paths that no longer
|
|
137
|
+
// exist while preserving every surviving entry's verdict fields.
|
|
138
|
+
const DEAD_PATH = "packages/gone/deleted.ts";
|
|
139
|
+
const LEDGER_ENTRY = (path: string) =>
|
|
140
|
+
` - path: ${path}\n entityId: "eid-${path}"\n auditedRevision: 7\n auditedHash: "hash-${path}"\n auditedAt: "2026-01-01T00:00:00Z"\n verdict: pass\n agent: human:test\n`;
|
|
141
|
+
|
|
142
|
+
describe("compass.audit.validate --prune — RFC-1242", () => {
|
|
143
|
+
let root: string;
|
|
144
|
+
|
|
145
|
+
beforeEach(async () => {
|
|
146
|
+
root = await mkdtemp(join(tmpdir(), "compass-prune-"));
|
|
147
|
+
await mkdir(join(root, "packages", "foo"), { recursive: true });
|
|
148
|
+
await mkdir(join(root, "docs"), { recursive: true });
|
|
149
|
+
await writeFile(join(root, "packages", "foo", "index.ts"), "export const x = 1;\n", "utf8");
|
|
150
|
+
await writeFile(
|
|
151
|
+
join(root, "docs", "compass-audit-ledger.generated.yaml"),
|
|
152
|
+
`entries:\n${LEDGER_ENTRY(ELIGIBLE_FILE)}${LEDGER_ENTRY(DEAD_PATH)}`,
|
|
153
|
+
"utf8",
|
|
154
|
+
);
|
|
155
|
+
await execFileAsync("git", ["init"], { cwd: root });
|
|
156
|
+
await execFileAsync("git", ["config", "user.email", "test@test.com"], { cwd: root });
|
|
157
|
+
await execFileAsync("git", ["config", "user.name", "Test"], { cwd: root });
|
|
158
|
+
await execFileAsync("git", ["add", "."], { cwd: root });
|
|
159
|
+
await execFileAsync("git", ["commit", "-m", "init"], { cwd: root });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
afterEach(async () => {
|
|
163
|
+
await rm(root, { recursive: true, force: true });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("AC-5: ledger contains no entry whose path does not exist after prune", async () => {
|
|
167
|
+
const result = await runCompassAuditValidate(
|
|
168
|
+
makeInput({ prune: true, root: ["packages"] }),
|
|
169
|
+
makeContext(root),
|
|
170
|
+
);
|
|
171
|
+
const data = result.data as { pruned: number; prunedPaths: string[] };
|
|
172
|
+
expect(data.pruned).toBe(1);
|
|
173
|
+
expect(data.prunedPaths).toEqual([DEAD_PATH]);
|
|
174
|
+
const { readFile } = await import("node:fs/promises");
|
|
175
|
+
const ledgerText = await readFile(
|
|
176
|
+
join(root, "docs", "compass-audit-ledger.generated.yaml"),
|
|
177
|
+
"utf8",
|
|
178
|
+
);
|
|
179
|
+
expect(ledgerText).not.toContain(DEAD_PATH);
|
|
180
|
+
expect(ledgerText).toContain(ELIGIBLE_FILE);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("AC-6: surviving entries keep their verdict fields unchanged", async () => {
|
|
184
|
+
await runCompassAuditValidate(
|
|
185
|
+
makeInput({ prune: true, root: ["packages"] }),
|
|
186
|
+
makeContext(root),
|
|
187
|
+
);
|
|
188
|
+
const { readFile } = await import("node:fs/promises");
|
|
189
|
+
const { parse } = await import("yaml");
|
|
190
|
+
const ledger = parse(
|
|
191
|
+
await readFile(join(root, "docs", "compass-audit-ledger.generated.yaml"), "utf8"),
|
|
192
|
+
) as { entries: Array<Record<string, unknown>> };
|
|
193
|
+
const survivor = ledger.entries.find((e) => e.path === ELIGIBLE_FILE);
|
|
194
|
+
expect(
|
|
195
|
+
survivor,
|
|
196
|
+
"surviving ledger entry must keep its verdict fields — check the keep/drop partition",
|
|
197
|
+
).toMatchObject({
|
|
198
|
+
entityId: `eid-${ELIGIBLE_FILE}`,
|
|
199
|
+
auditedRevision: 7,
|
|
200
|
+
auditedHash: `hash-${ELIGIBLE_FILE}`,
|
|
201
|
+
auditedAt: "2026-01-01T00:00:00Z",
|
|
202
|
+
verdict: "pass",
|
|
203
|
+
agent: "human:test",
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("--prune --dry-run reports the drop set without rewriting the ledger", async () => {
|
|
208
|
+
const { readFile } = await import("node:fs/promises");
|
|
209
|
+
const ledgerPath = join(root, "docs", "compass-audit-ledger.generated.yaml");
|
|
210
|
+
const before = await readFile(ledgerPath, "utf8");
|
|
211
|
+
const result = await runCompassAuditValidate(
|
|
212
|
+
makeInput({ prune: true, root: ["packages"] }),
|
|
213
|
+
makeContext(root, true),
|
|
214
|
+
);
|
|
215
|
+
const data = result.data as { pruned: number };
|
|
216
|
+
expect(data.pruned).toBe(1);
|
|
217
|
+
expect(
|
|
218
|
+
await readFile(ledgerPath, "utf8"),
|
|
219
|
+
"dry-run must not rewrite the ledger — writes must route through context.io honoring context.dryRun",
|
|
220
|
+
).toBe(before);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-1242 AC-1..AC-4: compass.docs.validate scans the docs/*.xml corpus and
|
|
5
|
+
must fail on unresolvable <path> entries (COMPASS-DOC-01) and dead workspace
|
|
6
|
+
ids (COMPASS-DOC-02), warn on unresolvable link targets (COMPASS-DOC-03),
|
|
7
|
+
and flag malformed XML (COMPASS-DOC-00). A green corpus exits 0 with
|
|
8
|
+
status-shaped data.
|
|
9
|
+
</purpose>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
14
|
+
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { runCompassDocsValidate } from "../compass-docs-validate.ts";
|
|
18
|
+
import { ambientIo } from "../../../../src/utils/io.ts";
|
|
19
|
+
import type { Diagnostic, ForgeCommandInput, ForgeRuntimeContext } from "../../../../src/types.ts";
|
|
20
|
+
|
|
21
|
+
const logger = {
|
|
22
|
+
section() {},
|
|
23
|
+
info() {},
|
|
24
|
+
warn() {},
|
|
25
|
+
error() {},
|
|
26
|
+
success() {},
|
|
27
|
+
getEvents() {
|
|
28
|
+
return [];
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
33
|
+
return {
|
|
34
|
+
workspaceRoot,
|
|
35
|
+
site: undefined,
|
|
36
|
+
siteExplicit: false,
|
|
37
|
+
logger: logger as never,
|
|
38
|
+
dryRun: false,
|
|
39
|
+
outputFormat: "json",
|
|
40
|
+
io: ambientIo,
|
|
41
|
+
actualState: undefined as never,
|
|
42
|
+
fileIntents: [],
|
|
43
|
+
} as unknown as ForgeRuntimeContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeInput(): ForgeCommandInput {
|
|
47
|
+
return { argv: [], flags: {} } as unknown as ForgeCommandInput;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const GREEN_DOC = `<root>
|
|
51
|
+
<path>\`docs/other.xml\`</path>
|
|
52
|
+
<workspace id="pkg-foo"><path>\`packages/foo\`</path></workspace>
|
|
53
|
+
<node id="packages.foo" type="package"><path>\`packages/foo\`</path></node>
|
|
54
|
+
<node id="root.a" type="compass-doc"><link rel="binds-to" target="root.b" /></node>
|
|
55
|
+
<node id="root.b" type="compass-doc" />
|
|
56
|
+
</root>
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
describe("compass.docs.validate — RFC-1242", () => {
|
|
60
|
+
let root: string;
|
|
61
|
+
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
root = await mkdtemp(join(tmpdir(), "compass-docs-"));
|
|
64
|
+
await mkdir(join(root, "docs"), { recursive: true });
|
|
65
|
+
await mkdir(join(root, "packages", "foo"), { recursive: true });
|
|
66
|
+
await writeFile(join(root, "packages", "foo", "index.ts"), "export {};\n", "utf8");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
await rm(root, { recursive: true, force: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("AC-2: green corpus exits 0 and reports scanned counts", async () => {
|
|
74
|
+
await writeFile(join(root, "docs", "a.xml"), GREEN_DOC, "utf8");
|
|
75
|
+
await writeFile(join(root, "docs", "other.xml"), "<root/>", "utf8");
|
|
76
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
77
|
+
expect(result.exitCode).toBe(0);
|
|
78
|
+
const data = result.data as {
|
|
79
|
+
scanned: { xmlFiles: number; pathsChecked: number; idsChecked: number };
|
|
80
|
+
diagnostics: Diagnostic[];
|
|
81
|
+
};
|
|
82
|
+
expect(data.scanned.xmlFiles).toBe(2);
|
|
83
|
+
expect(data.diagnostics).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("AC-3: dead <path> emits COMPASS-DOC-01 error", async () => {
|
|
87
|
+
await writeFile(
|
|
88
|
+
join(root, "docs", "a.xml"),
|
|
89
|
+
`<root><path>\`packages/gone\`</path></root>`,
|
|
90
|
+
"utf8",
|
|
91
|
+
);
|
|
92
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
93
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
94
|
+
expect(
|
|
95
|
+
diagnostics.some((d) => d.ruleId === "COMPASS-DOC-01" && d.severity === "error"),
|
|
96
|
+
"dead <path> must emit COMPASS-DOC-01 — check pathTokens/pathCheckTarget resolution",
|
|
97
|
+
).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("AC-4: dead <path> produces a non-zero exit", async () => {
|
|
101
|
+
await writeFile(
|
|
102
|
+
join(root, "docs", "a.xml"),
|
|
103
|
+
`<root><path>\`packages/gone\`</path></root>`,
|
|
104
|
+
"utf8",
|
|
105
|
+
);
|
|
106
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
107
|
+
expect(result.exitCode).toBe(1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("dead workspace id emits COMPASS-DOC-02 error", async () => {
|
|
111
|
+
await writeFile(join(root, "docs", "a.xml"), `<root><workspace id="pkg-gone"/></root>`, "utf8");
|
|
112
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
113
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
114
|
+
expect(
|
|
115
|
+
diagnostics.some((d) => d.ruleId === "COMPASS-DOC-02" && d.message.includes("pkg-gone")),
|
|
116
|
+
"dead workspace id must emit COMPASS-DOC-02 — check workspaceIdTarget mapping",
|
|
117
|
+
).toBe(true);
|
|
118
|
+
expect(result.exitCode).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("glob-tail <path> resolves against the parent directory", async () => {
|
|
122
|
+
await writeFile(
|
|
123
|
+
join(root, "docs", "a.xml"),
|
|
124
|
+
`<root><path>\`packages/*\`</path></root>`,
|
|
125
|
+
"utf8",
|
|
126
|
+
);
|
|
127
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
128
|
+
expect(result.exitCode).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("unresolvable link target emits COMPASS-DOC-03 warning, never an error", async () => {
|
|
132
|
+
await writeFile(
|
|
133
|
+
join(root, "docs", "a.xml"),
|
|
134
|
+
`<root><node id="x"><link rel="binds-to" target="docs.rfcs.rfc-9999" /></node></root>`,
|
|
135
|
+
"utf8",
|
|
136
|
+
);
|
|
137
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
138
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
139
|
+
expect(
|
|
140
|
+
diagnostics.some((d) => d.ruleId === "COMPASS-DOC-03" && d.severity === "warning"),
|
|
141
|
+
"dangling link target must warn via COMPASS-DOC-03",
|
|
142
|
+
).toBe(true);
|
|
143
|
+
expect(result.exitCode).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("link target resolving to an archived RFC file passes", async () => {
|
|
147
|
+
await mkdir(join(root, "docs", "rfcs", "archive"), { recursive: true });
|
|
148
|
+
await writeFile(join(root, "docs", "rfcs", "archive", "rfc-0097-old.md"), "x", "utf8");
|
|
149
|
+
await writeFile(
|
|
150
|
+
join(root, "docs", "a.xml"),
|
|
151
|
+
`<root><node id="x"><link rel="binds-to" target="docs.rfcs.rfc-0097" /></node></root>`,
|
|
152
|
+
"utf8",
|
|
153
|
+
);
|
|
154
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
155
|
+
expect(result.exitCode).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("malformed XML emits COMPASS-DOC-00 and skips ref checks for that file", async () => {
|
|
159
|
+
await writeFile(join(root, "docs", "a.xml"), `<root><name>oops</root>`, "utf8");
|
|
160
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
161
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
162
|
+
expect(diagnostics.some((d) => d.ruleId === "COMPASS-DOC-00")).toBe(true);
|
|
163
|
+
expect(diagnostics.some((d) => d.ruleId === "COMPASS-DOC-01")).toBe(false);
|
|
164
|
+
expect(result.exitCode).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("quoted attribute value ending in / is not misread as self-closing", async () => {
|
|
168
|
+
await writeFile(
|
|
169
|
+
join(root, "docs", "a.xml"),
|
|
170
|
+
`<root><node id="x" type="compass-doc" path="docs/"><link rel="binds-to" target="x" /></node></root>`,
|
|
171
|
+
"utf8",
|
|
172
|
+
);
|
|
173
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
174
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
175
|
+
expect(diagnostics.some((d) => d.ruleId === "COMPASS-DOC-00")).toBe(false);
|
|
176
|
+
expect(result.exitCode).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("docs.plans.plan-rfc-* link target resolves to a plan-*.md basename", async () => {
|
|
180
|
+
await mkdir(join(root, "docs", "plans"), { recursive: true });
|
|
181
|
+
await writeFile(join(root, "docs", "plans", "plan-rfc-1242-x.md"), "x", "utf8");
|
|
182
|
+
await writeFile(
|
|
183
|
+
join(root, "docs", "a.xml"),
|
|
184
|
+
`<root><node id="x"><link rel="binds-to" target="docs.plans.plan-rfc-1242-x" /></node></root>`,
|
|
185
|
+
"utf8",
|
|
186
|
+
);
|
|
187
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
188
|
+
const diagnostics = (result.data as { diagnostics: Diagnostic[] }).diagnostics;
|
|
189
|
+
expect(diagnostics.some((d) => d.ruleId === "COMPASS-DOC-03")).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("AC-1: result data carries command-shaped payload fields", async () => {
|
|
193
|
+
await writeFile(join(root, "docs", "a.xml"), GREEN_DOC, "utf8");
|
|
194
|
+
await writeFile(join(root, "docs", "other.xml"), "<root/>", "utf8");
|
|
195
|
+
const result = await runCompassDocsValidate(makeInput(), makeContext(root));
|
|
196
|
+
const data = result.data as Record<string, unknown>;
|
|
197
|
+
expect(data).toHaveProperty("command", "compass.docs.validate");
|
|
198
|
+
expect(data).toHaveProperty("status", "pass");
|
|
199
|
+
expect(data).toHaveProperty("scanned");
|
|
200
|
+
expect(data).toHaveProperty("diagnostics");
|
|
201
|
+
expect(Array.isArray(data.diagnostics)).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -16,6 +16,7 @@ import { execFile } from "node:child_process";
|
|
|
16
16
|
import { promisify } from "node:util";
|
|
17
17
|
import { parse as yamlParse } from "yaml";
|
|
18
18
|
import { runCompassAuditBaseline } from "../compass-audit-handler.ts";
|
|
19
|
+
import { ambientIo } from "../../../../src/utils/io.ts";
|
|
19
20
|
import type { ForgeCommandInput, ForgeRuntimeContext } from "../../../../src/types.ts";
|
|
20
21
|
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
@@ -39,7 +40,7 @@ function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
|
39
40
|
logger: logger as never,
|
|
40
41
|
dryRun: false,
|
|
41
42
|
outputFormat: "json",
|
|
42
|
-
io:
|
|
43
|
+
io: ambientIo,
|
|
43
44
|
actualState: undefined as never,
|
|
44
45
|
fileIntents: [],
|
|
45
46
|
} as unknown as ForgeRuntimeContext;
|
|
@@ -9,6 +9,8 @@ the named-config-error contract.</purpose>
|
|
|
9
9
|
</MODULE_CONTRACT>
|
|
10
10
|
<CHANGE_SUMMARY>
|
|
11
11
|
<item>RFC-1096: initial coverage for resolveCompassPolicy (AC-1..AC-5).</item>
|
|
12
|
+
<item>RFC-1220: step 1 — widen governance-ID tail regex</item>
|
|
13
|
+
<item>RFC-1220: fo-review — F-1 shared forge-yaml fixture, F-2 temp-dir cleanup</item>
|
|
12
14
|
</CHANGE_SUMMARY>
|
|
13
15
|
*/
|
|
14
16
|
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
parseGovernanceIdParts,
|
|
24
26
|
} from "../../policy.ts";
|
|
25
27
|
import { runCompassInventory } from "../compass-inventory-handler.ts";
|
|
28
|
+
import { forgeYaml } from "./forge-yaml-fixture.ts";
|
|
26
29
|
import type { ForgeRuntimeContext } from "../../../../src/types.ts";
|
|
27
30
|
|
|
28
31
|
// packages/forge — real forge root so `profile:` resolution finds shipped profiles.
|
|
@@ -30,24 +33,7 @@ const FORGE_ROOT = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
|
30
33
|
// os/compass directory — scanned by the AC-4 literal check.
|
|
31
34
|
const COMPASS_DIR = fileURLToPath(new URL("../../", import.meta.url));
|
|
32
35
|
|
|
33
|
-
const FORGE_YAML =
|
|
34
|
-
schema: forge/config@1
|
|
35
|
-
project:
|
|
36
|
-
name: test-ws
|
|
37
|
-
stack: [typescript]
|
|
38
|
-
packageManager: pnpm
|
|
39
|
-
paths:
|
|
40
|
-
rfcsDir: docs/rfcs
|
|
41
|
-
adrsDir: docs/adrs
|
|
42
|
-
plansDir: docs/plans
|
|
43
|
-
auditsDir: docs/audits
|
|
44
|
-
specsDir: docs/specs
|
|
45
|
-
skillsDir: .agents/skills
|
|
46
|
-
bindings:
|
|
47
|
-
schema: forge/bindings@1
|
|
48
|
-
commands: {}
|
|
49
|
-
paths: {}
|
|
50
|
-
${bindings}`;
|
|
36
|
+
const FORGE_YAML = forgeYaml;
|
|
51
37
|
|
|
52
38
|
const FORGE_YAML_WITH_PROFILE = `
|
|
53
39
|
schema: forge/config@1
|
|
@@ -246,4 +232,15 @@ describe("resolveCompassPolicy (RFC-1096)", () => {
|
|
|
246
232
|
});
|
|
247
233
|
expect(parseGovernanceIdParts("no-number")).toBeNull();
|
|
248
234
|
});
|
|
235
|
+
|
|
236
|
+
it("parseGovernanceIdParts accepts mission-format tails (RFC-1220)", () => {
|
|
237
|
+
expect(parseGovernanceIdParts("acme-m000175")).toEqual({
|
|
238
|
+
namespace: "acme",
|
|
239
|
+
numeric: 175,
|
|
240
|
+
});
|
|
241
|
+
expect(parseGovernanceIdParts("acme-m1")).toEqual({ namespace: "acme", numeric: 1 });
|
|
242
|
+
// Bare "m" without digits is not a numeric tail.
|
|
243
|
+
expect(parseGovernanceIdParts("acme-m")).toBeNull();
|
|
244
|
+
expect(parseGovernanceIdParts("no-number")).toBeNull();
|
|
245
|
+
});
|
|
249
246
|
});
|
|
@@ -3,6 +3,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { runCompassValidation, runCompassInventory } from "../compass-inventory-handler.ts";
|
|
6
|
+
import { writeForgeYaml } from "./forge-yaml-fixture.ts";
|
|
6
7
|
import type {
|
|
7
8
|
ForgeCommandInput,
|
|
8
9
|
ForgeCommandResult,
|
|
@@ -51,6 +52,13 @@ const MEDIUM_FILE = "packages/fixture-pkg/src/components/mission-widget.ts";
|
|
|
51
52
|
// Low-risk path: bare src/** maps to layer "source" → riskClass low.
|
|
52
53
|
const LOW_FILE = "packages/fixture-pkg/src/mission-notes.ts";
|
|
53
54
|
|
|
55
|
+
// Generic NS-NNN alternative plus a mission-tail alternative (RFC-1220).
|
|
56
|
+
// Single-quoted YAML keeps backslashes literal — JS "\\b" emits regex "\b".
|
|
57
|
+
const MISSION_ID_PATTERN = "\\b(?:[A-Z][A-Z0-9]*-)+\\d+\\b|\\b[a-z][a-z0-9-]*-m\\d{6}\\b";
|
|
58
|
+
// Admits a no-digit-tail token that passes idPatternFull yet cannot parse —
|
|
59
|
+
// simulates future policy/parser drift (RFC-1220 fail-closed path).
|
|
60
|
+
const DRIFT_ID_PATTERN = "\\b(?:[A-Z][A-Z0-9]*-)+\\d+\\b|\\b[A-Z]+-[A-Z]+\\b";
|
|
61
|
+
|
|
54
62
|
describe("compass v2 contract (RFC-1094)", () => {
|
|
55
63
|
let tempDir: string;
|
|
56
64
|
|
|
@@ -214,6 +222,36 @@ describe("compass v2 contract (RFC-1094)", () => {
|
|
|
214
222
|
).toBeDefined();
|
|
215
223
|
});
|
|
216
224
|
|
|
225
|
+
it("CS-07: mission-format history token validates without crash (RFC-1220)", async () => {
|
|
226
|
+
writeForgeYaml(tempDir, ` compass:\n idPattern: '${MISSION_ID_PATTERN}'`);
|
|
227
|
+
writeFixture(
|
|
228
|
+
MEDIUM_FILE,
|
|
229
|
+
wrap(MC(PURPOSE_WITH_TOKEN), KD([KD_ITEM]), CS([CS_ITEM], "acme-m000175")),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const result = await runCompassValidation(makeInput(), makeContext(tempDir));
|
|
233
|
+
const cs07 = dataOf(result).diagnostics.filter((d) => d.ruleId === "COMPASS-CS-07");
|
|
234
|
+
expect(
|
|
235
|
+
cs07,
|
|
236
|
+
`mission-format token must be a legal history ID: ${JSON.stringify(cs07)}`,
|
|
237
|
+
).toHaveLength(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("CS-07: token passing idPatternFull but unparseable emits diagnostic, no crash (RFC-1220)", async () => {
|
|
241
|
+
writeForgeYaml(tempDir, ` compass:\n idPattern: '${DRIFT_ID_PATTERN}'`);
|
|
242
|
+
writeFixture(
|
|
243
|
+
MEDIUM_FILE,
|
|
244
|
+
wrap(MC(PURPOSE_WITH_TOKEN), KD([KD_ITEM]), CS([CS_ITEM], "FOO-BAR")),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const result = await runCompassValidation(makeInput(), makeContext(tempDir));
|
|
248
|
+
const cs07 = dataOf(result).diagnostics.find(
|
|
249
|
+
(d) => d.ruleId === "COMPASS-CS-07" && d.message.includes("unparseable"),
|
|
250
|
+
);
|
|
251
|
+
expect(cs07, "unparseable token must produce a CS-07 diagnostic").toBeDefined();
|
|
252
|
+
expect(result.exitCode).toBeDefined();
|
|
253
|
+
});
|
|
254
|
+
|
|
217
255
|
it("empty CHANGE_SUMMARY (no items, no history) is legal", async () => {
|
|
218
256
|
writeFixture(MEDIUM_FILE, wrap(MC(PURPOSE_WITH_TOKEN), KD([KD_ITEM]), CS([])));
|
|
219
257
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Shared forge.yaml fixture helpers for compass tests — one schema-valid minimal workspace shape (bindings placeholder), a sync writer, and a temp-dir policy resolver with tracked cleanup so pattern-specific policy tests never fall back silently to generic defaults or leak mkdtemp roots.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not build full workspace fixtures — only forge.yaml, created on demand in caller-managed temp dirs.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-1220: extract shared forge.yaml fixture from triplicated test copies — schema-required paths section included; mkdtemp roots tracked for afterEach cleanup.</item>
|
|
10
|
+
<item>RFC-1220: dedupe auto-stamped CHANGE_SUMMARY item in forge-yaml fixture</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { resolveCompassPolicy, type CompassPolicy } from "../../policy.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Minimal schema-valid forge.yaml body. `paths:` is REQUIRED — a fixture
|
|
21
|
+
* without it fails `loadForgeConfig` schema validation and the resolver
|
|
22
|
+
* silently falls back to generic policy.
|
|
23
|
+
*/
|
|
24
|
+
export function forgeYaml(bindings: string): string {
|
|
25
|
+
return `
|
|
26
|
+
schema: forge/config@1
|
|
27
|
+
project:
|
|
28
|
+
name: test-ws
|
|
29
|
+
stack: [typescript]
|
|
30
|
+
packageManager: pnpm
|
|
31
|
+
paths:
|
|
32
|
+
rfcsDir: docs/rfcs
|
|
33
|
+
adrsDir: docs/adrs
|
|
34
|
+
plansDir: docs/plans
|
|
35
|
+
auditsDir: docs/audits
|
|
36
|
+
specsDir: docs/specs
|
|
37
|
+
skillsDir: .agents/skills
|
|
38
|
+
bindings:
|
|
39
|
+
schema: forge/bindings@1
|
|
40
|
+
commands: {}
|
|
41
|
+
paths: {}
|
|
42
|
+
${bindings}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Write a minimal forge.yaml into `<dir>/forge.yaml`. */
|
|
46
|
+
export function writeForgeYaml(dir: string, bindings: string): void {
|
|
47
|
+
writeFileSync(join(dir, "forge.yaml"), forgeYaml(bindings));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const patternRoots: string[] = [];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a Compass policy for a fresh temp workspace whose
|
|
54
|
+
* `bindings.compass.idPattern` is the given union. The temp root is tracked —
|
|
55
|
+
* call {@link cleanupForgeYamlFixtures} from an `afterEach`.
|
|
56
|
+
*/
|
|
57
|
+
export function policyWithIdPattern(idPattern: string): CompassPolicy {
|
|
58
|
+
const root = mkdtempSync(join(tmpdir(), "compass-pattern-"));
|
|
59
|
+
patternRoots.push(root);
|
|
60
|
+
writeForgeYaml(root, ` compass:\n idPattern: '${idPattern}'`);
|
|
61
|
+
return resolveCompassPolicy(root);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Remove every temp root created by {@link policyWithIdPattern}. */
|
|
65
|
+
export function cleanupForgeYamlFixtures(): void {
|
|
66
|
+
for (const root of patternRoots.splice(0)) {
|
|
67
|
+
rmSync(root, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|