@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.
- package/dist/bin/cli.js +1 -1
- package/dist/os/adr/adr.module.d.ts.map +1 -1
- package/dist/os/adr/adr.module.js +1 -0
- package/dist/os/adr/adr.module.js.map +1 -1
- package/dist/os/audit/audit.module.d.ts.map +1 -1
- package/dist/os/audit/audit.module.js +1 -0
- package/dist/os/audit/audit.module.js.map +1 -1
- package/dist/os/compass/compass.module.d.ts.map +1 -1
- package/dist/os/compass/compass.module.js +1 -0
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.js +82 -1
- package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +16 -0
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/core/handlers/assets-helpers.js +1 -1
- package/dist/os/core/handlers/assets-helpers.js.map +1 -1
- package/dist/os/core/handlers/determinism-check.js +1 -1
- package/dist/os/core/handlers/determinism-check.js.map +1 -1
- package/dist/os/core/handlers/forge-autonomy-validate.d.ts +18 -0
- package/dist/os/core/handlers/forge-autonomy-validate.d.ts.map +1 -0
- package/dist/os/core/handlers/forge-autonomy-validate.js +78 -0
- package/dist/os/core/handlers/forge-autonomy-validate.js.map +1 -0
- package/dist/os/core/handlers/knowledge-compact.d.ts.map +1 -1
- package/dist/os/core/handlers/knowledge-compact.js +7 -1
- package/dist/os/core/handlers/knowledge-compact.js.map +1 -1
- package/dist/os/core/handlers/release-prepare.js +1 -1
- package/dist/os/core/handlers/release-prepare.js.map +1 -1
- package/dist/os/exploration/exploration.module.d.ts.map +1 -1
- package/dist/os/exploration/exploration.module.js +1 -0
- package/dist/os/exploration/exploration.module.js.map +1 -1
- package/dist/os/mission/mission.module.d.ts.map +1 -1
- package/dist/os/mission/mission.module.js +1 -0
- package/dist/os/mission/mission.module.js.map +1 -1
- package/dist/os/naming/naming.module.d.ts.map +1 -1
- package/dist/os/naming/naming.module.js +1 -0
- package/dist/os/naming/naming.module.js.map +1 -1
- package/dist/os/notes/notes.module.d.ts.map +1 -1
- package/dist/os/notes/notes.module.js +1 -0
- package/dist/os/notes/notes.module.js.map +1 -1
- package/dist/os/plan/plan.module.d.ts.map +1 -1
- package/dist/os/plan/plan.module.js +1 -0
- package/dist/os/plan/plan.module.js.map +1 -1
- package/dist/os/plugin/plugin.module.d.ts +25 -0
- package/dist/os/plugin/plugin.module.d.ts.map +1 -0
- package/dist/os/plugin/plugin.module.js +208 -0
- package/dist/os/plugin/plugin.module.js.map +1 -0
- package/dist/os/program/program.module.d.ts.map +1 -1
- package/dist/os/program/program.module.js +1 -0
- package/dist/os/program/program.module.js.map +1 -1
- package/dist/os/rfc/rfc.module.d.ts.map +1 -1
- package/dist/os/rfc/rfc.module.js +1 -0
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/session/session.module.d.ts.map +1 -1
- package/dist/os/session/session.module.js +1 -0
- package/dist/os/session/session.module.js.map +1 -1
- package/dist/os/spec/spec.module.d.ts.map +1 -1
- package/dist/os/spec/spec.module.js +1 -0
- package/dist/os/spec/spec.module.js.map +1 -1
- package/dist/os/werkstatt/handlers/workspace-deps.d.ts.map +1 -0
- package/dist/os/{core → werkstatt}/handlers/workspace-deps.js +4 -3
- package/dist/os/werkstatt/handlers/workspace-deps.js.map +1 -0
- package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.js +1 -0
- package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
- package/dist/os/workflow/workflow.module.d.ts.map +1 -1
- package/dist/os/workflow/workflow.module.js +1 -0
- package/dist/os/workflow/workflow.module.js.map +1 -1
- package/dist/src/compass/contract-registry.d.ts +7 -0
- package/dist/src/compass/contract-registry.d.ts.map +1 -0
- package/dist/src/compass/contract-registry.js +93 -0
- package/dist/src/compass/contract-registry.js.map +1 -0
- package/dist/src/compass/types.d.ts +28 -0
- package/dist/src/compass/types.d.ts.map +1 -0
- package/dist/src/compass/types.js +14 -0
- package/dist/src/compass/types.js.map +1 -0
- package/dist/src/forge-module.d.ts +1 -0
- package/dist/src/forge-module.d.ts.map +1 -1
- package/dist/src/forge-module.js +2 -1
- package/dist/src/forge-module.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +89 -7
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +22 -1
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/scaffold.d.ts.map +1 -1
- package/dist/src/onboarding/scaffold.js +1 -0
- package/dist/src/onboarding/scaffold.js.map +1 -1
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +7 -1
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/dist/src/plugin/ForgePluginManifest.d.ts +38 -0
- package/dist/src/plugin/ForgePluginManifest.d.ts.map +1 -0
- package/dist/src/plugin/ForgePluginManifest.js +52 -0
- package/dist/src/plugin/ForgePluginManifest.js.map +1 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +28 -0
- package/dist/src/registry.js.map +1 -1
- package/dist/src/validators/port-validate.js +3 -3
- package/dist/src/validators/port-validate.js.map +1 -1
- package/os/adr/adr.module.ts +1 -0
- package/os/audit/audit.module.ts +1 -0
- package/os/compass/compass.module.ts +1 -0
- package/os/compass/handlers/compass-inventory-handler.ts +107 -1
- package/os/core/core.module.ts +18 -0
- package/os/core/handlers/assets-helpers.ts +1 -1
- package/os/core/handlers/determinism-check.ts +1 -1
- package/os/core/handlers/forge-autonomy-validate.ts +115 -0
- package/os/core/handlers/knowledge-compact.ts +6 -1
- package/os/core/handlers/release-prepare.ts +1 -1
- package/os/exploration/exploration.module.ts +1 -0
- package/os/mission/mission.module.ts +1 -0
- package/os/naming/naming.module.ts +1 -0
- package/os/notes/notes.module.ts +1 -0
- package/os/plan/plan.module.ts +1 -0
- package/os/plugin/plugin.module.ts +261 -0
- package/os/program/program.module.ts +1 -0
- package/os/rfc/rfc.module.ts +1 -0
- package/os/session/session.module.ts +1 -0
- package/os/spec/spec.module.ts +1 -0
- package/os/{core → werkstatt}/handlers/workspace-deps.ts +4 -3
- package/os/werkstatt/werkstatt.module.ts +1 -0
- package/os/workflow/workflow.module.ts +1 -0
- package/package.json +13 -3
- package/profiles/forge-shell.yaml +1 -1
- package/profiles/godot-csharp.yaml +13 -13
- package/profiles/knowledge-typescript-turborepo.yaml +1 -1
- package/profiles/phaser-turborepo.yaml +13 -13
- package/skills/fo/fo-add-tests/SKILL.md +54 -0
- package/skills/fo/fo-compass-annotate/SKILL.md +1 -0
- package/skills/fo/fo-idea-implement/SKILL.md +80 -0
- package/src/compass/contract-registry.ts +114 -0
- package/src/compass/types.ts +45 -0
- package/src/forge-module.ts +3 -1
- package/src/index.ts +17 -0
- package/src/onboarding/doctor.ts +89 -7
- package/src/onboarding/init.ts +20 -1
- package/src/onboarding/scaffold.ts +1 -0
- package/src/onboarding/templates/behavioral-layer-core.md +9 -0
- package/src/onboarding/upgrade.ts +6 -1
- package/src/plugin/ForgePluginManifest.ts +76 -0
- package/src/registry.ts +36 -0
- package/src/tests/contract-registry.test.ts +248 -0
- package/src/tests/discover-pack-skills.test.ts +112 -0
- package/src/tests/doctor-autonomy.test.ts +3 -3
- package/src/tests/fixtures/agents-generate-business-before.txt +9 -0
- package/src/tests/forge-autonomy-validate.test.ts +157 -0
- package/src/tests/plugin-manifest.test.ts +226 -0
- package/src/tests/upgrade.test.ts +5 -0
- package/src/validators/port-validate.ts +3 -3
- package/dist/os/core/handlers/workspace-deps.d.ts.map +0 -1
- package/dist/os/core/handlers/workspace-deps.js.map +0 -1
- /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
|
+
}
|
package/src/forge-module.ts
CHANGED
|
@@ -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";
|
package/src/onboarding/doctor.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/src/onboarding/init.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
}
|