@warpgogol/forge 2.8.0 → 2.8.2

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.
Files changed (271) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +23 -13
  3. package/README.uk.md +19 -12
  4. package/dist/os/session/handlers/validate.d.ts.map +1 -1
  5. package/dist/os/session/handlers/validate.js +23 -1
  6. package/dist/os/session/handlers/validate.js.map +1 -1
  7. package/dist/os/session/types.d.ts +31 -1
  8. package/dist/os/session/types.d.ts.map +1 -1
  9. package/dist/os/session/types.js +7 -0
  10. package/dist/os/session/types.js.map +1 -1
  11. package/os/adr/adr.module.ts +158 -0
  12. package/os/adr/frontmatter-io.ts +80 -0
  13. package/os/adr/handlers/archive.ts +244 -0
  14. package/os/adr/handlers/implement-stamp.ts +331 -0
  15. package/os/adr/handlers/list-create.ts +207 -0
  16. package/os/adr/handlers/validate.test.ts +232 -0
  17. package/os/adr/handlers/validate.ts +429 -0
  18. package/os/adr/index.ts +33 -0
  19. package/os/adr/types.ts +141 -0
  20. package/os/audit/audit.module.ts +48 -0
  21. package/os/audit/frontmatter-io.ts +84 -0
  22. package/os/audit/handlers/archive.ts +239 -0
  23. package/os/audit/index.ts +23 -0
  24. package/os/audit/types.ts +29 -0
  25. package/os/compass/compass.module.ts +186 -0
  26. package/os/compass/handlers/compass-audit-handler.ts +382 -0
  27. package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
  28. package/os/compass/handlers/compass-inventory-handler.ts +294 -0
  29. package/os/compass/handlers/compass-inventory.ts +521 -0
  30. package/os/compass/handlers/git-revision.ts +137 -0
  31. package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
  32. package/os/compass/handlers/resolve-scan-root.ts +79 -0
  33. package/os/compass/index.ts +33 -0
  34. package/os/core/core.module.ts +948 -0
  35. package/os/core/handlers/assets-check.ts +122 -0
  36. package/os/core/handlers/assets-helpers.ts +174 -0
  37. package/os/core/handlers/assets-list.ts +99 -0
  38. package/os/core/handlers/build.ts +155 -0
  39. package/os/core/handlers/determinism-check.ts +354 -0
  40. package/os/core/handlers/dev.ts +127 -0
  41. package/os/core/handlers/invariant-engine.test.ts +681 -0
  42. package/os/core/handlers/knowledge-compact.ts +248 -0
  43. package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
  44. package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
  45. package/os/core/handlers/note-link-validate.test.ts +117 -0
  46. package/os/core/handlers/note-orphan-detect.test.ts +94 -0
  47. package/os/core/handlers/package-health.test.ts +226 -0
  48. package/os/core/handlers/package-health.ts +231 -0
  49. package/os/core/handlers/pinned-check.ts +200 -0
  50. package/os/core/handlers/pinned-init.ts +333 -0
  51. package/os/core/handlers/pinned-types.ts +50 -0
  52. package/os/core/handlers/pinned-validate.ts +301 -0
  53. package/os/core/handlers/profile-resolve.ts +92 -0
  54. package/os/core/handlers/release-prepare.ts +294 -0
  55. package/os/core/handlers/release-publish.ts +211 -0
  56. package/os/core/handlers/validate.ts +262 -0
  57. package/os/core/handlers/workspace-deps.ts +55 -0
  58. package/os/core/index.ts +13 -0
  59. package/os/exploration/exploration.module.ts +90 -0
  60. package/os/exploration/frontmatter-io.ts +77 -0
  61. package/os/exploration/handlers/archive.ts +159 -0
  62. package/os/exploration/handlers/list.ts +77 -0
  63. package/os/exploration/handlers/show.ts +107 -0
  64. package/os/exploration/index.ts +25 -0
  65. package/os/exploration/types.ts +65 -0
  66. package/os/mission/handlers/archive.test.ts +391 -0
  67. package/os/mission/handlers/archive.ts +453 -0
  68. package/os/mission/index.ts +1 -0
  69. package/os/mission/mission.module.ts +48 -0
  70. package/os/mission/types.ts +41 -0
  71. package/os/naming/index.ts +14 -0
  72. package/os/naming/naming-convention.test.ts +163 -0
  73. package/os/naming/naming-convention.ts +378 -0
  74. package/os/naming/naming.module.ts +36 -0
  75. package/os/notes/index.ts +10 -0
  76. package/os/notes/notes.module.ts +109 -0
  77. package/os/plan/frontmatter-io.ts +82 -0
  78. package/os/plan/handlers/archive.ts +238 -0
  79. package/os/plan/index.ts +23 -0
  80. package/os/plan/plan.module.ts +48 -0
  81. package/os/plan/types.ts +29 -0
  82. package/os/program/discovery.ts +294 -0
  83. package/os/program/handlers/complete.ts +419 -0
  84. package/os/program/handlers/lease.ts +433 -0
  85. package/os/program/handlers/seal.ts +332 -0
  86. package/os/program/handlers/validate.ts +213 -0
  87. package/os/program/lease.ts +156 -0
  88. package/os/program/program.module.ts +212 -0
  89. package/os/program/schemas.ts +218 -0
  90. package/os/program/state.ts +264 -0
  91. package/os/rfc/acceptance.ts +344 -0
  92. package/os/rfc/decision-log.ts +310 -0
  93. package/os/rfc/dna-trace.ts +280 -0
  94. package/os/rfc/frontmatter-io.test.ts +132 -0
  95. package/os/rfc/frontmatter-io.ts +130 -0
  96. package/os/rfc/handlers/archive.ts +300 -0
  97. package/os/rfc/handlers/check.ts +238 -0
  98. package/os/rfc/handlers/implement-stamp.ts +515 -0
  99. package/os/rfc/handlers/index-graph.ts +237 -0
  100. package/os/rfc/handlers/lifecycle.test.ts +127 -0
  101. package/os/rfc/handlers/lifecycle.ts +249 -0
  102. package/os/rfc/handlers/list-create.ts +296 -0
  103. package/os/rfc/handlers/pipeline-status.ts +173 -0
  104. package/os/rfc/handlers/shared.ts +121 -0
  105. package/os/rfc/handlers/supersede-propose.ts +241 -0
  106. package/os/rfc/handlers/validate-rules.test.ts +891 -0
  107. package/os/rfc/handlers/validate-rules.ts +984 -0
  108. package/os/rfc/handlers/validate.ts +166 -0
  109. package/os/rfc/handlers.ts +19 -0
  110. package/os/rfc/index.ts +97 -0
  111. package/os/rfc/rfc.module.ts +416 -0
  112. package/os/rfc/types.ts +596 -0
  113. package/os/rfc/verification-evidence.ts +244 -0
  114. package/os/session/atif-parser.ts +166 -0
  115. package/os/session/frontmatter-io.ts +163 -0
  116. package/os/session/handlers/archive.ts +288 -0
  117. package/os/session/handlers/list.ts +174 -0
  118. package/os/session/handlers/save.ts +348 -0
  119. package/os/session/handlers/validate.ts +250 -0
  120. package/os/session/index.ts +50 -0
  121. package/os/session/session.module.ts +126 -0
  122. package/os/session/types.ts +203 -0
  123. package/os/spec/live-spec-list-show-validate.test.ts +257 -0
  124. package/os/spec/live-spec-list.ts +78 -0
  125. package/os/spec/live-spec-merge.test.ts +261 -0
  126. package/os/spec/live-spec-merge.ts +413 -0
  127. package/os/spec/live-spec-show.ts +117 -0
  128. package/os/spec/live-spec-types.ts +93 -0
  129. package/os/spec/live-spec-validate.ts +170 -0
  130. package/os/spec/spec-materialize.ts +390 -0
  131. package/os/spec/spec-schema.ts +174 -0
  132. package/os/spec/spec-status.ts +284 -0
  133. package/os/spec/spec-validate.ts +513 -0
  134. package/os/spec/spec.module.ts +135 -0
  135. package/os/werkstatt/handlers/lock.ts +168 -0
  136. package/os/werkstatt/handlers/schema.ts +47 -0
  137. package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
  138. package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
  139. package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
  140. package/os/werkstatt/index.ts +39 -0
  141. package/os/werkstatt/werkstatt.module.ts +62 -0
  142. package/os/workflow/handlers.ts +304 -0
  143. package/os/workflow/index.ts +28 -0
  144. package/os/workflow/types.ts +99 -0
  145. package/os/workflow/workflow.module.ts +54 -0
  146. package/package.json +3 -3
  147. package/profiles/godot-csharp.yaml +4 -4
  148. package/profiles/knowledge-typescript-turborepo.yaml +110 -0
  149. package/profiles/phaser-turborepo.yaml +5 -5
  150. package/skills/_shared/fo-session-summary.md +132 -16
  151. package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
  152. package/skills/fo/fo-doc-audit/SKILL.md +6 -6
  153. package/skills/fo/fo-handoff/SKILL.md +5 -0
  154. package/skills/fo/fo-idea/SKILL.md +1 -1
  155. package/skills/fo/fo-review/SKILL.md +1 -1
  156. package/skills/fo/fo-session-retro/SKILL.md +8 -0
  157. package/skills/fo/fo-session-save/SKILL.md +8 -0
  158. package/src/cli-output.ts +59 -0
  159. package/src/config/__tests__/resolve-terminology.test.ts +45 -0
  160. package/src/config/forge-config.ts +524 -0
  161. package/src/forge-module.ts +39 -0
  162. package/src/index.ts +172 -0
  163. package/src/knowledge/__tests__/promote.test.ts +219 -0
  164. package/src/knowledge/__tests__/serialize.test.ts +192 -0
  165. package/src/knowledge/budgets.ts +202 -0
  166. package/src/knowledge/compact.ts +405 -0
  167. package/src/knowledge/index.ts +53 -0
  168. package/src/knowledge/parse.ts +227 -0
  169. package/src/knowledge/promote.ts +156 -0
  170. package/src/knowledge/schema.ts +112 -0
  171. package/src/knowledge/serialize.ts +72 -0
  172. package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
  173. package/src/migration-adapters/__tests__/registry.test.ts +131 -0
  174. package/src/migration-adapters/__tests__/types.test.ts +86 -0
  175. package/src/migration-adapters/git-utils.ts +71 -0
  176. package/src/migration-adapters/ignored-files.ts +221 -0
  177. package/src/migration-adapters/index.ts +21 -0
  178. package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
  179. package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
  180. package/src/migration-adapters/registry.ts +61 -0
  181. package/src/migration-adapters/types.ts +68 -0
  182. package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
  183. package/src/onboarding/agents-generate.ts +443 -0
  184. package/src/onboarding/create.ts +427 -0
  185. package/src/onboarding/doctor.ts +1297 -0
  186. package/src/onboarding/init.ts +337 -0
  187. package/src/onboarding/invariant-engine.ts +448 -0
  188. package/src/onboarding/memory-scaffold.ts +189 -0
  189. package/src/onboarding/nested-agents-generate.ts +93 -0
  190. package/src/onboarding/nested-agents-templates.ts +233 -0
  191. package/src/onboarding/profile-validate.ts +139 -0
  192. package/src/onboarding/scaffold-project.ts +299 -0
  193. package/src/onboarding/scaffold.ts +143 -0
  194. package/src/onboarding/upgrade.ts +558 -0
  195. package/src/onboarding/workspace-discovery.ts +167 -0
  196. package/src/profiles/__tests__/profile-schema.test.ts +228 -0
  197. package/src/profiles/__tests__/stack-profile.test.ts +190 -0
  198. package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
  199. package/src/profiles/profile-schema.ts +350 -0
  200. package/src/profiles/stack-profile.ts +188 -0
  201. package/src/profiles/terminology-utils.ts +36 -0
  202. package/src/registry.ts +185 -0
  203. package/src/skill-schema.ts +37 -0
  204. package/src/tests/acceptance-criteria.test.ts +99 -0
  205. package/src/tests/adr-implement-stamp.test.ts +368 -0
  206. package/src/tests/agents-generate-domain.test.ts +317 -0
  207. package/src/tests/agents-generate.test.ts +458 -0
  208. package/src/tests/bindings-schema.test.ts +314 -0
  209. package/src/tests/budgets.test.ts +213 -0
  210. package/src/tests/cli-output.test.ts +196 -0
  211. package/src/tests/compact.test.ts +796 -0
  212. package/src/tests/create.test.ts +289 -0
  213. package/src/tests/doctor-autonomy.test.ts +98 -0
  214. package/src/tests/doctor-bindings.test.ts +286 -0
  215. package/src/tests/doctor-domain.test.ts +113 -0
  216. package/src/tests/exploration-handlers.test.ts +256 -0
  217. package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
  218. package/src/tests/forge-config.test.ts +420 -0
  219. package/src/tests/fs-atomic.test.ts +96 -0
  220. package/src/tests/fs.test.ts +103 -0
  221. package/src/tests/generated-marker.test.ts +161 -0
  222. package/src/tests/hash.test.ts +37 -0
  223. package/src/tests/implement-stamp.test.ts +505 -0
  224. package/src/tests/init-bindings.test.ts +183 -0
  225. package/src/tests/knowledge-parse.test.ts +307 -0
  226. package/src/tests/knowledge-pbt.test.ts +164 -0
  227. package/src/tests/memory-scaffold.test.ts +192 -0
  228. package/src/tests/migration-adapters.test.ts +480 -0
  229. package/src/tests/package-files.test.ts +54 -0
  230. package/src/tests/pinned-check.test.ts +194 -0
  231. package/src/tests/pinned-init.test.ts +126 -0
  232. package/src/tests/profile-schema.test.ts +194 -0
  233. package/src/tests/profile-validate.test.ts +120 -0
  234. package/src/tests/program-lease.test.ts +245 -0
  235. package/src/tests/program-paths-extended.test.ts +117 -0
  236. package/src/tests/program-paths.test.ts +98 -0
  237. package/src/tests/program-property.test.ts +139 -0
  238. package/src/tests/program-schemas.test.ts +445 -0
  239. package/src/tests/program-spec-node.test.ts +250 -0
  240. package/src/tests/promote.test.ts +481 -0
  241. package/src/tests/registry.test.ts +73 -0
  242. package/src/tests/scaffold-project.test.ts +150 -0
  243. package/src/tests/session-handlers.test.ts +503 -0
  244. package/src/tests/session-pbt.test.ts +190 -0
  245. package/src/tests/skill-schema.test.ts +146 -0
  246. package/src/tests/skill-validate-knowledge.test.ts +26 -0
  247. package/src/tests/skill-validate.test.ts +254 -0
  248. package/src/tests/stack-profile.test.ts +221 -0
  249. package/src/tests/string-utils.test.ts +42 -0
  250. package/src/tests/upgrade.test.ts +428 -0
  251. package/src/tests/werkstatt-lock.test.ts +343 -0
  252. package/src/tests/workspace-discovery-domain.test.ts +112 -0
  253. package/src/tests/workspace-discovery.test.ts +135 -0
  254. package/src/types.ts +221 -0
  255. package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
  256. package/src/utils/__tests__/fs-trash.test.ts +45 -0
  257. package/src/utils/fs-atomic.ts +93 -0
  258. package/src/utils/fs-idempotent.ts +41 -0
  259. package/src/utils/fs-trash-sync.ts +37 -0
  260. package/src/utils/fs-trash.ts +24 -0
  261. package/src/utils/fs.ts +74 -0
  262. package/src/utils/generated-marker.ts +166 -0
  263. package/src/utils/hash.ts +19 -0
  264. package/src/utils/index.ts +29 -0
  265. package/src/utils/string-utils.ts +19 -0
  266. package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
  267. package/src/validators/note-frontmatter-validate.ts +142 -0
  268. package/src/validators/note-link-validate.ts +145 -0
  269. package/src/validators/note-orphan-detect.ts +146 -0
  270. package/src/validators/port-validate.ts +97 -0
  271. package/src/validators/skill-validate.ts +831 -0
