@warpgogol/forge 2.7.0 → 2.8.1
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/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/src/config/forge-config.d.ts +1 -1
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +2 -2
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/onboarding/agents-generate.d.ts +2 -1
- package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/agents-generate.js +24 -4
- package/dist/src/onboarding/agents-generate.js.map +1 -1
- package/dist/src/profiles/profile-schema.d.ts +2 -0
- package/dist/src/profiles/profile-schema.d.ts.map +1 -1
- package/dist/src/profiles/profile-schema.js +1 -0
- package/dist/src/profiles/profile-schema.js.map +1 -1
- package/dist/src/profiles/stack-profile.d.ts +1 -0
- package/dist/src/profiles/stack-profile.d.ts.map +1 -1
- package/dist/src/profiles/stack-profile.js +2 -0
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +5 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/profiles/root-agents-godot.md +133 -0
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +51 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +217 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Register the forge Compass command family against the Site OS kernel registry.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement compass handler logic — delegate to handlers/ inlined by RFC-0556.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0374: initial forgeCompassModule registering 12 compass commands.</item>
|
|
10
|
+
<item>RFC-0538: renamed compass.changesummary.tidy to compass.summary.trim, removed compass.annotate, compass.clear, compass.markup.migrate, compass.invariant.add.</item>
|
|
11
|
+
<item>RFC-0556: removed dynamic import of @warpgogol/site-kernel-checks, all handlers now inlined in forge/os/compass/handlers/.</item>
|
|
12
|
+
</CHANGE_SUMMARY>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ForgeModule } from "../../src/forge-module.ts";
|
|
16
|
+
import type { ForgeFlagSpec } from "../../src/types.ts";
|
|
17
|
+
import { runCompassInventory, runCompassValidation } from "./handlers/compass-inventory-handler.ts";
|
|
18
|
+
import {
|
|
19
|
+
runCompassAuditPlan,
|
|
20
|
+
runCompassAuditRecord,
|
|
21
|
+
runCompassAuditBaseline,
|
|
22
|
+
runCompassAuditValidate,
|
|
23
|
+
} from "./handlers/compass-audit-handler.ts";
|
|
24
|
+
import {
|
|
25
|
+
runCompassChangeSummaryValidate,
|
|
26
|
+
runCompassSummaryTrim,
|
|
27
|
+
} from "./handlers/compass-change-summary-handler.ts";
|
|
28
|
+
|
|
29
|
+
const compassScanFlags = {
|
|
30
|
+
packages: {
|
|
31
|
+
kind: "boolean",
|
|
32
|
+
description: "Scan packages/ instead of the inferred app or default workspace scope.",
|
|
33
|
+
},
|
|
34
|
+
package: {
|
|
35
|
+
kind: "string",
|
|
36
|
+
description: "With --packages, scan one package by directory/name.",
|
|
37
|
+
},
|
|
38
|
+
workpiece: {
|
|
39
|
+
kind: "string",
|
|
40
|
+
description:
|
|
41
|
+
"Scan a mission workpiece directory (RFC-0617). Mutually exclusive with --packages and --site.",
|
|
42
|
+
},
|
|
43
|
+
} satisfies Record<string, ForgeFlagSpec>;
|
|
44
|
+
|
|
45
|
+
export const forgeCompassModule: ForgeModule = {
|
|
46
|
+
name: "forge-compass",
|
|
47
|
+
version: "0.1.0",
|
|
48
|
+
async register(registry) {
|
|
49
|
+
registry.registerCommand({
|
|
50
|
+
name: "compass.inventory",
|
|
51
|
+
description: "Generate the repository-wide Compass source inventory XML report.",
|
|
52
|
+
scope: "workspace",
|
|
53
|
+
supportsAllSites: true,
|
|
54
|
+
flags: { ...compassScanFlags },
|
|
55
|
+
reads: ["packages/**/*.{ts,tsx,md}", "apps/**/*.{ts,tsx,md}", "services/**/*.{ts,tsx,md}"],
|
|
56
|
+
execute: runCompassInventory,
|
|
57
|
+
});
|
|
58
|
+
registry.registerCommand({
|
|
59
|
+
name: "compass.validate",
|
|
60
|
+
description:
|
|
61
|
+
"Validate authored source files against current Compass scaffolding requirements.",
|
|
62
|
+
scope: "workspace",
|
|
63
|
+
supportsAllSites: true,
|
|
64
|
+
flags: { ...compassScanFlags },
|
|
65
|
+
reads: [
|
|
66
|
+
"packages/**/*.{ts,tsx,md}",
|
|
67
|
+
"apps/**/*.{ts,tsx,md}",
|
|
68
|
+
"services/**/*.{ts,tsx,md}",
|
|
69
|
+
"docs/source-markup.xml",
|
|
70
|
+
],
|
|
71
|
+
execute: runCompassValidation,
|
|
72
|
+
});
|
|
73
|
+
registry.registerCommand({
|
|
74
|
+
name: "compass.changesummary.validate",
|
|
75
|
+
description:
|
|
76
|
+
"Validate CHANGE_SUMMARY blocks for boilerplate items and over-cap unprotected items (RFC-0349).",
|
|
77
|
+
scope: "workspace",
|
|
78
|
+
supportsAllSites: true,
|
|
79
|
+
flags: { ...compassScanFlags },
|
|
80
|
+
reads: ["packages/**/*.{ts,tsx,md}", "apps/**/*.{ts,tsx,md}", "services/**/*.{ts,tsx,md}"],
|
|
81
|
+
execute: runCompassChangeSummaryValidate,
|
|
82
|
+
});
|
|
83
|
+
registry.registerCommand({
|
|
84
|
+
name: "compass.summary.trim",
|
|
85
|
+
description:
|
|
86
|
+
"Deterministically trim CHANGE_SUMMARY blocks: remove boilerplate, cap total items to 30, preserve protected items (RFC-0538).",
|
|
87
|
+
scope: "workspace",
|
|
88
|
+
mutatesState: true,
|
|
89
|
+
supportsAllSites: true,
|
|
90
|
+
writes: [
|
|
91
|
+
"apps/**/*.{astro,ts,tsx,md}",
|
|
92
|
+
"packages/**/*.{astro,ts,tsx,md}",
|
|
93
|
+
"services/**/*.{ts,tsx,md}",
|
|
94
|
+
],
|
|
95
|
+
reads: ["packages/**/*.{ts,tsx,md}", "apps/**/*.{ts,tsx,md}", "services/**/*.{ts,tsx,md}"],
|
|
96
|
+
cacheable: false,
|
|
97
|
+
flags: { ...compassScanFlags },
|
|
98
|
+
execute: runCompassSummaryTrim,
|
|
99
|
+
});
|
|
100
|
+
registry.registerCommand({
|
|
101
|
+
name: "compass.audit.plan",
|
|
102
|
+
description:
|
|
103
|
+
"Emit a deterministic work-order of files whose revision has advanced past the threshold since their last Compass audit (RFC-0352). Read-only, no LLM.",
|
|
104
|
+
scope: "workspace",
|
|
105
|
+
supportsAllSites: true,
|
|
106
|
+
flags: {
|
|
107
|
+
...compassScanFlags,
|
|
108
|
+
threshold: {
|
|
109
|
+
kind: "string",
|
|
110
|
+
description: "Revision distance that makes a file audit-overdue.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
reads: [
|
|
114
|
+
"packages/**/*.{ts,tsx,md}",
|
|
115
|
+
"apps/**/*.{ts,tsx,md}",
|
|
116
|
+
"services/**/*.{ts,tsx,md}",
|
|
117
|
+
"docs/compass-audit-ledger.generated.yaml",
|
|
118
|
+
],
|
|
119
|
+
execute: runCompassAuditPlan,
|
|
120
|
+
});
|
|
121
|
+
registry.registerCommand({
|
|
122
|
+
name: "compass.audit.record",
|
|
123
|
+
description:
|
|
124
|
+
"Stamp a file's audit verdict and current revision into the compass-audit ledger (RFC-0352). Mutating.",
|
|
125
|
+
scope: "workspace",
|
|
126
|
+
mutatesState: true,
|
|
127
|
+
supportsAllSites: true,
|
|
128
|
+
writes: ["docs/compass-audit-ledger.generated.yaml"],
|
|
129
|
+
reads: ["docs/compass-audit-ledger.generated.yaml"],
|
|
130
|
+
cacheable: false,
|
|
131
|
+
flags: {
|
|
132
|
+
file: { kind: "string", required: true, description: "Authored file path to stamp." },
|
|
133
|
+
verdict: {
|
|
134
|
+
kind: "string",
|
|
135
|
+
required: true,
|
|
136
|
+
description: "Audit verdict: pass, repaired, or baseline.",
|
|
137
|
+
},
|
|
138
|
+
agent: { kind: "string", description: "Agent identity to record in the ledger." },
|
|
139
|
+
},
|
|
140
|
+
execute: runCompassAuditRecord,
|
|
141
|
+
});
|
|
142
|
+
registry.registerCommand({
|
|
143
|
+
name: "compass.audit.baseline",
|
|
144
|
+
description:
|
|
145
|
+
"Seed the compass-audit ledger for every authored file at its current revision with verdict=baseline (RFC-0352). One-time bootstrap.",
|
|
146
|
+
scope: "workspace",
|
|
147
|
+
mutatesState: true,
|
|
148
|
+
supportsAllSites: true,
|
|
149
|
+
writes: ["docs/compass-audit-ledger.generated.yaml"],
|
|
150
|
+
reads: ["packages/**/*.{ts,tsx,md}", "apps/**/*.{ts,tsx,md}", "services/**/*.{ts,tsx,md}"],
|
|
151
|
+
cacheable: false,
|
|
152
|
+
flags: { ...compassScanFlags },
|
|
153
|
+
execute: runCompassAuditBaseline,
|
|
154
|
+
});
|
|
155
|
+
registry.registerCommand({
|
|
156
|
+
name: "compass.audit.validate",
|
|
157
|
+
description:
|
|
158
|
+
"Validate that no authored file is audit-overdue per the revision threshold (RFC-0352). Warns by default, fails with --strict.",
|
|
159
|
+
scope: "workspace",
|
|
160
|
+
supportsAllSites: true,
|
|
161
|
+
flags: {
|
|
162
|
+
...compassScanFlags,
|
|
163
|
+
strict: {
|
|
164
|
+
kind: "boolean",
|
|
165
|
+
description: "Fail when audit-overdue authored files are found.",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
reads: [
|
|
169
|
+
"packages/**/*.{ts,tsx,md}",
|
|
170
|
+
"apps/**/*.{ts,tsx,md}",
|
|
171
|
+
"services/**/*.{ts,tsx,md}",
|
|
172
|
+
"docs/compass-audit-ledger.generated.yaml",
|
|
173
|
+
],
|
|
174
|
+
execute: runCompassAuditValidate,
|
|
175
|
+
gate: {
|
|
176
|
+
severity: "mixed",
|
|
177
|
+
phase: "author",
|
|
178
|
+
conditional: {
|
|
179
|
+
kind: "flag",
|
|
180
|
+
ref: "--strict",
|
|
181
|
+
description: "Warns by default, fails with --strict",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
};
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Compass audit command handlers (plan, record, baseline, validate).
|
|
4
|
+
Moved from @warpgogol/site-kernel-checks to @warpgogol/forge for full autonomous
|
|
5
|
+
mode (RFC-0556). Drives per-file semantic-truth auditing on a revision cadence (RFC-0352).</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not perform semantic comparison inside a command — commands are deterministic; the code-vs-prose judgment is the agent's.</item>
|
|
8
|
+
<item>Do not call an LLM or read any API key.</item>
|
|
9
|
+
<item>Do not replace compass.validate — this adds a heavy truth audit on top.</item>
|
|
10
|
+
</non-goals>
|
|
11
|
+
</MODULE_CONTRACT>
|
|
12
|
+
<CHANGE_SUMMARY>
|
|
13
|
+
<item>RFC-0352: initial implementation of compass.audit.plan, compass.audit.record, compass.audit.baseline, compass.audit.validate.</item>
|
|
14
|
+
<item>RFC-0556: moved from @warpgogol/site-kernel-checks to @warpgogol/forge for autonomous mode.</item>
|
|
15
|
+
</CHANGE_SUMMARY>
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { resolve, relative } from "node:path";
|
|
20
|
+
import { execFile } from "node:child_process";
|
|
21
|
+
import { promisify } from "node:util";
|
|
22
|
+
import { createCompassInventoryEntries } from "./compass-inventory.ts";
|
|
23
|
+
import { resolveCompassScanRoot } from "./resolve-scan-root.ts";
|
|
24
|
+
import { getRevisionByPath } from "./git-revision.ts";
|
|
25
|
+
import type { CompassInventoryEntry } from "./compass-inventory.ts";
|
|
26
|
+
import { writeFileAtomic } from "../../../src/utils/fs-atomic.ts";
|
|
27
|
+
import { buildGeneratedHeader } from "../../../src/utils/generated-marker.ts";
|
|
28
|
+
import type {
|
|
29
|
+
ForgeCommandInput,
|
|
30
|
+
ForgeCommandResult,
|
|
31
|
+
ForgeRuntimeContext,
|
|
32
|
+
} from "../../../src/types.ts";
|
|
33
|
+
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
34
|
+
|
|
35
|
+
const execFileAsync = promisify(execFile);
|
|
36
|
+
const LEDGER_PATH = "docs/compass-audit-ledger.generated.yaml";
|
|
37
|
+
const DEFAULT_THRESHOLD = 30;
|
|
38
|
+
|
|
39
|
+
function resolveRevisionThreshold(value: unknown, fallback: number): number {
|
|
40
|
+
if (value === undefined) return fallback;
|
|
41
|
+
const parsed = typeof value === "number" ? value : Number(String(value));
|
|
42
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type CompassAuditVerdict = "pass" | "repaired" | "baseline";
|
|
46
|
+
|
|
47
|
+
interface CompassAuditLedgerEntry {
|
|
48
|
+
path: string;
|
|
49
|
+
entityId: string;
|
|
50
|
+
auditedRevision: number;
|
|
51
|
+
auditedHash: string;
|
|
52
|
+
auditedAt: string;
|
|
53
|
+
verdict: CompassAuditVerdict;
|
|
54
|
+
agent: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CompassAuditLedger {
|
|
58
|
+
revisionThreshold: number;
|
|
59
|
+
entries: CompassAuditLedgerEntry[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface CompassAuditWorkOrderItem {
|
|
63
|
+
path: string;
|
|
64
|
+
currentRevision: number;
|
|
65
|
+
auditedRevision: number | null;
|
|
66
|
+
reason: "never-audited" | "revision-threshold-crossed";
|
|
67
|
+
moduleContract: string;
|
|
68
|
+
changeSummary: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isAuditDue(current: number, audited: number | null, threshold: number): boolean {
|
|
72
|
+
if (audited === null) return true;
|
|
73
|
+
return current - audited >= threshold;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const _LEDGER_ADVISORY = { ownerCommand: "compass.audit.record" };
|
|
77
|
+
|
|
78
|
+
function withLedgerAdvisory(ledger: Partial<CompassAuditLedger>): CompassAuditLedger {
|
|
79
|
+
return {
|
|
80
|
+
revisionThreshold: ledger.revisionThreshold ?? DEFAULT_THRESHOLD,
|
|
81
|
+
entries: Array.isArray(ledger.entries) ? ledger.entries : [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function loadLedger(workspaceRoot: string): Promise<CompassAuditLedger> {
|
|
86
|
+
const abs = resolve(workspaceRoot, LEDGER_PATH);
|
|
87
|
+
try {
|
|
88
|
+
const content = await readFile(abs, "utf8");
|
|
89
|
+
return withLedgerAdvisory(yamlParse(content) as Partial<CompassAuditLedger>);
|
|
90
|
+
} catch {
|
|
91
|
+
return withLedgerAdvisory({});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function saveLedger(workspaceRoot: string, ledger: CompassAuditLedger): Promise<void> {
|
|
96
|
+
const normalized = withLedgerAdvisory(ledger);
|
|
97
|
+
normalized.entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
98
|
+
const abs = resolve(workspaceRoot, LEDGER_PATH);
|
|
99
|
+
const header = buildGeneratedHeader({
|
|
100
|
+
ownerCommand: "compass.audit.record",
|
|
101
|
+
filePath: LEDGER_PATH,
|
|
102
|
+
});
|
|
103
|
+
const yaml = header + yamlStringify(normalized) + "\n";
|
|
104
|
+
await writeFileAtomic(abs, yaml);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractBlock(source: string, tagName: string): string {
|
|
108
|
+
const match = source.match(new RegExp(`<${tagName}>[\\s\\S]*?<\\/${tagName}>`));
|
|
109
|
+
return match?.[0] ?? "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function getGitUser(workspaceRoot: string): Promise<string> {
|
|
113
|
+
try {
|
|
114
|
+
const { stdout } = await execFileAsync("git", ["config", "user.name"], {
|
|
115
|
+
cwd: workspaceRoot,
|
|
116
|
+
});
|
|
117
|
+
return `human:${stdout.trim()}`;
|
|
118
|
+
} catch {
|
|
119
|
+
return "human:unknown";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getAuthoredEntries(entries: CompassInventoryEntry[]): CompassInventoryEntry[] {
|
|
124
|
+
return entries.filter(
|
|
125
|
+
(e) => e.authoringStatus === "authored" && e.requiredScaffolding !== "none",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function runCompassAuditPlan(
|
|
130
|
+
input: ForgeCommandInput,
|
|
131
|
+
context: ForgeRuntimeContext,
|
|
132
|
+
): Promise<
|
|
133
|
+
ForgeCommandResult<{
|
|
134
|
+
threshold: number;
|
|
135
|
+
dueCount: number;
|
|
136
|
+
items: CompassAuditWorkOrderItem[];
|
|
137
|
+
}>
|
|
138
|
+
> {
|
|
139
|
+
const scanRoot = resolveCompassScanRoot(input, context);
|
|
140
|
+
const entries = await createCompassInventoryEntries(context.workspaceRoot, input, scanRoot);
|
|
141
|
+
const authored = getAuthoredEntries(entries);
|
|
142
|
+
const ledger = await loadLedger(context.workspaceRoot);
|
|
143
|
+
|
|
144
|
+
const threshold = resolveRevisionThreshold(input.flags["threshold"], ledger.revisionThreshold);
|
|
145
|
+
|
|
146
|
+
const ledgerMap = new Map<string, CompassAuditLedgerEntry>();
|
|
147
|
+
for (const e of ledger.entries) {
|
|
148
|
+
ledgerMap.set(e.path, e);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const items: CompassAuditWorkOrderItem[] = [];
|
|
152
|
+
|
|
153
|
+
for (const entry of authored) {
|
|
154
|
+
const ledgerEntry = ledgerMap.get(entry.path);
|
|
155
|
+
const auditedRevision = ledgerEntry?.auditedRevision ?? null;
|
|
156
|
+
|
|
157
|
+
const { revision: currentRevision } = await getRevisionByPath(
|
|
158
|
+
context.workspaceRoot,
|
|
159
|
+
entry.path,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!isAuditDue(currentRevision, auditedRevision, threshold)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const absPath = resolve(context.workspaceRoot, entry.path);
|
|
167
|
+
const source = await readFile(absPath, "utf8");
|
|
168
|
+
const moduleContract = extractBlock(source, "MODULE_CONTRACT");
|
|
169
|
+
const changeSummary = extractBlock(source, "CHANGE_SUMMARY");
|
|
170
|
+
|
|
171
|
+
items.push({
|
|
172
|
+
path: entry.path,
|
|
173
|
+
currentRevision,
|
|
174
|
+
auditedRevision,
|
|
175
|
+
reason: auditedRevision === null ? "never-audited" : "revision-threshold-crossed",
|
|
176
|
+
moduleContract,
|
|
177
|
+
changeSummary,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
items.sort((a, b) => a.path.localeCompare(b.path));
|
|
182
|
+
|
|
183
|
+
context.logger.info(`[compass.audit.plan] threshold=${threshold}, due=${items.length}`);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
data: { threshold, dueCount: items.length, items },
|
|
187
|
+
exitCode: 0,
|
|
188
|
+
summary: `[compass.audit.plan] ${items.length} files due for audit (threshold=${threshold})`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function runCompassAuditRecord(
|
|
193
|
+
input: ForgeCommandInput,
|
|
194
|
+
context: ForgeRuntimeContext,
|
|
195
|
+
): Promise<
|
|
196
|
+
ForgeCommandResult<{
|
|
197
|
+
path: string;
|
|
198
|
+
action: "recorded";
|
|
199
|
+
}>
|
|
200
|
+
> {
|
|
201
|
+
const rawFilePath = input.flags["file"] as string | undefined;
|
|
202
|
+
const verdict = input.flags["verdict"] as CompassAuditVerdict | undefined;
|
|
203
|
+
const agentFlag = input.flags["agent"] as string | undefined;
|
|
204
|
+
|
|
205
|
+
if (!rawFilePath) {
|
|
206
|
+
context.logger.error("[compass.audit.record] --file is required");
|
|
207
|
+
return { exitCode: 1, data: { path: "", action: "recorded" } };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!verdict || !["pass", "repaired", "baseline"].includes(verdict)) {
|
|
211
|
+
context.logger.error("[compass.audit.record] --verdict must be pass|repaired|baseline");
|
|
212
|
+
return { exitCode: 1, data: { path: rawFilePath, action: "recorded" } };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const filePath = relative(context.workspaceRoot, resolve(process.cwd(), rawFilePath));
|
|
216
|
+
|
|
217
|
+
const { revision, entityId, contentHash } = await getRevisionByPath(
|
|
218
|
+
context.workspaceRoot,
|
|
219
|
+
filePath,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const agent = agentFlag ?? (await getGitUser(context.workspaceRoot));
|
|
223
|
+
const ledger = await loadLedger(context.workspaceRoot);
|
|
224
|
+
|
|
225
|
+
const existingIdx = ledger.entries.findIndex((e) => e.path === filePath);
|
|
226
|
+
const entry: CompassAuditLedgerEntry = {
|
|
227
|
+
path: filePath,
|
|
228
|
+
entityId: entityId ?? "",
|
|
229
|
+
auditedRevision: revision,
|
|
230
|
+
auditedHash: contentHash,
|
|
231
|
+
auditedAt: new Date().toISOString(),
|
|
232
|
+
verdict,
|
|
233
|
+
agent,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (existingIdx >= 0) {
|
|
237
|
+
ledger.entries[existingIdx] = entry;
|
|
238
|
+
} else {
|
|
239
|
+
ledger.entries.push(entry);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!context.dryRun) {
|
|
243
|
+
await saveLedger(context.workspaceRoot, ledger);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
context.logger.info(
|
|
247
|
+
`[compass.audit.record] ${filePath}: verdict=${verdict}, revision=${revision}, agent=${agent}`,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
data: { path: filePath, action: "recorded" },
|
|
252
|
+
exitCode: 0,
|
|
253
|
+
summary: `[compass.audit.record] ${filePath}: ${verdict} at revision ${revision}`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function runCompassAuditBaseline(
|
|
258
|
+
input: ForgeCommandInput,
|
|
259
|
+
context: ForgeRuntimeContext,
|
|
260
|
+
): Promise<
|
|
261
|
+
ForgeCommandResult<{
|
|
262
|
+
seeded: number;
|
|
263
|
+
total: number;
|
|
264
|
+
}>
|
|
265
|
+
> {
|
|
266
|
+
const scanRoot = resolveCompassScanRoot(input, context);
|
|
267
|
+
const entries = await createCompassInventoryEntries(context.workspaceRoot, input, scanRoot);
|
|
268
|
+
const authored = getAuthoredEntries(entries);
|
|
269
|
+
const ledger = await loadLedger(context.workspaceRoot);
|
|
270
|
+
|
|
271
|
+
const existingPaths = new Set(ledger.entries.map((e) => e.path));
|
|
272
|
+
let seededCount = 0;
|
|
273
|
+
|
|
274
|
+
for (const entry of authored) {
|
|
275
|
+
if (existingPaths.has(entry.path)) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const { revision, entityId, contentHash } = await getRevisionByPath(
|
|
280
|
+
context.workspaceRoot,
|
|
281
|
+
entry.path,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
ledger.entries.push({
|
|
285
|
+
path: entry.path,
|
|
286
|
+
entityId: entityId ?? "",
|
|
287
|
+
auditedRevision: revision,
|
|
288
|
+
auditedHash: contentHash,
|
|
289
|
+
auditedAt: new Date().toISOString(),
|
|
290
|
+
verdict: "baseline",
|
|
291
|
+
agent: "system:baseline",
|
|
292
|
+
});
|
|
293
|
+
seededCount++;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
ledger.entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
297
|
+
|
|
298
|
+
if (!context.dryRun) {
|
|
299
|
+
await saveLedger(context.workspaceRoot, ledger);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
context.logger.info(
|
|
303
|
+
`[compass.audit.baseline] seeded=${seededCount}, total=${ledger.entries.length}`,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
data: { seeded: seededCount, total: ledger.entries.length },
|
|
308
|
+
exitCode: 0,
|
|
309
|
+
summary: `[compass.audit.baseline] seeded ${seededCount} entries (total: ${ledger.entries.length})`,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function runCompassAuditValidate(
|
|
314
|
+
input: ForgeCommandInput,
|
|
315
|
+
context: ForgeRuntimeContext,
|
|
316
|
+
): Promise<
|
|
317
|
+
ForgeCommandResult<{
|
|
318
|
+
strict: boolean;
|
|
319
|
+
dueCount: number;
|
|
320
|
+
diagnostics: Array<{
|
|
321
|
+
ruleId: string;
|
|
322
|
+
severity: string;
|
|
323
|
+
file: string;
|
|
324
|
+
message: string;
|
|
325
|
+
fix: string;
|
|
326
|
+
}>;
|
|
327
|
+
}>
|
|
328
|
+
> {
|
|
329
|
+
const strict = input.flags["strict"] === true;
|
|
330
|
+
const scanRoot = resolveCompassScanRoot(input, context);
|
|
331
|
+
const entries = await createCompassInventoryEntries(context.workspaceRoot, input, scanRoot);
|
|
332
|
+
const authored = getAuthoredEntries(entries);
|
|
333
|
+
const ledger = await loadLedger(context.workspaceRoot);
|
|
334
|
+
|
|
335
|
+
const ledgerMap = new Map<string, CompassAuditLedgerEntry>();
|
|
336
|
+
for (const e of ledger.entries) {
|
|
337
|
+
ledgerMap.set(e.path, e);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const diagnostics: Array<{
|
|
341
|
+
ruleId: string;
|
|
342
|
+
severity: string;
|
|
343
|
+
file: string;
|
|
344
|
+
message: string;
|
|
345
|
+
fix: string;
|
|
346
|
+
}> = [];
|
|
347
|
+
|
|
348
|
+
for (const entry of authored) {
|
|
349
|
+
const ledgerEntry = ledgerMap.get(entry.path);
|
|
350
|
+
const auditedRevision = ledgerEntry?.auditedRevision ?? null;
|
|
351
|
+
|
|
352
|
+
const { revision: currentRevision } = await getRevisionByPath(
|
|
353
|
+
context.workspaceRoot,
|
|
354
|
+
entry.path,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
if (!isAuditDue(currentRevision, auditedRevision, ledger.revisionThreshold)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const severity = strict ? "error" : "warning";
|
|
362
|
+
context.logger[strict ? "error" : "warn"](
|
|
363
|
+
`[compass.audit.validate] COMPASS-AUDIT-01: ${entry.path}: audit overdue (current=${currentRevision}, audited=${auditedRevision ?? "never"}, threshold=${ledger.revisionThreshold})`,
|
|
364
|
+
);
|
|
365
|
+
diagnostics.push({
|
|
366
|
+
ruleId: "COMPASS-AUDIT-01",
|
|
367
|
+
severity,
|
|
368
|
+
file: entry.path,
|
|
369
|
+
message: `Compass audit overdue: ${currentRevision - (auditedRevision ?? 0)} revisions since last audit (threshold=${ledger.revisionThreshold})`,
|
|
370
|
+
fix: "fix: run compass.audit.plan, reconcile the blocks with the code, then compass.audit.record",
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const dueCount = diagnostics.length;
|
|
375
|
+
const hasErrors = strict && dueCount > 0;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
data: { strict, dueCount, diagnostics },
|
|
379
|
+
exitCode: hasErrors ? 1 : 0,
|
|
380
|
+
summary: dueCount > 0 ? undefined : `[compass.audit.validate] OK (0 files overdue)`,
|
|
381
|
+
};
|
|
382
|
+
}
|