@warpgogol/forge 2.9.11 → 2.9.23

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 (159) hide show
  1. package/dist/bin/cli.js +1 -1
  2. package/dist/os/adr/adr.module.d.ts.map +1 -1
  3. package/dist/os/adr/adr.module.js +1 -0
  4. package/dist/os/adr/adr.module.js.map +1 -1
  5. package/dist/os/audit/audit.module.d.ts.map +1 -1
  6. package/dist/os/audit/audit.module.js +1 -0
  7. package/dist/os/audit/audit.module.js.map +1 -1
  8. package/dist/os/compass/compass.module.d.ts.map +1 -1
  9. package/dist/os/compass/compass.module.js +1 -0
  10. package/dist/os/compass/compass.module.js.map +1 -1
  11. package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
  12. package/dist/os/compass/handlers/compass-inventory-handler.js +82 -1
  13. package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
  14. package/dist/os/core/core.module.d.ts.map +1 -1
  15. package/dist/os/core/core.module.js +16 -0
  16. package/dist/os/core/core.module.js.map +1 -1
  17. package/dist/os/core/handlers/assets-helpers.js +1 -1
  18. package/dist/os/core/handlers/assets-helpers.js.map +1 -1
  19. package/dist/os/core/handlers/determinism-check.js +1 -1
  20. package/dist/os/core/handlers/determinism-check.js.map +1 -1
  21. package/dist/os/core/handlers/forge-autonomy-validate.d.ts +18 -0
  22. package/dist/os/core/handlers/forge-autonomy-validate.d.ts.map +1 -0
  23. package/dist/os/core/handlers/forge-autonomy-validate.js +78 -0
  24. package/dist/os/core/handlers/forge-autonomy-validate.js.map +1 -0
  25. package/dist/os/core/handlers/knowledge-compact.d.ts.map +1 -1
  26. package/dist/os/core/handlers/knowledge-compact.js +7 -1
  27. package/dist/os/core/handlers/knowledge-compact.js.map +1 -1
  28. package/dist/os/core/handlers/release-prepare.js +1 -1
  29. package/dist/os/core/handlers/release-prepare.js.map +1 -1
  30. package/dist/os/exploration/exploration.module.d.ts.map +1 -1
  31. package/dist/os/exploration/exploration.module.js +1 -0
  32. package/dist/os/exploration/exploration.module.js.map +1 -1
  33. package/dist/os/mission/mission.module.d.ts.map +1 -1
  34. package/dist/os/mission/mission.module.js +1 -0
  35. package/dist/os/mission/mission.module.js.map +1 -1
  36. package/dist/os/naming/naming.module.d.ts.map +1 -1
  37. package/dist/os/naming/naming.module.js +1 -0
  38. package/dist/os/naming/naming.module.js.map +1 -1
  39. package/dist/os/notes/notes.module.d.ts.map +1 -1
  40. package/dist/os/notes/notes.module.js +1 -0
  41. package/dist/os/notes/notes.module.js.map +1 -1
  42. package/dist/os/plan/plan.module.d.ts.map +1 -1
  43. package/dist/os/plan/plan.module.js +1 -0
  44. package/dist/os/plan/plan.module.js.map +1 -1
  45. package/dist/os/plugin/plugin.module.d.ts +25 -0
  46. package/dist/os/plugin/plugin.module.d.ts.map +1 -0
  47. package/dist/os/plugin/plugin.module.js +208 -0
  48. package/dist/os/plugin/plugin.module.js.map +1 -0
  49. package/dist/os/program/program.module.d.ts.map +1 -1
  50. package/dist/os/program/program.module.js +1 -0
  51. package/dist/os/program/program.module.js.map +1 -1
  52. package/dist/os/rfc/rfc.module.d.ts.map +1 -1
  53. package/dist/os/rfc/rfc.module.js +1 -0
  54. package/dist/os/rfc/rfc.module.js.map +1 -1
  55. package/dist/os/session/session.module.d.ts.map +1 -1
  56. package/dist/os/session/session.module.js +1 -0
  57. package/dist/os/session/session.module.js.map +1 -1
  58. package/dist/os/spec/spec.module.d.ts.map +1 -1
  59. package/dist/os/spec/spec.module.js +1 -0
  60. package/dist/os/spec/spec.module.js.map +1 -1
  61. package/dist/os/werkstatt/handlers/workspace-deps.d.ts.map +1 -0
  62. package/dist/os/{core → werkstatt}/handlers/workspace-deps.js +4 -3
  63. package/dist/os/werkstatt/handlers/workspace-deps.js.map +1 -0
  64. package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
  65. package/dist/os/werkstatt/werkstatt.module.js +1 -0
  66. package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
  67. package/dist/os/workflow/workflow.module.d.ts.map +1 -1
  68. package/dist/os/workflow/workflow.module.js +1 -0
  69. package/dist/os/workflow/workflow.module.js.map +1 -1
  70. package/dist/src/compass/contract-registry.d.ts +7 -0
  71. package/dist/src/compass/contract-registry.d.ts.map +1 -0
  72. package/dist/src/compass/contract-registry.js +93 -0
  73. package/dist/src/compass/contract-registry.js.map +1 -0
  74. package/dist/src/compass/types.d.ts +28 -0
  75. package/dist/src/compass/types.d.ts.map +1 -0
  76. package/dist/src/compass/types.js +14 -0
  77. package/dist/src/compass/types.js.map +1 -0
  78. package/dist/src/forge-module.d.ts +1 -0
  79. package/dist/src/forge-module.d.ts.map +1 -1
  80. package/dist/src/forge-module.js +2 -1
  81. package/dist/src/forge-module.js.map +1 -1
  82. package/dist/src/index.d.ts +3 -0
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +3 -0
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/onboarding/doctor.d.ts.map +1 -1
  87. package/dist/src/onboarding/doctor.js +89 -7
  88. package/dist/src/onboarding/doctor.js.map +1 -1
  89. package/dist/src/onboarding/init.d.ts.map +1 -1
  90. package/dist/src/onboarding/init.js +22 -1
  91. package/dist/src/onboarding/init.js.map +1 -1
  92. package/dist/src/onboarding/scaffold.d.ts.map +1 -1
  93. package/dist/src/onboarding/scaffold.js +1 -0
  94. package/dist/src/onboarding/scaffold.js.map +1 -1
  95. package/dist/src/onboarding/upgrade.d.ts.map +1 -1
  96. package/dist/src/onboarding/upgrade.js +7 -1
  97. package/dist/src/onboarding/upgrade.js.map +1 -1
  98. package/dist/src/plugin/ForgePluginManifest.d.ts +38 -0
  99. package/dist/src/plugin/ForgePluginManifest.d.ts.map +1 -0
  100. package/dist/src/plugin/ForgePluginManifest.js +52 -0
  101. package/dist/src/plugin/ForgePluginManifest.js.map +1 -0
  102. package/dist/src/registry.d.ts.map +1 -1
  103. package/dist/src/registry.js +28 -0
  104. package/dist/src/registry.js.map +1 -1
  105. package/dist/src/validators/port-validate.js +3 -3
  106. package/dist/src/validators/port-validate.js.map +1 -1
  107. package/os/adr/adr.module.ts +1 -0
  108. package/os/audit/audit.module.ts +1 -0
  109. package/os/compass/compass.module.ts +1 -0
  110. package/os/compass/handlers/compass-inventory-handler.ts +107 -1
  111. package/os/core/core.module.ts +18 -0
  112. package/os/core/handlers/assets-helpers.ts +1 -1
  113. package/os/core/handlers/determinism-check.ts +1 -1
  114. package/os/core/handlers/forge-autonomy-validate.ts +115 -0
  115. package/os/core/handlers/knowledge-compact.ts +6 -1
  116. package/os/core/handlers/release-prepare.ts +1 -1
  117. package/os/exploration/exploration.module.ts +1 -0
  118. package/os/mission/mission.module.ts +1 -0
  119. package/os/naming/naming.module.ts +1 -0
  120. package/os/notes/notes.module.ts +1 -0
  121. package/os/plan/plan.module.ts +1 -0
  122. package/os/plugin/plugin.module.ts +261 -0
  123. package/os/program/program.module.ts +1 -0
  124. package/os/rfc/rfc.module.ts +1 -0
  125. package/os/session/session.module.ts +1 -0
  126. package/os/spec/spec.module.ts +1 -0
  127. package/os/{core → werkstatt}/handlers/workspace-deps.ts +4 -3
  128. package/os/werkstatt/werkstatt.module.ts +1 -0
  129. package/os/workflow/workflow.module.ts +1 -0
  130. package/package.json +13 -3
  131. package/profiles/forge-shell.yaml +1 -1
  132. package/profiles/godot-csharp.yaml +13 -13
  133. package/profiles/knowledge-typescript-turborepo.yaml +1 -1
  134. package/profiles/phaser-turborepo.yaml +13 -13
  135. package/skills/fo/fo-add-tests/SKILL.md +54 -0
  136. package/skills/fo/fo-compass-annotate/SKILL.md +1 -0
  137. package/skills/fo/fo-idea-implement/SKILL.md +80 -0
  138. package/src/compass/contract-registry.ts +114 -0
  139. package/src/compass/types.ts +45 -0
  140. package/src/forge-module.ts +3 -1
  141. package/src/index.ts +17 -0
  142. package/src/onboarding/doctor.ts +89 -7
  143. package/src/onboarding/init.ts +20 -1
  144. package/src/onboarding/scaffold.ts +1 -0
  145. package/src/onboarding/templates/behavioral-layer-core.md +9 -0
  146. package/src/onboarding/upgrade.ts +6 -1
  147. package/src/plugin/ForgePluginManifest.ts +76 -0
  148. package/src/registry.ts +36 -0
  149. package/src/tests/contract-registry.test.ts +248 -0
  150. package/src/tests/discover-pack-skills.test.ts +112 -0
  151. package/src/tests/doctor-autonomy.test.ts +3 -3
  152. package/src/tests/fixtures/agents-generate-business-before.txt +9 -0
  153. package/src/tests/forge-autonomy-validate.test.ts +157 -0
  154. package/src/tests/plugin-manifest.test.ts +226 -0
  155. package/src/tests/upgrade.test.ts +5 -0
  156. package/src/validators/port-validate.ts +3 -3
  157. package/dist/os/core/handlers/workspace-deps.d.ts.map +0 -1
  158. package/dist/os/core/handlers/workspace-deps.js.map +0 -1
  159. /package/dist/os/{core → werkstatt}/handlers/workspace-deps.d.ts +0 -0