@@ -0,0 +1,84 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Audit frontmatter file I/O — recursive file discovery and YAML frontmatter
5
+ parsing for audit files under docs/audits/. Only files matching the
6
+ audit-rfc-XXXX-* pattern are candidates for archiving; standalone audits
7
+ are silently excluded.
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Do not validate audit content.</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-0521: initial audit frontmatter I/O.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ import fs from "node:fs/promises";
19
+ import path from "node:path";
20
+ import YAML from "yaml";
21
+ import { AUDIT_RFC_FILE_PATTERN } from "./types.ts";
22
+
23
+ export interface ParsedAudit {
24
+ frontmatter: Record<string, unknown>;
25
+ body: string;
26
+ }
27
+
28
+ export function parseAuditFile(source: string): ParsedAudit {
29
+ const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
30
+ if (!match) {
31
+ return { frontmatter: {}, body: source };
32
+ }
33
+ return {
34
+ frontmatter: (YAML.parse(match[1]!) ?? {}) as Record<string, unknown>,
35
+ body: match[2] ?? "",
36
+ };
37
+ }
38
+
39
+ export async function listAuditFiles(auditDirPath: string): Promise<string[]> {
40
+ const results: string[] = [];
41
+
42
+ async function scanDir(dirPath: string, relativePrefix: string): Promise<void> {
43
+ try {
44
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
45
+ for (const entry of entries) {
46
+ const relativePath = relativePrefix ? `${relativePrefix}/${entry.name}` : entry.name;
47
+ if (entry.isDirectory()) {
48
+ await scanDir(path.join(dirPath, entry.name), relativePath);
49
+ } else if (
50
+ entry.isFile() &&
51
+ entry.name.endsWith(".md") &&
52
+ !entry.name.startsWith("audit-0000") &&
53
+ entry.name !== "README.md" &&
54
+ AUDIT_RFC_FILE_PATTERN.test(entry.name)
55
+ ) {
56
+ results.push(relativePath);
57
+ }
58
+ }
59
+ } catch {
60
+ // Directory doesn't exist or is unreadable — return empty
61
+ }
62
+ }
63
+
64
+ await scanDir(auditDirPath, "");
65
+ return results.sort();
66
+ }
67
+
68
+ export async function readAndParseAudit(
69
+ auditDirPath: string,
70
+ fileName: string,
71
+ ): Promise<{ fileName: string; parsed: ParsedAudit } | undefined> {
72
+ try {
73
+ const filePath = path.join(auditDirPath, fileName);
74
+ const content = await fs.readFile(filePath, "utf-8");
75
+ return { fileName, parsed: parseAuditFile(content) };
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ export function extractRfcIdFromAuditFile(fileName: string): string | undefined {
82
+ const match = path.basename(fileName).match(/^audit-(rfc-\d{4})-/i);
83
+ return match ? match[1]!.toUpperCase() : undefined;
84
+ }
@@ -0,0 +1,239 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Audit archive handler — moves audit files whose parent RFC has terminal status
5
+ into status-specific subdirectories under docs/audits/archive/ and moves
6
+ non-terminal files found in subdirectories back to the root. Standalone audit
7
+ files (not matching audit-rfc-XXXX-*) are silently excluded.
8
+ </purpose>
9
+ <non-goals>
10
+ <item>Does not validate audit content.</item>
11
+ <item>Does not change audit frontmatter — only moves files on disk.</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>RFC-0521: implement audit.archive command.</item>
16
+ <item>RFC-0733: add pinned-files pre-check — skip pinned files with warning instead of moving them.</item>
17
+ </CHANGE_SUMMARY>
18
+ */
19
+
20
+ import fs from "node:fs/promises";
21
+ import path from "node:path";
22
+ import { listAuditFiles, extractRfcIdFromAuditFile } from "../frontmatter-io.ts";
23
+ import { loadRfcStatusMap } from "../../rfc/frontmatter-io.ts";
24
+ import type { ArchiveMove, ArchiveSkip } from "../../rfc/handlers/archive.ts";
25
+ import type {
26
+ ForgeCommandInput,
27
+ ForgeCommandResult,
28
+ ForgeRuntimeContext,
29
+ } from "../../../src/types.ts";
30
+ import { AUDIT_DIR } from "../types.ts";
31
+ import type { AuditArchiveResult } from "../types.ts";
32
+ import { loadPinnedManifest, isPinned, isIntraDirMove } from "../../core/handlers/pinned-check.ts";
33
+
34
+ const TERMINAL_STATUSES = ["implemented", "rejected", "superseded"] as const;
35
+
36
+ export async function runAuditArchive(
37
+ input: ForgeCommandInput,
38
+ context: ForgeRuntimeContext,
39
+ ): Promise<ForgeCommandResult<AuditArchiveResult>> {
40
+ const { workspaceRoot, logger, outputFormat } = context;
41
+ const auditDirPath = path.join(workspaceRoot, AUDIT_DIR);
42
+ const rfcDirPath = path.join(workspaceRoot, "docs", "rfcs");
43
+
44
+ const dryRun = context.dryRun || input.flags["dry-run"] === true;
45
+ const statusFilter = input.flags["status"] as string | undefined;
46
+
47
+ if (statusFilter && !TERMINAL_STATUSES.includes(statusFilter as never)) {
48
+ throw new Error(
49
+ `Invalid --status "${statusFilter}". Must be one of: ${TERMINAL_STATUSES.join(", ")}`,
50
+ );
51
+ }
52
+
53
+ const files = await listAuditFiles(auditDirPath);
54
+ const rfcStatusMap = await loadRfcStatusMap(rfcDirPath);
55
+ const moved: ArchiveMove[] = [];
56
+ const skipped: ArchiveSkip[] = [];
57
+
58
+ // RFC-0733: Load pinned manifest once per invocation
59
+ let pinnedManifest = null;
60
+ try {
61
+ pinnedManifest = await loadPinnedManifest(workspaceRoot);
62
+ } catch {
63
+ // Malformed manifest — skip pre-check
64
+ }
65
+
66
+ for (const fileName of files) {
67
+ const rfcId = extractRfcIdFromAuditFile(fileName);
68
+ const relFile = path.join(AUDIT_DIR, fileName);
69
+ const basename = path.basename(fileName);
70
+
71
+ if (!rfcId) {
72
+ skipped.push({ id: "UNKNOWN", file: relFile, reason: "no RFC id in filename" });
73
+ continue;
74
+ }
75
+
76
+ const rfcStatus = rfcStatusMap.get(rfcId);
77
+ if (!rfcStatus) {
78
+ skipped.push({ id: rfcId, file: relFile, reason: "parent RFC not found" });
79
+ continue;
80
+ }
81
+
82
+ const isTerminal =
83
+ rfcStatus === "implemented" || rfcStatus === "rejected" || rfcStatus === "superseded";
84
+ const isInArchive = fileName.includes("/");
85
+
86
+ if (statusFilter && rfcStatus !== statusFilter) {
87
+ skipped.push({
88
+ id: rfcId,
89
+ file: relFile,
90
+ reason: `parent RFC status ${rfcStatus} does not match --status ${statusFilter}`,
91
+ });
92
+ continue;
93
+ }
94
+
95
+ if (isTerminal && !isInArchive) {
96
+ const targetDir = path.join(auditDirPath, "archive", rfcStatus);
97
+ const targetPath = path.join(targetDir, basename);
98
+ const targetRel = path.join(AUDIT_DIR, "archive", rfcStatus, basename);
99
+
100
+ // RFC-0733: Check if file is pinned before moving
101
+ // Gap fix: exempt intra-directory moves (file stays within the same pinned dir)
102
+ if (
103
+ pinnedManifest &&
104
+ isPinned(pinnedManifest, relFile) &&
105
+ !isIntraDirMove(pinnedManifest, relFile, targetRel)
106
+ ) {
107
+ skipped.push({
108
+ id: rfcId,
109
+ file: relFile,
110
+ reason: "pinned (protected by .forge/pinned.yaml)",
111
+ });
112
+ if (outputFormat === "pretty") {
113
+ logger.warn(` pinned: skipping ${relFile} (protected)`);
114
+ }
115
+ continue;
116
+ }
117
+
118
+ try {
119
+ await fs.access(targetPath);
120
+ skipped.push({ id: rfcId, file: relFile, reason: "destination exists" });
121
+ continue;
122
+ } catch {
123
+ // destination doesn't exist — proceed
124
+ }
125
+
126
+ if (!dryRun) {
127
+ await fs.mkdir(targetDir, { recursive: true });
128
+ try {
129
+ await fs.rename(path.join(auditDirPath, fileName), targetPath);
130
+ } catch (err) {
131
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
132
+ skipped.push({ id: rfcId, file: relFile, reason: "already moved by another process" });
133
+ continue;
134
+ }
135
+ throw err;
136
+ }
137
+ }
138
+
139
+ moved.push({
140
+ id: rfcId,
141
+ file: targetRel,
142
+ status: rfcStatus,
143
+ from: relFile,
144
+ to: targetRel,
145
+ direction: "into-archive",
146
+ });
147
+ } else if (!isTerminal && isInArchive) {
148
+ const targetPath = path.join(auditDirPath, basename);
149
+ const targetRel = path.join(AUDIT_DIR, basename);
150
+
151
+ // RFC-0733: Check if file is pinned before moving
152
+ // Gap fix: exempt intra-directory moves (file stays within the same pinned dir)
153
+ if (
154
+ pinnedManifest &&
155
+ isPinned(pinnedManifest, relFile) &&
156
+ !isIntraDirMove(pinnedManifest, relFile, targetRel)
157
+ ) {
158
+ skipped.push({
159
+ id: rfcId,
160
+ file: relFile,
161
+ reason: "pinned (protected by .forge/pinned.yaml)",
162
+ });
163
+ if (outputFormat === "pretty") {
164
+ logger.warn(` pinned: skipping ${relFile} (protected)`);
165
+ }
166
+ continue;
167
+ }
168
+
169
+ try {
170
+ await fs.access(targetPath);
171
+ skipped.push({ id: rfcId, file: relFile, reason: "destination exists" });
172
+ continue;
173
+ } catch {
174
+ // destination doesn't exist — proceed
175
+ }
176
+
177
+ if (!dryRun) {
178
+ try {
179
+ await fs.rename(path.join(auditDirPath, fileName), targetPath);
180
+ } catch (err) {
181
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
182
+ skipped.push({ id: rfcId, file: relFile, reason: "already moved by another process" });
183
+ continue;
184
+ }
185
+ throw err;
186
+ }
187
+ }
188
+
189
+ moved.push({
190
+ id: rfcId,
191
+ file: targetRel,
192
+ status: rfcStatus,
193
+ from: relFile,
194
+ to: targetRel,
195
+ direction: "out-of-archive",
196
+ });
197
+ } else if (isTerminal && isInArchive) {
198
+ skipped.push({ id: rfcId, file: relFile, reason: `already archived (${rfcStatus})` });
199
+ } else {
200
+ skipped.push({
201
+ id: rfcId,
202
+ file: relFile,
203
+ reason: `parent RFC status ${rfcStatus} is non-terminal`,
204
+ });
205
+ }
206
+ }
207
+
208
+ if (outputFormat === "pretty") {
209
+ if (dryRun) {
210
+ logger.info(
211
+ `[dry-run] audit.archive: would move ${moved.length} file(s), skip ${skipped.length}`,
212
+ );
213
+ } else {
214
+ logger.success(`audit.archive: moved ${moved.length} file(s), skipped ${skipped.length}`);
215
+ }
216
+ for (const m of moved) {
217
+ logger.info(` ${m.direction}: ${m.id} (${m.status}) ${m.from} → ${m.to}`);
218
+ }
219
+ }
220
+
221
+ return {
222
+ data: {
223
+ command: "audit.archive",
224
+ status: "ok",
225
+ moved,
226
+ skipped,
227
+ dryRun,
228
+ },
229
+ summary: dryRun
230
+ ? `[dry-run] Would move ${moved.length} file(s), skip ${skipped.length}`
231
+ : `Moved ${moved.length} file(s), skipped ${skipped.length}`,
232
+ nextSteps: [
233
+ {
234
+ action: "Run: pnpm exec werkstatt run audit.list --json to verify archive status",
235
+ kind: "optional",
236
+ },
237
+ ],
238
+ };
239
+ }
@@ -0,0 +1,23 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Barrel export for the audit domain — types, module, and handlers.</purpose>
4
+ <non-goals>
5
+ <item>Do not implement audit logic here; delegate to handlers/ and audit.module.ts.</item>
6
+ </non-goals>
7
+ </MODULE_CONTRACT>
8
+ <CHANGE_SUMMARY>
9
+ <item>RFC-0521: initial audit module barrel.</item>
10
+ </CHANGE_SUMMARY>
11
+ */
12
+
13
+ export { forgeAuditModule } from "./audit.module.ts";
14
+ export { runAuditArchive } from "./handlers/archive.ts";
15
+ export {
16
+ listAuditFiles,
17
+ parseAuditFile,
18
+ readAndParseAudit,
19
+ extractRfcIdFromAuditFile,
20
+ type ParsedAudit,
21
+ } from "./frontmatter-io.ts";
22
+ export type { AuditArchiveResult, AuditArchiveMove, AuditArchiveSkip } from "./types.ts";
23
+ export { AUDIT_DIR, AUDIT_RFC_FILE_PATTERN } from "./types.ts";
@@ -0,0 +1,29 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>
4
+ Type definitions for the audit archive domain — audit file discovery, parsing,
5
+ and archive result shapes.
6
+ </purpose>
7
+ <non-goals>
8
+ <item>Do not define archive handler logic here — that lives in handlers/archive.ts.</item>
9
+ </non-goals>
10
+ </MODULE_CONTRACT>
11
+ <CHANGE_SUMMARY>
12
+ <item>RFC-0521: initial audit archive types.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import type { ArchiveMove, ArchiveSkip } from "../rfc/handlers/archive.ts";
17
+
18
+ export const AUDIT_DIR = "docs/audits";
19
+ export const AUDIT_RFC_FILE_PATTERN = /^audit-rfc-\d{4}-/;
20
+
21
+ export interface AuditArchiveResult {
22
+ command: "audit.archive";
23
+ status: "ok";
24
+ moved: ArchiveMove[];
25
+ skipped: ArchiveSkip[];
26
+ dryRun: boolean;
27
+ }
28
+
29
+ export type { ArchiveMove as AuditArchiveMove, ArchiveSkip as AuditArchiveSkip };
@@ -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
+ };