@warpgogol/forge 5.3.4 → 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 +47 -15
- 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/summary-record.d.ts +11 -2
- package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
- package/dist/os/compass/handlers/summary-record.js +93 -5
- package/dist/os/compass/handlers/summary-record.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/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/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 +3 -0
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +2 -0
- package/dist/src/config/forge-config.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/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/os/adr/adr.module.ts +2 -0
- package/os/audit/audit.module.ts +2 -0
- package/os/compass/compass.module.ts +49 -15
- package/os/compass/handlers/compass-audit-handler.ts +50 -9
- package/os/compass/handlers/compass-docs-validate.ts +359 -0
- package/os/compass/handlers/summary-record.ts +104 -5
- 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/summary-record.test.ts +127 -0
- 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/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/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-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 +4 -6
- package/skills/fo/fo-idea-create-rfc/SKILL.md +2 -1
- 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 +53 -10
- 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 +3 -2
- 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 +4 -0
- package/src/onboarding/init.ts +8 -3
- package/src/pipeline-status.ts +32 -8
- package/src/tests/cli-flags.test.ts +20 -0
- package/src/tests/cli-output.test.ts +13 -7
- package/src/tests/fixtures/agents-generate-business-before.txt +7 -7
- package/src/tests/implement-stamp.test.ts +38 -0
- package/src/tests/session-handlers.test.ts +33 -0
- package/src/types.ts +13 -1
- package/src/utils/generated-marker.ts +28 -0
- package/skills/fo/fo-idea-implement/ADR-FLOW.md +0 -179
- package/skills/fo/fo-review/AXES.md +0 -70
|
@@ -28,11 +28,13 @@ Sweep batch 4: 73 Compass headers on headerless engine files (certification, com
|
|
|
28
28
|
|
|
29
29
|
compass-migrate-handler hint used a consumer-specific run command — switched to generic 'pnpm exec forge run' convention. Reworded recorded CHANGE_SUMMARY items in 3 handlers to drop the consumer-specific literal. compass-policy AC-4 test green (65/65).</item>
|
|
30
30
|
<item>RFC-1220: step 2 — fail-closed guards in history paths</item>
|
|
31
|
+
<item>RFC-1233: stripGitTrailers at the record point + sanitizeItemText star-slash guard — commit-message trailers (Token: value / Token #value, plus markdown-link attribution lines like "Generated with [X](url)") no longer pollute injected items.</item>
|
|
32
|
+
<item>Session-retro 2026-10-08: id-in-diff dedup — auto-inject skips when the worktree diff already carries a same-ID item (HEAD-vs-worktree via git show HEAD:path), closing the descriptive+generic duplicate class seen on RFC-1237.</item>
|
|
31
33
|
<history>RFC-1095</history>
|
|
32
34
|
</CHANGE_SUMMARY>
|
|
33
35
|
*/
|
|
34
36
|
|
|
35
|
-
import { existsSync } from "../../../src/utils/sync-fs.ts";
|
|
37
|
+
import { execFile, existsSync } from "../../../src/utils/sync-fs.ts";
|
|
36
38
|
import { ambientIo, resolveIo } from "../../../src/utils/io.ts";
|
|
37
39
|
|
|
38
40
|
import { resolve, relative } from "node:path";
|
|
@@ -69,7 +71,7 @@ export interface SummaryRecordInput {
|
|
|
69
71
|
workpiece?: string;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
export type SummaryRecordSkipReason = "no-block" | "duplicate" | "unparseable";
|
|
74
|
+
export type SummaryRecordSkipReason = "no-block" | "duplicate" | "unparseable" | "id-in-diff";
|
|
73
75
|
|
|
74
76
|
export interface SummaryRecordResult {
|
|
75
77
|
command: "compass.summary.record";
|
|
@@ -167,6 +169,41 @@ export function stripConventionalPrefix(subject: string): string {
|
|
|
167
169
|
return subject.replace(CONVENTIONAL_PREFIX_RE, "").trim();
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
// RFC-1233: git-trailer tail block — strict `Token: value` / `Token #value`
|
|
173
|
+
// lines (interpret-trailers shape, empty value allowed), plus lines ending
|
|
174
|
+
// in a markdown link (the observed "Generated with [X](url)" attribution
|
|
175
|
+
// boilerplate has no separator). A link-shaped line is admitted only when
|
|
176
|
+
// the block also contains at least one strict trailer line — a lone link
|
|
177
|
+
// tail is prose.
|
|
178
|
+
const TRAILER_LINE_RE = /^[A-Za-z0-9-]+(:| #)\s*.*$/;
|
|
179
|
+
const MARKDOWN_LINK_TAIL_RE = /^.*\[[^\]]*\]\([^)]+\)\s*$/;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Remove a trailing git-trailer block: the contiguous tail of strict
|
|
183
|
+
* trailer lines and admitted markdown-link lines. A blank line ends the
|
|
184
|
+
* tail paragraph. Returns the message without the block; when the tail
|
|
185
|
+
* holds no strict trailer line the message is returned unchanged.
|
|
186
|
+
*/
|
|
187
|
+
export function stripGitTrailers(message: string): string {
|
|
188
|
+
const lines = message.trimEnd().split("\n");
|
|
189
|
+
let start = lines.length;
|
|
190
|
+
let sawStrict = false;
|
|
191
|
+
while (start > 0) {
|
|
192
|
+
const line = lines[start - 1]!.trim();
|
|
193
|
+
if (line === "") break;
|
|
194
|
+
if (TRAILER_LINE_RE.test(line)) {
|
|
195
|
+
sawStrict = true;
|
|
196
|
+
start--;
|
|
197
|
+
} else if (MARKDOWN_LINK_TAIL_RE.test(line)) {
|
|
198
|
+
start--;
|
|
199
|
+
} else {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!sawStrict || start === lines.length) return message;
|
|
204
|
+
return lines.slice(0, start).join("\n").trimEnd();
|
|
205
|
+
}
|
|
206
|
+
|
|
170
207
|
// Literal Compass block tags inside an item corrupt block parsing — a commit
|
|
171
208
|
// message saying "collapsed into history" would be matched by HISTORY_RE and
|
|
172
209
|
// produce phantom non-ID tokens (CS-07). Strip the angle brackets on record.
|
|
@@ -174,7 +211,14 @@ const COMPASS_TAG_LITERAL_RE =
|
|
|
174
211
|
/<\/?(?:CHANGE_SUMMARY|MODULE_CONTRACT|KEY_DECISIONS|history|item|purpose|non-goals)>/g;
|
|
175
212
|
|
|
176
213
|
export function sanitizeItemText(text: string): string {
|
|
177
|
-
return
|
|
214
|
+
return (
|
|
215
|
+
text
|
|
216
|
+
.replace(COMPASS_TAG_LITERAL_RE, (tag) => tag.replace(/[<>]/g, ""))
|
|
217
|
+
// RFC-1233: a literal star-slash sequence would terminate the
|
|
218
|
+
// surrounding block comment when the item lands in /* ... */ headers.
|
|
219
|
+
// ∕ (U+2215) is visually near-identical, idempotent, and never re-forms.
|
|
220
|
+
.replace(/\*\//g, "*\u2215")
|
|
221
|
+
);
|
|
178
222
|
}
|
|
179
223
|
|
|
180
224
|
export function isValidGovernanceId(id: string, policy: CompassPolicy): boolean {
|
|
@@ -190,6 +234,8 @@ interface RecordOutcome {
|
|
|
190
234
|
/**
|
|
191
235
|
* Append `ID: text` to one file's CHANGE_SUMMARY and collapse the window.
|
|
192
236
|
* Returns the outcome; writes the file only when mutated and not dry-run.
|
|
237
|
+
* `headSource` is the file's content at HEAD (when resolvable) — it enables
|
|
238
|
+
* the id-in-diff dedup; pass undefined outside git workspaces.
|
|
193
239
|
*/
|
|
194
240
|
export async function recordSummaryItem(
|
|
195
241
|
absPath: string,
|
|
@@ -198,6 +244,7 @@ export async function recordSummaryItem(
|
|
|
198
244
|
text: string,
|
|
199
245
|
dryRun: boolean,
|
|
200
246
|
policy: CompassPolicy,
|
|
247
|
+
headSource?: string,
|
|
201
248
|
): Promise<RecordOutcome> {
|
|
202
249
|
const source = await io.readFile(absPath);
|
|
203
250
|
const blockMatch = source.match(CHANGE_SUMMARY_BLOCK_RE);
|
|
@@ -228,6 +275,22 @@ export async function recordSummaryItem(
|
|
|
228
275
|
return { recorded: false, collapsed: false, skipReason: "duplicate" };
|
|
229
276
|
}
|
|
230
277
|
|
|
278
|
+
// A same-ID item already in the worktree but absent from HEAD means the
|
|
279
|
+
// pending change already carries a hand-authored record for this governance
|
|
280
|
+
// ID — the auto-injected subject-derived item would duplicate it at lower
|
|
281
|
+
// specificity (observed 2026-10-08: 27 files carried descriptive + generic
|
|
282
|
+
// RFC-1237 items). HEAD-resident same-ID items do NOT suppress: consecutive
|
|
283
|
+
// commits under one RFC legitimately stack step items.
|
|
284
|
+
if (headSource !== undefined) {
|
|
285
|
+
const headBlock = headSource.match(CHANGE_SUMMARY_BLOCK_RE);
|
|
286
|
+
const headItems = headBlock ? parseChangeSummary(headBlock[0]).items : [];
|
|
287
|
+
const idInWorktree = items.some((item) => leadingGovernanceId(item, policy) === id);
|
|
288
|
+
const idInHead = headItems.some((item) => leadingGovernanceId(item, policy) === id);
|
|
289
|
+
if (idInWorktree && !idInHead) {
|
|
290
|
+
return { recorded: false, collapsed: false, skipReason: "id-in-diff" };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
231
294
|
const nextItems = [...items, newItem];
|
|
232
295
|
const collapsedIds: string[] = [];
|
|
233
296
|
while (nextItems.length > CHANGE_SUMMARY_WINDOW) {
|
|
@@ -246,6 +309,27 @@ export async function recordSummaryItem(
|
|
|
246
309
|
return { recorded: true, collapsed };
|
|
247
310
|
}
|
|
248
311
|
|
|
312
|
+
function execGit(cwd: string, args: string[]): Promise<string | null> {
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
execFile("git", args, { cwd, timeout: 5000 }, (err, stdout) => {
|
|
315
|
+
resolve(err ? null : stdout);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Read `path`'s content at HEAD — the baseline for the id-in-diff dedup.
|
|
322
|
+
* Returns undefined when the file is untracked or the workspace is not a git
|
|
323
|
+
* repo (the dedup then falls back to exact-text matching only).
|
|
324
|
+
*/
|
|
325
|
+
async function readHeadSource(gitTop: string | null, absPath: string): Promise<string | undefined> {
|
|
326
|
+
if (!gitTop) return undefined;
|
|
327
|
+
const rel = relative(gitTop, absPath).replace(/\\/g, "/");
|
|
328
|
+
if (rel === "" || rel.startsWith("..")) return undefined;
|
|
329
|
+
const source = await execGit(gitTop, ["show", `HEAD:${rel}`]);
|
|
330
|
+
return source ?? undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
249
333
|
function riskReminderNeeded(relPath: string, source: string, policy: CompassPolicy): boolean {
|
|
250
334
|
const segments = relPath.split("/").filter(Boolean);
|
|
251
335
|
const workspaceRel = getWorkspaceRelativeSegments(segments, policy).join("/");
|
|
@@ -278,7 +362,10 @@ export async function runCompassSummaryRecord(
|
|
|
278
362
|
: [];
|
|
279
363
|
|
|
280
364
|
const rawText = input.flags["text"];
|
|
281
|
-
|
|
365
|
+
// RFC-1233: strip the git-trailer block at the single injection point —
|
|
366
|
+
// ecosystem.commit and mission.git.commit both pipe the raw message here.
|
|
367
|
+
const stripped = typeof rawText === "string" ? stripGitTrailers(rawText).trim() : "";
|
|
368
|
+
const text = stripped.length > 0 ? stripped : id;
|
|
282
369
|
|
|
283
370
|
const result: SummaryRecordResult = {
|
|
284
371
|
command: "compass.summary.record",
|
|
@@ -289,6 +376,9 @@ export async function runCompassSummaryRecord(
|
|
|
289
376
|
keyDecisionsReminders: [],
|
|
290
377
|
};
|
|
291
378
|
|
|
379
|
+
const gitTopRaw = await execGit(baseRoot, ["rev-parse", "--show-toplevel"]);
|
|
380
|
+
const gitTop = gitTopRaw?.trim() || null;
|
|
381
|
+
|
|
292
382
|
for (const file of files) {
|
|
293
383
|
const absPath = resolve(baseRoot, file);
|
|
294
384
|
const relPath = relative(baseRoot, absPath).replace(/\\/g, "/");
|
|
@@ -298,7 +388,16 @@ export async function runCompassSummaryRecord(
|
|
|
298
388
|
continue;
|
|
299
389
|
}
|
|
300
390
|
try {
|
|
301
|
-
const
|
|
391
|
+
const headSource = await readHeadSource(gitTop, absPath);
|
|
392
|
+
const outcome = await recordSummaryItem(
|
|
393
|
+
absPath,
|
|
394
|
+
relPath,
|
|
395
|
+
id,
|
|
396
|
+
text,
|
|
397
|
+
context.dryRun,
|
|
398
|
+
policy,
|
|
399
|
+
headSource,
|
|
400
|
+
);
|
|
302
401
|
if (outcome.skipReason) {
|
|
303
402
|
result.skipped.push({ file, reason: outcome.skipReason });
|
|
304
403
|
continue;
|
|
@@ -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 { runCompassAuditRecord } 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);
|
|
@@ -42,7 +43,7 @@ function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
|
42
43
|
logger: logger as never,
|
|
43
44
|
dryRun: false,
|
|
44
45
|
outputFormat: "json",
|
|
45
|
-
io:
|
|
46
|
+
io: ambientIo,
|
|
46
47
|
actualState: undefined as never,
|
|
47
48
|
fileIntents: [],
|
|
48
49
|
} as unknown as ForgeRuntimeContext;
|
|
@@ -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;
|