@@ -100,6 +100,26 @@ Stage only the files touched by this step. Do not stage unrelated changes — an
100
100
  - **Compass scaffolding.** New non-trivial source files in `apps/` or `packages/` must carry `MODULE_CONTRACT` and `CHANGE_SUMMARY` scaffolding. Check the project's invariants file for the canonical Compass markup rule.
101
101
  - **Compass terminology.** Use Compass (not GRACE) in all new code, documentation, and log messages.
102
102
 
103
+ #### 4.3b. Write tests
104
+
105
+ After implementing each plan step that produces or modifies code, write tests for the new behavior. Tests are written for **agents**, not humans — see `fo-add-tests` for the full process and decision tree.
106
+
107
+ **Principles:**
108
+
109
+ - **Test through public interfaces.** Tests verify behavior, not implementation details.
110
+ - **Agent-readable failures.** When a test fails, the error message should tell an agent reading the console output what to fix and where — but only when the fix is not obvious from the assertion itself. If `expect(result).toBe(42)` fails with `received 41`, the fix is obvious. If a complex invariant fails, add a failure message: `expect(result, "Pipeline must skip already-validated entries — check the dedup guard in <function>")`).toEqual(expected)`.
111
+ - **Per-function classification.** Pure functions with verifiable properties get PBT (`*.pbt.test.ts`); everything else gets example-based tests (`*.test.ts`). See `fo-add-tests` §Process step 2.
112
+ - **Do not weaken or delete existing tests** without explicit operator direction.
113
+ - **Run tests after writing them:** `ref(forge.yaml bindings.commands.test) --filter <package>` or `pnpm --filter <package> test`.
114
+
115
+ Commit tests alongside the implementation they cover, or as a separate commit if the step was large:
116
+
117
+ ```txt
118
+ test: RFC-XXXX step N — cover <module>
119
+
120
+ Add unit and PBT coverage for <what was implemented in this step>.
121
+ ```
122
+
103
123
  #### 4.4. Run heavy checks
104
124
 
105
125
  After **all** plan steps are complete, run the heavy validation suite in order:
@@ -232,6 +252,26 @@ If the RFC has no `commands.*` changes (e.g. it only changes schemas, docs, or i
232
252
 
233
253
  This step is MANDATORY for any RFC with `commands.added`, `commands.changed`, or `commands.removed`. A stale manifest causes `RFC-CMD-02` violations that block `rfc.validate` for all implemented RFCs.
234
254
 
255
+ #### 4.8c. Regenerate agent files (AGENTS.md)
256
+
257
+ If the implementation changed contracts, conventions, commands, pipelines, package boundaries, or workflows that agents rely on, regenerate `AGENTS.md` so the generated instructions stay in sync with code:
258
+
259
+ ```sh
260
+ ref(forge.yaml bindings.commands.agentsGenerate)
261
+ ```
262
+
263
+ This regenerates root and nested `AGENTS.md` files from `forge.yaml` and stack profile data. The generated files carry a `<!-- forge:generated -->` marker — hand-written `AGENTS.md` files are never overwritten.
264
+
265
+ Commit regenerated agent files:
266
+
267
+ ```txt
268
+ docs: regenerate AGENTS.md for RFC-XXXX
269
+
270
+ Update generated agent instructions after RFC-XXXX implementation changes.
271
+ ```
272
+
273
+ Stage only the regenerated `AGENTS.md` files. If no agent-facing contracts changed (e.g. internal logic refactor with no new commands or conventions), skip this step.
274
+
235
275
  #### 4.9. Documentation audit (fo-doc-audit)
236
276
 
237
277
  After implementation is complete and all checks pass, invoke `fo-doc-audit` via the `skill` tool. It analyzes the session's changes, checks all documentation surfaces (AGENTS.md, README, Compass XML, architecture-dna.md, templates, generated artifacts, COMMANDS.md/PACKAGE_GRAPH.md), applies needed updates, and commits them separately. Wait for it to complete.
@@ -339,6 +379,26 @@ Read the `## Decision` section and implement it in code. **For each decision poi
339
379
 
340
380
  - If a tool call fails with a recoverable error, recover autonomously: split content, use `edit`/`multi_edit`, decompose files, and retry immediately.
341
381
 
382
+ #### 5.3b. Write tests
383
+
384
+ After implementing the decision, write tests for the new behavior. Tests are written for **agents**, not humans — see `fo-add-tests` for the full process and decision tree.
385
+
386
+ **Principles:**
387
+
388
+ - **Test through public interfaces.** Tests verify behavior, not implementation details.
389
+ - **Agent-readable failures.** When a test fails, the error message should tell an agent reading the console output what to fix and where — but only when the fix is not obvious from the assertion itself. If `expect(result).toBe(42)` fails with `received 41`, the fix is obvious. If a complex invariant fails, add a failure message: `expect(result, "Pipeline must skip already-validated entries — check the dedup guard in <function>")`).toEqual(expected)`.
390
+ - **Per-function classification.** Pure functions with verifiable properties get PBT (`*.pbt.test.ts`); everything else gets example-based tests (`*.test.ts`). See `fo-add-tests` §Process step 2.
391
+ - **Do not weaken or delete existing tests** without explicit operator direction.
392
+ - **Run tests after writing them:** `ref(forge.yaml bindings.commands.test) --filter <package>` or `pnpm --filter <package> test`.
393
+
394
+ Commit tests alongside the implementation they cover:
395
+
396
+ ```txt
397
+ test: ADR-XXXX — cover <module>
398
+
399
+ Add unit and PBT coverage for <what was implemented>.
400
+ ```
401
+
342
402
  #### 5.4. Run scoped build checks
343
403
 
344
404
  After implementation is complete, run heavy checks for the impacted workspaces only:
@@ -387,6 +447,26 @@ After implementation is complete and all checks pass, invoke `fo-doc-audit` via
387
447
 
388
448
  If `fo-doc-audit` reports that no updates are needed, proceed to the next step.
389
449
 
450
+ #### 5.6b. Regenerate agent files (AGENTS.md)
451
+
452
+ If the implementation changed contracts, conventions, commands, pipelines, package boundaries, or workflows that agents rely on, regenerate `AGENTS.md` so the generated instructions stay in sync with code:
453
+
454
+ ```sh
455
+ ref(forge.yaml bindings.commands.agentsGenerate)
456
+ ```
457
+
458
+ This regenerates root and nested `AGENTS.md` files from `forge.yaml` and stack profile data. The generated files carry a `<!-- forge:generated -->` marker — hand-written `AGENTS.md` files are never overwritten.
459
+
460
+ Commit regenerated agent files:
461
+
462
+ ```txt
463
+ docs: regenerate AGENTS.md for ADR-XXXX
464
+
465
+ Update generated agent instructions after ADR-XXXX implementation changes.
466
+ ```
467
+
468
+ Stage only the regenerated `AGENTS.md` files. If no agent-facing contracts changed, skip this step.
469
+
390
470
  #### 5.7. Code review (fo-review)
391
471
 
392
472
  After the documentation audit, invoke `fo-review` via the `skill` tool. It performs a cross-session fitness check of the code diff against Forge standards. The review covers all code changes made in this session.
@@ -0,0 +1,114 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Compass contract block registry — loads built-in and pack-declared block specs from forge.plugin.yaml manifests. Declarative data only, no executable hooks (RFC-0943).</purpose>
4
+ <non-goals>
5
+ <item>Do not execute pack-provided validate() functions — block specs are declarative data interpreted by compass.validate.</item>
6
+ <item>Do not import from os/ or kernel modules — this is a portable contract module.</item>
7
+ <item>Do not handle FORBIDDEN_PATTERNS — the negative list stays hardcoded in compass-inventory.ts.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0943: initial contract registry with built-in specs and pack-declared block loading.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { parse as parseYaml } from "yaml";
18
+ import picomatch from "picomatch";
19
+ import type { ForgeConfig } from "../config/forge-config.ts";
20
+ import { forgePluginManifestSchema } from "../plugin/ForgePluginManifest.ts";
21
+ import type {
22
+ CompassContractBlockSpec,
23
+ CompassContractRegistry,
24
+ CompassContractRegistryEntry,
25
+ } from "./types.ts";
26
+
27
+ const BUILT_IN_SPECS: CompassContractBlockSpec[] = [
28
+ {
29
+ blockId: "module-contract",
30
+ requiredFor: ["packages/**/*.ts", "packages/**/*.tsx", "packages/**/*.astro", "packages/**/*.js", "packages/**/*.mjs", "services/**/*.ts", "apps/**/*.ts", "apps/**/*.tsx", "apps/**/*.astro"],
31
+ requiredTags: [
32
+ { name: "purpose", minWords: 10 },
33
+ { name: "non-goals" },
34
+ ],
35
+ },
36
+ {
37
+ blockId: "change-summary",
38
+ requiredFor: ["packages/**/*.ts", "packages/**/*.tsx", "packages/**/*.astro", "packages/**/*.js", "packages/**/*.mjs", "services/**/*.ts", "apps/**/*.ts", "apps/**/*.tsx", "apps/**/*.astro"],
39
+ requiredTags: [],
40
+ },
41
+ ];
42
+
43
+ export function getBuiltInSpecs(): CompassContractBlockSpec[] {
44
+ return BUILT_IN_SPECS;
45
+ }
46
+
47
+ export function loadContractRegistry(
48
+ workspaceRoot: string,
49
+ config?: ForgeConfig,
50
+ ): CompassContractRegistry {
51
+ const pack: CompassContractRegistryEntry[] = [];
52
+ const seenBlockIds = new Set<string>();
53
+
54
+ if (config?.skillPacks) {
55
+ for (const skillPack of config.skillPacks) {
56
+ const packDir = path.resolve(workspaceRoot, skillPack.dir);
57
+ const manifestPath = path.join(packDir, "forge.plugin.yaml");
58
+ if (!fs.existsSync(manifestPath)) continue;
59
+
60
+ let manifestRaw: string;
61
+ try {
62
+ manifestRaw = fs.readFileSync(manifestPath, "utf-8");
63
+ } catch {
64
+ continue;
65
+ }
66
+
67
+ let manifestParsed: unknown;
68
+ try {
69
+ manifestParsed = parseYaml(manifestRaw);
70
+ } catch {
71
+ continue;
72
+ }
73
+
74
+ const result = forgePluginManifestSchema.safeParse(manifestParsed);
75
+ if (!result.success) continue;
76
+
77
+ const manifest = result.data;
78
+ const blocks = manifest.extensionPoints?.compass?.contract?.blocks;
79
+ if (!blocks || blocks.length === 0) continue;
80
+
81
+ for (const block of blocks) {
82
+ if (seenBlockIds.has(block.blockId)) {
83
+ throw new Error(
84
+ `COMPASS-PLUGIN-DUP-01: duplicate blockId "${block.blockId}" declared by pack "${manifest.id}" — already declared by another pack. Each blockId must be unique across all packs.`,
85
+ );
86
+ }
87
+ seenBlockIds.add(block.blockId);
88
+ pack.push({
89
+ blockId: block.blockId,
90
+ requiredFor: block.requiredFor,
91
+ requiredTags: block.requiredTags,
92
+ packId: manifest.id,
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ return {
99
+ builtIn: BUILT_IN_SPECS,
100
+ pack,
101
+ };
102
+ }
103
+
104
+ export function fileMatchesGlobs(filePath: string, globs: string[]): boolean {
105
+ const matchers = globs.map((g) => picomatch(g, { dot: true }));
106
+ return matchers.some((m) => m(filePath));
107
+ }
108
+
109
+ export function findApplicablePackSpecs(
110
+ registry: CompassContractRegistry,
111
+ filePath: string,
112
+ ): CompassContractRegistryEntry[] {
113
+ return registry.pack.filter((spec) => fileMatchesGlobs(filePath, spec.requiredFor));
114
+ }
@@ -0,0 +1,45 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Compass contract block spec types for plugin-extensible Compass validation (RFC-0943). Declarative data contracts only — no executable hooks.</purpose>
4
+ <non-goals>
5
+ <item>Do not define validate() functions — block specs are declarative data, interpreted by compass.validate.</item>
6
+ <item>Do not import from os/ or kernel modules — this is a portable contract module.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0943: initial CompassContractBlockSpec and CompassContractExtensionPoint types.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ export interface CompassContractRequiredTag {
15
+ name: string;
16
+ minWords?: number;
17
+ }
18
+
19
+ export interface CompassContractBlockSpec {
20
+ blockId: string;
21
+ requiredFor: string[];
22
+ requiredTags?: CompassContractRequiredTag[];
23
+ }
24
+
25
+ export interface CompassContractExtensionPoint {
26
+ blocks: CompassContractBlockSpec[];
27
+ }
28
+
29
+ export interface CompassContractRegistryEntry extends CompassContractBlockSpec {
30
+ packId: string;
31
+ }
32
+
33
+ export interface CompassContractRegistry {
34
+ builtIn: CompassContractBlockSpec[];
35
+ pack: CompassContractRegistryEntry[];
36
+ }
37
+
38
+ export interface CompassContractValidationDiagnostic {
39
+ ruleId: string;
40
+ severity: string;
41
+ file: string;
42
+ message: string;
43
+ fix: string;
44
+ pack?: string;
45
+ }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  <MODULE_CONTRACT>
3
- <purpose>ForgeModule interfaces — structurally compatible with KernelModule from @warpgogol/site-kernel. Forge does NOT import from site-kernel.</purpose>
3
+ <purpose>ForgeModule interfaces — structurally compatible with KernelModule from @warpgogol/site-kernel. Forge does NOT import from site-kernel. The optional runtime field (RFC-0940) declares whether a module is autonomous or a werkstatt adapter.</purpose>
4
4
  <non-goals>
5
5
  <item>Do not import from @warpgogol/site-kernel — forge must be installable without it.</item>
6
6
  <item>Do not add project-specific fields to ForgeModule — keep it minimal and portable.</item>
@@ -9,6 +9,7 @@
9
9
  <CHANGE_SUMMARY>
10
10
  <item>RFC-0374: initial ForgeModule, ForgeModuleRegistry, ForgeCommandDefinition, ForgePipelineStep interfaces.</item>
11
11
  <item>Forge autonomy refactor: ForgeCommandDefinition and ForgeCommandResult now reference canonical types from types.ts.</item>
12
+ <item>RFC-0940: add optional runtime field to ForgeModule for autonomy declaration.</item>
12
13
  </CHANGE_SUMMARY>
13
14
  */
14
15
 
@@ -27,6 +28,7 @@ export interface ForgeModuleRegistry {
27
28
  export interface ForgeModule {
28
29
  name: string;
29
30
  version: string;
31
+ runtime: "autonomous" | "werkstatt-adapter";
30
32
  register(registry: ForgeModuleRegistry): void | Promise<void>;
31
33
  }
32
34
 
package/src/index.ts CHANGED
@@ -155,6 +155,22 @@ export {
155
155
  detectAdapters,
156
156
  } from "./migration-adapters/index.ts";
157
157
 
158
+ // Plugin manifest (RFC-0941, RFC-0943)
159
+ export {
160
+ forgePluginManifestSchema,
161
+ type ForgePluginManifest,
162
+ } from "./plugin/ForgePluginManifest.ts";
163
+
164
+ // Compass contract types (RFC-0943)
165
+ export type {
166
+ CompassContractBlockSpec,
167
+ CompassContractRequiredTag,
168
+ CompassContractExtensionPoint,
169
+ CompassContractRegistry,
170
+ CompassContractRegistryEntry,
171
+ CompassContractValidationDiagnostic,
172
+ } from "./compass/types.ts";
173
+
158
174
  // OS modules
159
175
  export { forgeCoreModule } from "../os/core/core.module.ts";
160
176
  export { forgeRfcModule } from "../os/rfc/rfc.module.ts";
@@ -170,3 +186,4 @@ export { forgeMissionModule } from "../os/mission/mission.module.ts";
170
186
  export { forgeExplorationModule } from "../os/exploration/exploration.module.ts";
171
187
  export { forgeNotesModule } from "../os/notes/notes.module.ts";
172
188
  export { forgeProgramModule } from "../os/program/program.module.ts";
189
+ export { forgePluginModule } from "../os/plugin/plugin.module.ts";
@@ -22,13 +22,15 @@ Checks for forge.yaml, AGENTS.md, PREFERENCES.md, .agents/skills/, docs/rfcs/,
22
22
  <item>RFC-0663: added knowledge-duplicates check (cross-skill L2 duplicate detection) and shared-knowledge-file check (schema/id uniqueness for the shared layer).</item>
23
23
  <item>RFC-0664: added memory-layer health check (budget usage, gitignore coverage, daily-file leak risk).</item>
24
24
  <item>RFC-0704: added independent-version-packages check — validates that paths in independentVersionPackages exist and contain package.json.</item>
25
+ <item>RFC-0941: added pack-manifests advisory check — validates forge.plugin.yaml existence and schema for each declared skill pack.</item>
25
26
  </CHANGE_SUMMARY>
26
27
  */
27
28
 
28
29
  import { readFile, readdir, stat } from "node:fs/promises";
29
- import { readFileSync } from "node:fs";
30
- import { join, relative, dirname } from "node:path";
30
+ import { readFileSync, existsSync } from "node:fs";
31
+ import { join, relative, dirname, resolve } from "node:path";
31
32
  import { parse as parseYaml } from "yaml";
33
+ import { forgePluginManifestSchema } from "../plugin/ForgePluginManifest.ts";
32
34
  import type {
33
35
  ForgeCommandInput,
34
36
  ForgeCommandResult,
@@ -256,7 +258,12 @@ async function checkStaleKnowledgeFiles(
256
258
  }
257
259
 
258
260
  // RFC-0539: Check pack skill knowledge files
259
- const packSkills = discoverPackSkills(workspaceRoot, config);
261
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
262
+ try {
263
+ packSkills = discoverPackSkills(workspaceRoot, config);
264
+ } catch {
265
+ // Manifest missing or invalid — skip pack skill checks
266
+ }
260
267
  for (const skill of packSkills) {
261
268
  if (!skill.knowledge || skill.knowledge.length === 0) continue;
262
269
 
@@ -327,7 +334,12 @@ function checkLegacyKnowledgeSections(
327
334
  }
328
335
 
329
336
  // Check pack skills
330
- const packSkills = discoverPackSkills(workspaceRoot, config);
337
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
338
+ try {
339
+ packSkills = discoverPackSkills(workspaceRoot, config);
340
+ } catch {
341
+ // Manifest missing or invalid — skip pack skill checks
342
+ }
331
343
  for (const skill of packSkills) {
332
344
  if (!skill.knowledge || skill.knowledge.length === 0) continue;
333
345
  const skillDir = join(workspaceRoot, skill.dir, dirname(skill.path));
@@ -416,7 +428,12 @@ function checkKnowledgeBudgets(
416
428
  }
417
429
  }
418
430
 
419
- const packSkills = discoverPackSkills(workspaceRoot, config);
431
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
432
+ try {
433
+ packSkills = discoverPackSkills(workspaceRoot, config);
434
+ } catch {
435
+ // Manifest missing or invalid — skip pack skill checks
436
+ }
420
437
  for (const skill of packSkills) {
421
438
  if (!skill.knowledge || skill.knowledge.length === 0) continue;
422
439
  const skillDir = join(workspaceRoot, skill.dir, dirname(skill.path));
@@ -503,7 +520,12 @@ function collectL2Files(
503
520
  }
504
521
  }
505
522
 
506
- const packSkills = discoverPackSkills(workspaceRoot, config);
523
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
524
+ try {
525
+ packSkills = discoverPackSkills(workspaceRoot, config);
526
+ } catch {
527
+ // Manifest missing or invalid — skip pack skill checks
528
+ }
507
529
  for (const skill of packSkills) {
508
530
  if (!skill.knowledge || skill.knowledge.length === 0) continue;
509
531
  const skillDir = join(workspaceRoot, skill.dir, dirname(skill.path));
@@ -700,7 +722,12 @@ async function checkPackSkills(workspaceRoot: string): Promise<DoctorCheck> {
700
722
  }
701
723
 
702
724
  // Check for stale/missing pack skill copies
703
- const packSkills = discoverPackSkills(workspaceRoot, config);
725
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
726
+ try {
727
+ packSkills = discoverPackSkills(workspaceRoot, config);
728
+ } catch {
729
+ // Manifest missing or invalid — skip pack skill checks
730
+ }
704
731
  const agentsSkillsDir = join(workspaceRoot, config.paths.skillsDir);
705
732
 
706
733
  for (const skill of packSkills) {
@@ -731,6 +758,57 @@ async function checkPackSkills(workspaceRoot: string): Promise<DoctorCheck> {
731
758
  };
732
759
  }
733
760
 
761
+ // ---------------------------------------------------------------------------
762
+ // Pack manifest diagnostics (RFC-0941)
763
+ // ---------------------------------------------------------------------------
764
+
765
+ async function checkPackManifests(workspaceRoot: string): Promise<DoctorCheck> {
766
+ let config;
767
+ try {
768
+ config = loadForgeConfig(workspaceRoot);
769
+ } catch {
770
+ return {
771
+ name: "pack-manifests",
772
+ status: "pass",
773
+ message: "forge.yaml not loadable — pack manifest check skipped",
774
+ };
775
+ }
776
+
777
+ if (!config.skillPacks || config.skillPacks.length === 0) {
778
+ return { name: "pack-manifests", status: "pass", message: "No skill packs declared" };
779
+ }
780
+
781
+ const issues: string[] = [];
782
+ for (const pack of config.skillPacks) {
783
+ const packDir = resolve(workspaceRoot, pack.dir);
784
+ const manifestPath = join(packDir, "forge.plugin.yaml");
785
+ if (!existsSync(manifestPath)) {
786
+ issues.push(`pack '${pack.prefix}': forge.plugin.yaml missing at ${pack.dir}`);
787
+ continue;
788
+ }
789
+ try {
790
+ const raw = readFileSync(manifestPath, "utf-8");
791
+ const parsed = parseYaml(raw) as unknown;
792
+ const result = forgePluginManifestSchema.safeParse(parsed);
793
+ if (!result.success) {
794
+ const errs = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
795
+ issues.push(`pack '${pack.prefix}': invalid manifest — ${errs}`);
796
+ }
797
+ } catch (err) {
798
+ issues.push(`pack '${pack.prefix}': failed to read manifest — ${(err as Error).message}`);
799
+ }
800
+ }
801
+
802
+ return {
803
+ name: "pack-manifests",
804
+ status: issues.length === 0 ? "pass" : "warn",
805
+ message:
806
+ issues.length === 0
807
+ ? `${config.skillPacks.length} pack manifest(s) valid`
808
+ : `${issues.length} issue(s): ${issues.join(", ")}`,
809
+ };
810
+ }
811
+
734
812
  // ---------------------------------------------------------------------------
735
813
  // Nested AGENTS.md diagnostics (RFC-0611)
736
814
  // ---------------------------------------------------------------------------
@@ -1225,6 +1303,10 @@ export async function runDoctor(
1225
1303
  const packCheck = await checkPackSkills(workspaceRoot);
1226
1304
  checks.push(packCheck);
1227
1305
 
1306
+ // RFC-0941: Check pack manifests — forge.plugin.yaml existence and validity
1307
+ const packManifestCheck = await checkPackManifests(workspaceRoot);
1308
+ checks.push(packManifestCheck);
1309
+
1228
1310
  // RFC-0704: Check independent version packages — paths must exist and contain package.json
1229
1311
  const independentCheck = await checkIndependentVersionPackages(workspaceRoot);
1230
1312
  checks.push(independentCheck);
@@ -20,6 +20,7 @@
20
20
  <item>RFC-0640: accept optional domain fields from profile (register, domain, terminology, semanticBindings) and write them into PREFERENCES.md and forge.yaml.</item>
21
21
  <item>RFC-0643: accept optional profileId and write it to forge.yaml as the `profile` field.</item>
22
22
  <item>RFC-0663: added syncSharedKnowledge step to sync shared knowledge layer to .agents/skills/shared-knowledge/.</item>
23
+ <item>RFC-0941: create forge.plugin.yaml manifests for skill packs that lack them before calling discoverPackSkills.</item>
23
24
  </CHANGE_SUMMARY>
24
25
  */
25
26
 
@@ -244,7 +245,25 @@ export function runInit(
244
245
  // RFC-0539: Copy declared pack skills to .agents/skills/<name>/ for IDE discovery
245
246
  // RFC-0552: Detect Forge-vs-pack skill name conflicts, skip pack skills that conflict
246
247
  const forgeSkillNames = new Set(FORGE_SKILLS.map((s) => s.name));
247
- const packSkills = discoverPackSkills(workspaceRoot, config);
248
+ // RFC-0941: Create forge.plugin.yaml for packs that lack manifests
249
+ if (config.skillPacks) {
250
+ for (const pack of config.skillPacks) {
251
+ const packDir = path.resolve(workspaceRoot, pack.dir);
252
+ if (!fs.existsSync(packDir)) continue;
253
+ const manifestPath = path.join(packDir, "forge.plugin.yaml");
254
+ if (!fs.existsSync(manifestPath)) {
255
+ const manifest = { id: pack.prefix, version: "1.0.0" };
256
+ fs.writeFileSync(manifestPath, stringifyYaml(manifest), "utf8");
257
+ created.push(path.relative(workspaceRoot, manifestPath));
258
+ }
259
+ }
260
+ }
261
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
262
+ try {
263
+ packSkills = discoverPackSkills(workspaceRoot, config);
264
+ } catch {
265
+ // Manifest creation should prevent this, but catch defensively
266
+ }
248
267
  for (const skill of packSkills) {
249
268
  const srcPath = path.join(workspaceRoot, skill.dir, skill.path);
250
269
  const skillName = skill.name;
@@ -111,6 +111,7 @@ import type { ForgeModule, ForgeCommandDefinition } from "../../src/forge-module
111
111
  export const forge${capitalize(name)}Module: ForgeModule = {
112
112
  name: "forge-${name}",
113
113
  version: "0.1.0",
114
+ runtime: "autonomous",
114
115
  register(registry) {
115
116
  registry.registerCommand({
116
117
  name: "${name}",
@@ -34,6 +34,15 @@ Session save mechanism:
34
34
 
35
35
  The agent SHOULD auto-run `fo-review` after implementing a significant change. Review results are presented in creator language — only actionable issues are highlighted.
36
36
 
37
+ ### Auto-testing
38
+
39
+ The agent SHOULD write tests for new code it produces. Tests are written for **agents**, not humans — when a test fails, the error message should tell an agent reading the console output what to fix and where, but only when the fix is not obvious from the assertion itself.
40
+
41
+ - **Significant changes** (new features, new functions, bug fixes) → write tests.
42
+ - **Minor edits** (typo fix, small CSS change, renaming a variable) → no new tests required.
43
+ - Use `fo-add-tests` for the full process and decision tree (example-based vs PBT).
44
+ - Do not weaken or delete existing tests without explicit operator direction.
45
+
37
46
  ### Context awareness
38
47
 
39
48
  Before starting significant work, the agent SHOULD read recent ADRs, RFCs, and session transcripts (last 5-10 documents) to understand prior decisions and avoid conflicts. Minor edits do not require context reading.
@@ -213,7 +213,12 @@ function syncPackSkills(
213
213
  const updated: string[] = [];
214
214
  const skipped: SkippedSkill[] = [];
215
215
  const agentsSkillsDir = path.join(workspaceRoot, skillsDir);
216
- const packSkills = discoverPackSkills(workspaceRoot, config);
216
+ let packSkills: ReturnType<typeof discoverPackSkills> = [];
217
+ try {
218
+ packSkills = discoverPackSkills(workspaceRoot, config);
219
+ } catch {
220
+ // Manifest missing or invalid — skip pack skill sync
221
+ }
217
222
  const forgeSkillNames = new Set(FORGE_SKILLS.map((s) => s.name));
218
223
 
219
224
  for (const skill of packSkills) {
@@ -0,0 +1,76 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>ForgePluginManifest — pack identity contract for project-declared skill packs (RFC-0941). Strict Zod schema with optional extensionPoints (RFC-0943).</purpose>
4
+ <non-goals>
5
+ <item>Do not add forgeRange, commands, or skills fields — deferred until real use cases emerge.</item>
6
+ <item>Do not replace forge.yaml bindings — the manifest declares pack identity and extension points only.</item>
7
+ <item>Do not allow pack-provided validate() functions — extension points are declarative data, not executable code.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0941: initial ForgePluginManifest interface and Zod schema.</item>
12
+ <item>RFC-0943: added optional extensionPoints.compass.contract field for declarative Compass contract block specs.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import { z } from "zod";
17
+
18
+ const KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
19
+ const SEMVER_REGEX =
20
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
21
+
22
+ const compassContractBlockSpecSchema = z.object({
23
+ blockId: z
24
+ .string()
25
+ .regex(KEBAB_CASE_REGEX, "blockId must be kebab-case (lowercase letters, digits, hyphens)"),
26
+ requiredFor: z.array(z.string().min(1)).min(1),
27
+ requiredTags: z
28
+ .array(
29
+ z.object({
30
+ name: z.string().min(1),
31
+ minWords: z.number().int().positive().optional(),
32
+ }),
33
+ )
34
+ .optional(),
35
+ });
36
+
37
+ const compassContractExtensionPointSchema = z.object({
38
+ blocks: z.array(compassContractBlockSpecSchema),
39
+ });
40
+
41
+ const extensionPointsSchema = z
42
+ .object({
43
+ compass: z
44
+ .object({
45
+ contract: compassContractExtensionPointSchema.optional(),
46
+ })
47
+ .strict()
48
+ .optional(),
49
+ })
50
+ .strict();
51
+
52
+ export const forgePluginManifestSchema = z
53
+ .object({
54
+ id: z
55
+ .string()
56
+ .regex(KEBAB_CASE_REGEX, "id must be kebab-case (lowercase letters, digits, hyphens)"),
57
+ version: z.string().regex(SEMVER_REGEX, "version must be a valid semver string (e.g. 1.0.0)"),
58
+ extensionPoints: extensionPointsSchema.optional(),
59
+ })
60
+ .strict();
61
+
62
+ export interface ForgePluginManifest {
63
+ id: string;
64
+ version: string;
65
+ extensionPoints?: {
66
+ compass?: {
67
+ contract?: {
68
+ blocks: Array<{
69
+ blockId: string;
70
+ requiredFor: string[];
71
+ requiredTags?: Array<{ name: string; minWords?: number }>;
72
+ }>;
73
+ };
74
+ };
75
+ };
76
+ }