@warpgogol/forge 5.3.3 → 5.3.4
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/os/compass/compass.module.js +1 -1
- package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.js +5 -2
- package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
- package/dist/os/compass/handlers/summary-record.d.ts +6 -1
- package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
- package/dist/os/compass/handlers/summary-record.js +14 -5
- package/dist/os/compass/handlers/summary-record.js.map +1 -1
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.d.ts +18 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.d.ts.map +1 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.js +64 -0
- package/dist/os/compass/handlers/tests/forge-yaml-fixture.js.map +1 -0
- package/dist/os/compass/policy.d.ts +6 -1
- package/dist/os/compass/policy.d.ts.map +1 -1
- package/dist/os/compass/policy.js +8 -2
- package/dist/os/compass/policy.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +1 -2
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/rfc/handlers/validate.js +1 -1
- package/dist/src/config/forge-config.d.ts +21 -3
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +36 -7
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +26 -10
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +49 -2
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +24 -8
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/dist/src/validators/skill-validate.d.ts.map +1 -1
- package/dist/src/validators/skill-validate.js +42 -15
- package/dist/src/validators/skill-validate.js.map +1 -1
- package/os/compass/compass.module.ts +1 -1
- package/os/compass/handlers/compass-inventory.ts +6 -3
- package/os/compass/handlers/summary-record.ts +14 -5
- package/os/compass/handlers/tests/compass-policy.test.ts +15 -18
- package/os/compass/handlers/tests/compass-v2-contract.test.ts +38 -0
- package/os/compass/handlers/tests/forge-yaml-fixture.ts +69 -0
- package/os/compass/handlers/tests/summary-record.test.ts +25 -0
- package/os/compass/policy.ts +8 -2
- package/os/mission/handlers/archive.ts +1 -2
- package/os/rfc/handlers/validate.ts +1 -1
- package/package.json +1 -1
- package/skills/fo/fo-compass-annotate/SKILL.md +1 -1
- package/skills/fo/fo-handoff/SKILL.md +2 -2
- package/skills/fo/fo-idea-audit/SKILL.md +1 -1
- package/skills/fo/fo-idea-create-rfc/SKILL.md +1 -1
- package/skills/fo/fo-idea-implement/SKILL.md +4 -3
- package/skills/fo/fo-session-retro/SKILL.md +1 -1
- package/skills/fo/fo-session-save/SKILL.md +1 -1
- package/src/config/forge-config.ts +102 -57
- package/src/onboarding/create.ts +25 -9
- package/src/onboarding/doctor.ts +51 -2
- package/src/onboarding/upgrade.ts +36 -6
- package/src/tests/bindings-schema.test.ts +53 -1
- package/src/tests/doctor-bindings.test.ts +7 -2
- package/src/tests/doctor-skills-unmanaged.test.ts +145 -0
- package/src/tests/forge-config.test.ts +106 -2
- package/src/tests/skill-validate.test.ts +180 -0
- package/src/tests/upgrade.test.ts +60 -0
- package/src/validators/skill-validate.ts +43 -16
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
</non-goals>
|
|
7
7
|
</MODULE_CONTRACT>
|
|
8
8
|
<CHANGE_SUMMARY>
|
|
9
|
-
<item>RFC-0661: added optional knowledge.budgets binding for hot/warm layer character budget overrides.</item>
|
|
10
9
|
<item>RFC-0662: added optional knowledge.retentionDays and knowledge.staleDays bindings for compaction overrides.</item>
|
|
11
10
|
<item>RFC-0704: added optional independentVersionPackages field to forgeConfigSchema for packages with autonomous npm versions.</item>
|
|
12
11
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
@@ -15,14 +14,15 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
15
14
|
<item>RFC-1097: sweep — packages/forge + services clean
|
|
16
15
|
|
|
17
16
|
Sweep batch 2: real KEY_DECISIONS on 10 files, expanded purposes (CONTRACT-02/PURPOSE-02), headers on mission/index + gen-upstreams, sanitizeItemText in summary.record (literal Compass tags corrupted history), excludedPaths for wrangler types, test-fixtures testPattern. forge+services now 0 diagnostics under --mode error.</item>
|
|
18
|
-
<
|
|
17
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
18
|
+
<history>RFC-0391, RFC-0393, RFC-0537, RFC-0538, RFC-0539, RFC-0540, RFC-0543, RFC-0546, RFC-0639, RFC-0640, RFC-0643, RFC-0661, RFC-1224</history>
|
|
19
19
|
</CHANGE_SUMMARY>
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import * as fs from "../utils/sync-fs.ts";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { z } from "zod";
|
|
25
|
-
import { parse as parseYaml } from "yaml";
|
|
25
|
+
import { parse as parseYaml, parseDocument } from "yaml";
|
|
26
26
|
import {
|
|
27
27
|
TERMINOLOGY_DEFAULTS,
|
|
28
28
|
compassPolicyOverridesSchema,
|
|
@@ -36,22 +36,27 @@ import { listStackProfiles, type StackProfile } from "../profiles/stack-profile.
|
|
|
36
36
|
|
|
37
37
|
export const forgeBindingsSchema = z.object({
|
|
38
38
|
schema: z.literal("forge/bindings@1"),
|
|
39
|
-
commands: z
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
commands: z
|
|
40
|
+
.object({
|
|
41
|
+
validateRfc: z.string().nullable().default(null),
|
|
42
|
+
validateAdr: z.string().nullable().default(null),
|
|
43
|
+
implementStamp: z.string().nullable().default(null),
|
|
44
|
+
adrImplementStamp: z.string().nullable().default(null),
|
|
45
|
+
typecheck: z.string().nullable().default(null),
|
|
46
|
+
test: z.string().nullable().default(null),
|
|
47
|
+
scopedBuild: z.string().nullable().default(null),
|
|
48
|
+
specValidate: z.string().nullable().default(null),
|
|
49
|
+
sessionSave: z.string().nullable().default(null),
|
|
50
|
+
// Semantic keys (RFC-0639) — domain-neutral, optional
|
|
51
|
+
validate: z.string().nullable().default(null),
|
|
52
|
+
produce: z.string().nullable().default(null),
|
|
53
|
+
verify: z.string().nullable().default(null),
|
|
54
|
+
preview: z.string().nullable().default(null),
|
|
55
|
+
lint: z.string().nullable().default(null),
|
|
56
|
+
// RFC-1224: consumer-defined command keys (e.g. createRfc) stay in the
|
|
57
|
+
// parsed config so resolveBinding can resolve them.
|
|
58
|
+
})
|
|
59
|
+
.passthrough(),
|
|
55
60
|
paths: z.object({
|
|
56
61
|
invariantsFile: z.string().nullable().default(null),
|
|
57
62
|
compassDocs: z.array(z.string()).default([]),
|
|
@@ -98,6 +103,7 @@ export interface ForgeBindings {
|
|
|
98
103
|
validateRfc: string | null;
|
|
99
104
|
validateAdr: string | null;
|
|
100
105
|
implementStamp: string | null;
|
|
106
|
+
adrImplementStamp: string | null;
|
|
101
107
|
typecheck: string | null;
|
|
102
108
|
test: string | null;
|
|
103
109
|
scopedBuild: string | null;
|
|
@@ -177,44 +183,48 @@ export interface ForgeMigrationAdapter {
|
|
|
177
183
|
// Schema (forge/config@1)
|
|
178
184
|
// ---------------------------------------------------------------------------
|
|
179
185
|
|
|
180
|
-
export const forgeConfigSchema = z
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
186
|
+
export const forgeConfigSchema = z
|
|
187
|
+
.object({
|
|
188
|
+
schema: z.literal("forge/config@1"),
|
|
189
|
+
project: z.object({
|
|
190
|
+
name: z.string().min(1),
|
|
191
|
+
stack: z.array(z.string()).default([]),
|
|
192
|
+
packageManager: z.enum(["pnpm", "npm", "yarn", "bun", "none"]).default("pnpm"),
|
|
193
|
+
// RFC-0640: optional domain field — absent means software-domain fallback
|
|
194
|
+
domain: z.string().optional(),
|
|
195
|
+
}),
|
|
196
|
+
paths: z.object({
|
|
197
|
+
rfcsDir: z.string().default("docs/rfcs"),
|
|
198
|
+
adrsDir: z.string().default("docs/adrs"),
|
|
199
|
+
plansDir: z.string().default("docs/plans"),
|
|
200
|
+
auditsDir: z.string().default("docs/audits"),
|
|
201
|
+
specsDir: z.string().default("docs/specs"),
|
|
202
|
+
skillsDir: z.string().default(".agents/skills"),
|
|
203
|
+
sessionsDir: z.string().default("docs/sessions"),
|
|
204
|
+
}),
|
|
205
|
+
/** Bindings contract (RFC-0393). Optional — absent means no bindings. */
|
|
206
|
+
bindings: forgeBindingsSchema.optional(),
|
|
207
|
+
/** Skill packs (RFC-0539). Optional — absent means no project-local packs. */
|
|
208
|
+
skillPacks: z.array(forgeSkillPackSchema).optional(),
|
|
209
|
+
/** Migration adapters (RFC-0546). Optional — absent means built-in adapters only. */
|
|
210
|
+
migrationAdapters: z.array(forgeMigrationAdapterSchema).optional(),
|
|
211
|
+
/** Forge sync metadata (RFC-0543). Optional — absent means never synced. */
|
|
212
|
+
forge: z
|
|
213
|
+
.object({
|
|
214
|
+
syncedVersion: z.string().nullable().default(null),
|
|
215
|
+
})
|
|
216
|
+
.optional(),
|
|
217
|
+
/** RFC-0643: profile id — when present, loadForgeConfig loads the corresponding profiles/<id>.yaml */
|
|
218
|
+
// RFC-1118: accept the corrupted object form (whole StackProfile written by a
|
|
219
|
+
// previous buggy serialize) so loadForgeConfig can recover the declared id —
|
|
220
|
+
// a hard schema throw here would deadlock `forge upgrade`, the healing path.
|
|
221
|
+
profile: z.union([z.string(), z.object({ id: z.string().min(1) }).passthrough()]).optional(),
|
|
222
|
+
/** RFC-0704: packages with autonomous npm versions — ecosystem.commit skips platform bump when all staged files belong to these packages */
|
|
223
|
+
independentVersionPackages: z.array(z.string()).optional(),
|
|
224
|
+
// RFC-1224: consumer-owned top-level sections (e.g. `plugins:`) stay in the
|
|
225
|
+
// parsed config so full-serialization writes do not silently drop them.
|
|
226
|
+
})
|
|
227
|
+
.passthrough();
|
|
218
228
|
|
|
219
229
|
export interface ForgeConfig {
|
|
220
230
|
schema: "forge/config@1";
|
|
@@ -268,6 +278,10 @@ export const FORGE_CLI_BINDING_DEFAULTS: ForgeCliBindingDefault[] = [
|
|
|
268
278
|
key: "commands.implementStamp",
|
|
269
279
|
template: "forge rfc.implement.stamp --id {id} --implementation-commit {commit}",
|
|
270
280
|
},
|
|
281
|
+
{
|
|
282
|
+
key: "commands.adrImplementStamp",
|
|
283
|
+
template: "forge adr.implement.stamp --id {id} --implementation-commit {commit}",
|
|
284
|
+
},
|
|
271
285
|
{ key: "commands.specValidate", template: "forge spec.validate --spec={id} --json" },
|
|
272
286
|
{ key: "commands.sessionSave", template: "forge session.save --json" },
|
|
273
287
|
];
|
|
@@ -302,6 +316,7 @@ export function applyCliBindingDefaults(pm: string): ForgeBindings["commands"] {
|
|
|
302
316
|
validateRfc: null,
|
|
303
317
|
validateAdr: null,
|
|
304
318
|
implementStamp: null,
|
|
319
|
+
adrImplementStamp: null,
|
|
305
320
|
typecheck: null,
|
|
306
321
|
test: null,
|
|
307
322
|
scopedBuild: null,
|
|
@@ -471,6 +486,36 @@ export function serializeForgeConfig(config: ForgeConfig): Record<string, unknow
|
|
|
471
486
|
return out;
|
|
472
487
|
}
|
|
473
488
|
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
// YAML document patching — RFC-1224
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* A single targeted write into forge.yaml: a dotted key path expressed as
|
|
495
|
+
* segments plus the value to set. `applyForgeYamlPatches` creates missing
|
|
496
|
+
* intermediate maps and preserves comments/formatting elsewhere in the file.
|
|
497
|
+
*/
|
|
498
|
+
export interface ForgeYamlPatch {
|
|
499
|
+
path: string[];
|
|
500
|
+
value: unknown;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Apply targeted patches to a forge.yaml document without rewriting it.
|
|
505
|
+
* Returns the patched YAML text, or null when the document cannot be parsed —
|
|
506
|
+
* callers should fall back to full serialization in that case.
|
|
507
|
+
*/
|
|
508
|
+
export function applyForgeYamlPatches(rawYaml: string, patches: ForgeYamlPatch[]): string | null {
|
|
509
|
+
const doc = parseDocument(rawYaml);
|
|
510
|
+
if (doc.errors.length > 0) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
for (const patch of patches) {
|
|
514
|
+
doc.setIn(patch.path, patch.value);
|
|
515
|
+
}
|
|
516
|
+
return doc.toString();
|
|
517
|
+
}
|
|
518
|
+
|
|
474
519
|
// ---------------------------------------------------------------------------
|
|
475
520
|
// Forge root resolver — the single place that decides monorepo vs npm-installed
|
|
476
521
|
// ---------------------------------------------------------------------------
|
package/src/onboarding/create.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
</non-goals>
|
|
10
10
|
</MODULE_CONTRACT>
|
|
11
11
|
<CHANGE_SUMMARY>
|
|
12
|
-
<item>RFC-0643: pass profileId to runInit so forge.yaml gets a `profile` field.</item>
|
|
13
12
|
<item>RFC-0664: scaffold memory layer (.agents/memory/) after init.</item>
|
|
14
13
|
<item>RFC-0877: in-place mode only — --in-place flag required, no subdirectory creation, name derived from folder, strict empty-directory check (only .git/ tolerated).</item>
|
|
15
14
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
@@ -18,7 +17,8 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
18
17
|
<item>RFC-1097: sweep — packages/forge + services clean
|
|
19
18
|
|
|
20
19
|
Sweep batch 2: real KEY_DECISIONS on 10 files, expanded purposes (CONTRACT-02/PURPOSE-02), headers on mission/index + gen-upstreams, sanitizeItemText in summary.record (literal Compass tags corrupted history), excludedPaths for wrangler types, test-fixtures testPattern. forge+services now 0 diagnostics under --mode error.</item>
|
|
21
|
-
<
|
|
20
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
21
|
+
<history>RFC-0544, RFC-0548, RFC-0550, RFC-0640, RFC-0643, RFC-1224</history>
|
|
22
22
|
</CHANGE_SUMMARY>
|
|
23
23
|
*/
|
|
24
24
|
|
|
@@ -32,6 +32,7 @@ import { scaffoldMemoryLayer } from "./memory-scaffold.ts";
|
|
|
32
32
|
import {
|
|
33
33
|
loadForgeConfig,
|
|
34
34
|
serializeForgeConfig,
|
|
35
|
+
applyForgeYamlPatches,
|
|
35
36
|
resolveForgeRoot,
|
|
36
37
|
resolvePackageManager,
|
|
37
38
|
applyCliBindingDefaults,
|
|
@@ -346,15 +347,30 @@ export async function runCreate(
|
|
|
346
347
|
const forgeYamlPath = path.join(targetDir, "forge.yaml");
|
|
347
348
|
if (await fio.exists(forgeYamlPath)) {
|
|
348
349
|
try {
|
|
349
|
-
const config = loadForgeConfig(targetDir);
|
|
350
350
|
const pm = resolvePackageManager(packageManager);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
const commandDefaults = applyCliBindingDefaults(pm);
|
|
352
|
+
// RFC-1224: patch the document init just wrote instead of re-serializing —
|
|
353
|
+
// preserves comments/formatting and any schema-unknown sections.
|
|
354
|
+
const raw = await fio.readFile(forgeYamlPath);
|
|
355
|
+
const patched = applyForgeYamlPatches(raw, [
|
|
356
|
+
{ path: ["project", "packageManager"], value: pm },
|
|
357
|
+
...Object.entries(commandDefaults).map(([key, value]) => ({
|
|
358
|
+
path: ["bindings", "commands", key],
|
|
359
|
+
value,
|
|
360
|
+
})),
|
|
361
|
+
]);
|
|
362
|
+
if (patched !== null) {
|
|
363
|
+
await fio.writeFile(forgeYamlPath, patched);
|
|
364
|
+
} else {
|
|
365
|
+
const config = loadForgeConfig(targetDir);
|
|
366
|
+
config.project.packageManager = pm;
|
|
367
|
+
if (config.bindings) {
|
|
368
|
+
config.bindings.commands = commandDefaults;
|
|
369
|
+
}
|
|
370
|
+
// RFC-1118: serializeForgeConfig writes the declared profile id verbatim —
|
|
371
|
+
// never the resolved StackProfile object, never drops an unresolvable id.
|
|
372
|
+
await fio.writeFile(forgeYamlPath, stringifyYaml(serializeForgeConfig(config)));
|
|
354
373
|
}
|
|
355
|
-
// RFC-1118: serializeForgeConfig writes the declared profile id verbatim —
|
|
356
|
-
// never the resolved StackProfile object, never drops an unresolvable id.
|
|
357
|
-
await fio.writeFile(forgeYamlPath, stringifyYaml(serializeForgeConfig(config)));
|
|
358
374
|
} catch {
|
|
359
375
|
// Post-processing is best-effort — init already wrote a valid config
|
|
360
376
|
}
|
package/src/onboarding/doctor.ts
CHANGED
|
@@ -8,7 +8,6 @@ Checks for forge.yaml, AGENTS.md, PREFERENCES.md, .agents/skills/, docs/rfcs/,
|
|
|
8
8
|
</non-goals>
|
|
9
9
|
</MODULE_CONTRACT>
|
|
10
10
|
<CHANGE_SUMMARY>
|
|
11
|
-
<item>RFC-0664: added memory-layer health check (budget usage, gitignore coverage, daily-file leak risk).</item>
|
|
12
11
|
<item>RFC-0704: added independent-version-packages check — validates that paths in independentVersionPackages exist and contain package.json.</item>
|
|
13
12
|
<item>RFC-0941: added pack-manifests advisory check — validates forge.plugin.yaml existence and schema for each declared skill pack.</item>
|
|
14
13
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
@@ -17,7 +16,9 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
17
16
|
<item>RFC-1097: sweep — packages/forge + services clean
|
|
18
17
|
|
|
19
18
|
Sweep batch 2: real KEY_DECISIONS on 10 files, expanded purposes (CONTRACT-02/PURPOSE-02), headers on mission/index + gen-upstreams, sanitizeItemText in summary.record (literal Compass tags corrupted history), excludedPaths for wrangler types, test-fixtures testPattern. forge+services now 0 diagnostics under --mode error.</item>
|
|
20
|
-
<
|
|
19
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
20
|
+
<item>RFC-1226: skills-unmanaged check — warn on SKILL.md-bearing dirs without .forge-managed marker</item>
|
|
21
|
+
<history>RFC-0391, RFC-0393, RFC-0524, RFC-0539, RFC-0540, RFC-0611, RFC-0640, RFC-0660, RFC-0661, RFC-0663, RFC-0664, RFC-0675, RFC-1224</history>
|
|
21
22
|
</CHANGE_SUMMARY>
|
|
22
23
|
*/
|
|
23
24
|
|
|
@@ -54,6 +55,7 @@ import { compactMemoryMd } from "./memory-compact.ts";
|
|
|
54
55
|
import { checkInvariants } from "./invariant-engine.ts";
|
|
55
56
|
import type { InvariantViolation } from "./invariant-engine.ts";
|
|
56
57
|
import { checkNpmToken, workshopNeedsWarpgogolToken } from "./npm-token-check.ts";
|
|
58
|
+
import { SKILL_MARKER_FILE } from "./skill-markers.ts";
|
|
57
59
|
import { execSync } from "../utils/sync-fs.ts";
|
|
58
60
|
import * as fs from "../utils/sync-fs.ts";
|
|
59
61
|
import type { ProfilePrerequisite } from "../profiles/profile-schema.ts";
|
|
@@ -142,6 +144,7 @@ const BINDING_COMMAND_KEYS = [
|
|
|
142
144
|
"commands.validateRfc",
|
|
143
145
|
"commands.validateAdr",
|
|
144
146
|
"commands.implementStamp",
|
|
147
|
+
"commands.adrImplementStamp",
|
|
145
148
|
"commands.typecheck",
|
|
146
149
|
"commands.test",
|
|
147
150
|
"commands.scopedBuild",
|
|
@@ -762,6 +765,49 @@ async function checkPackSkills(workspaceRoot: string, io: WorkspaceIO): Promise<
|
|
|
762
765
|
};
|
|
763
766
|
}
|
|
764
767
|
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
// Unmanaged skill directories (RFC-1226)
|
|
770
|
+
// ---------------------------------------------------------------------------
|
|
771
|
+
|
|
772
|
+
// Visibility only: a dir bearing SKILL.md without .forge-managed is
|
|
773
|
+
// consumer-owned per RFC-1154 — report it, never prune or block.
|
|
774
|
+
async function checkUnmanagedSkillDirs(workspaceRoot: string, io: WorkspaceIO): Promise<DoctorCheck> {
|
|
775
|
+
let config;
|
|
776
|
+
try {
|
|
777
|
+
config = loadForgeConfig(workspaceRoot);
|
|
778
|
+
} catch {
|
|
779
|
+
return { name: "skills-unmanaged", status: "pass", message: "No forge.yaml — unmanaged skill dir check skipped" };
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const skillsDir = join(workspaceRoot, config.paths.skillsDir);
|
|
783
|
+
let entries;
|
|
784
|
+
try {
|
|
785
|
+
entries = await io.readdir(skillsDir);
|
|
786
|
+
} catch {
|
|
787
|
+
return { name: "skills-unmanaged", status: "pass", message: "skillsDir absent — nothing to scan" };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const unmanaged: string[] = [];
|
|
791
|
+
for (const entry of entries) {
|
|
792
|
+
if (!entry.isDirectory) continue;
|
|
793
|
+
const dir = join(skillsDir, entry.name);
|
|
794
|
+
// Support dirs (_shared/, knowledge-only dirs) are not skills — skip.
|
|
795
|
+
if (!(await io.exists(join(dir, "SKILL.md")))) continue;
|
|
796
|
+
if (await io.exists(join(dir, SKILL_MARKER_FILE))) continue;
|
|
797
|
+
unmanaged.push(entry.name);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (unmanaged.length === 0) {
|
|
801
|
+
return { name: "skills-unmanaged", status: "pass", message: "all skill dirs forge-managed" };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
name: "skills-unmanaged",
|
|
806
|
+
status: "warn",
|
|
807
|
+
message: `${unmanaged.length} unmanaged skill dir(s): ${unmanaged.join(", ")} — consumer-owned (no ${SKILL_MARKER_FILE} marker). Port into a declared skillPacks pack to make them forge-managed, or leave as-is.`,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
765
811
|
// ---------------------------------------------------------------------------
|
|
766
812
|
// Pack manifest diagnostics (RFC-0941)
|
|
767
813
|
// ---------------------------------------------------------------------------
|
|
@@ -1455,6 +1501,9 @@ export async function runDoctor(
|
|
|
1455
1501
|
const packManifestCheck = await checkPackManifests(workspaceRoot, fio);
|
|
1456
1502
|
checks.push(packManifestCheck);
|
|
1457
1503
|
|
|
1504
|
+
// RFC-1226: List unmanaged skill dirs under skillsDir (visibility only)
|
|
1505
|
+
checks.push(await checkUnmanagedSkillDirs(workspaceRoot, fio));
|
|
1506
|
+
|
|
1458
1507
|
// RFC-0704: Check independent version packages — paths must exist and contain package.json
|
|
1459
1508
|
const independentCheck = await checkIndependentVersionPackages(workspaceRoot, fio);
|
|
1460
1509
|
checks.push(independentCheck);
|
|
@@ -11,7 +11,6 @@ With --update-npm, also updates @warpgogol/forge from npm before syncing (skippe
|
|
|
11
11
|
</non-goals>
|
|
12
12
|
</MODULE_CONTRACT>
|
|
13
13
|
<CHANGE_SUMMARY>
|
|
14
|
-
<item>RFC-0611: added nested AGENTS.md generation after skill sync.</item>
|
|
15
14
|
<item>RFC-0663: added syncSharedKnowledge step to sync shared knowledge layer to .agents/skills/shared-knowledge/.</item>
|
|
16
15
|
<item>RFC-0664: added scaffoldMemoryLayer step to scaffold .agents/memory/ and .gitignore block.</item>
|
|
17
16
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
@@ -20,7 +19,8 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
20
19
|
<item>RFC-1097: sweep — werkstatt-engine clean
|
|
21
20
|
|
|
22
21
|
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
|
|
23
|
-
<
|
|
22
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
23
|
+
<history>RFC-0543, RFC-0611, RFC-1224</history>
|
|
24
24
|
</CHANGE_SUMMARY>
|
|
25
25
|
*/
|
|
26
26
|
|
|
@@ -35,10 +35,12 @@ import {
|
|
|
35
35
|
resolveForgeRoot,
|
|
36
36
|
loadForgeConfig,
|
|
37
37
|
serializeForgeConfig,
|
|
38
|
+
applyForgeYamlPatches,
|
|
38
39
|
resolvePmRunner,
|
|
39
40
|
resolvePmInstall,
|
|
40
41
|
type ForgeConfig,
|
|
41
42
|
type ForgeBindings,
|
|
43
|
+
type ForgeYamlPatch,
|
|
42
44
|
} from "../config/forge-config.ts";
|
|
43
45
|
import { FORGE_SKILLS, discoverPackSkills } from "../registry.ts";
|
|
44
46
|
import { syncKnowledgeFile } from "../knowledge/index.ts";
|
|
@@ -452,18 +454,38 @@ async function updateSyncedVersion(
|
|
|
452
454
|
workspaceRoot: string,
|
|
453
455
|
config: ForgeConfig,
|
|
454
456
|
version: string,
|
|
457
|
+
bindingsAdded: { key: string; value: string }[],
|
|
455
458
|
dryRun: boolean,
|
|
456
459
|
io: WorkspaceIO,
|
|
460
|
+
warn: (msg: string) => void,
|
|
457
461
|
): Promise<void> {
|
|
458
462
|
if (dryRun) return;
|
|
459
463
|
|
|
460
|
-
//
|
|
464
|
+
// Keep the in-memory config truthful for downstream steps (doctor).
|
|
461
465
|
config.forge = { syncedVersion: version };
|
|
462
466
|
|
|
463
|
-
//
|
|
467
|
+
// RFC-1224: patch the existing document — syncedVersion plus newly added
|
|
468
|
+
// binding defaults — instead of re-serializing, so consumer-owned keys,
|
|
469
|
+
// sections, comments and ordering survive the rewrite.
|
|
470
|
+
const patches: ForgeYamlPatch[] = [
|
|
471
|
+
{ path: ["forge", "syncedVersion"], value: version },
|
|
472
|
+
...bindingsAdded.map((b) => ({
|
|
473
|
+
path: ["bindings", ...b.key.split(".")],
|
|
474
|
+
value: b.value,
|
|
475
|
+
})),
|
|
476
|
+
];
|
|
477
|
+
const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
|
|
478
|
+
const raw = await io.readFile(forgeYamlPath).catch(() => null);
|
|
479
|
+
const patched = raw === null ? null : applyForgeYamlPatches(raw, patches);
|
|
480
|
+
if (patched !== null) {
|
|
481
|
+
await io.writeFile(forgeYamlPath, patched);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
warn(
|
|
485
|
+
"forge.upgrade: forge.yaml patch failed — falling back to full serialization (schema-unknown keys may be dropped)",
|
|
486
|
+
);
|
|
464
487
|
// RFC-1118: serializeForgeConfig writes the declared profile id verbatim —
|
|
465
488
|
// never the resolved StackProfile object, never drops an unresolvable id.
|
|
466
|
-
const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
|
|
467
489
|
await io.writeFile(forgeYamlPath, stringifyYaml(serializeForgeConfig(config)));
|
|
468
490
|
}
|
|
469
491
|
|
|
@@ -689,7 +711,15 @@ export async function runUpgrade(
|
|
|
689
711
|
|
|
690
712
|
// Step 5: Update forge.syncedVersion
|
|
691
713
|
if (!isDryRun) {
|
|
692
|
-
await updateSyncedVersion(
|
|
714
|
+
await updateSyncedVersion(
|
|
715
|
+
workspaceRoot,
|
|
716
|
+
config,
|
|
717
|
+
toVersion,
|
|
718
|
+
bindingsAdded,
|
|
719
|
+
false,
|
|
720
|
+
fio,
|
|
721
|
+
(msg) => context.logger.warn(msg),
|
|
722
|
+
);
|
|
693
723
|
}
|
|
694
724
|
|
|
695
725
|
// Step 5b: Generate nested AGENTS.md (RFC-0611). RFC-1153: the write path
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
</MODULE_CONTRACT>
|
|
8
8
|
<CHANGE_SUMMARY>
|
|
9
9
|
<item>RFC-0639: initial test suite for semantic command keys, terminology resolution, and applyCliBindingDefaults.</item>
|
|
10
|
+
<item>RFC-1224: adrImplementStamp schema/default coverage; consumer-defined command key passthrough.</item>
|
|
11
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
10
12
|
</CHANGE_SUMMARY>
|
|
11
13
|
*/
|
|
12
14
|
|
|
@@ -174,12 +176,14 @@ describe("RFC-0639: Terminology schema change", () => {
|
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
describe("RFC-0639: applyCliBindingDefaults", () => {
|
|
177
|
-
test("returns
|
|
179
|
+
test("returns 14 keys with semantic keys as null", () => {
|
|
178
180
|
const commands = applyCliBindingDefaults("pnpm");
|
|
179
181
|
// CLI-backed keys are non-null
|
|
180
182
|
expect(commands.validateRfc).not.toBeNull();
|
|
181
183
|
expect(commands.validateAdr).not.toBeNull();
|
|
182
184
|
expect(commands.implementStamp).not.toBeNull();
|
|
185
|
+
// RFC-1224: adr.implement.stamp promoted to a first-class default binding
|
|
186
|
+
expect(commands.adrImplementStamp).not.toBeNull();
|
|
183
187
|
expect(commands.specValidate).not.toBeNull();
|
|
184
188
|
expect(commands.sessionSave).not.toBeNull();
|
|
185
189
|
// Software-domain keys are null
|
|
@@ -312,3 +316,51 @@ describe("RFC-0639: resolveTerminology", () => {
|
|
|
312
316
|
expect(resolveTerminology(config, undefined, "source")).toBe("source file");
|
|
313
317
|
});
|
|
314
318
|
});
|
|
319
|
+
|
|
320
|
+
describe("RFC-1224: promoted + consumer-defined command keys", () => {
|
|
321
|
+
test("forgeBindingsSchema accepts adrImplementStamp", () => {
|
|
322
|
+
const result = forgeBindingsSchema.safeParse({
|
|
323
|
+
schema: "forge/bindings@1",
|
|
324
|
+
commands: {
|
|
325
|
+
adrImplementStamp:
|
|
326
|
+
"pnpm exec forge adr.implement.stamp --id {id} --implementation-commit {commit}",
|
|
327
|
+
},
|
|
328
|
+
paths: {
|
|
329
|
+
invariantsFile: null,
|
|
330
|
+
compassDocs: [],
|
|
331
|
+
reviewsDir: null,
|
|
332
|
+
handoffsDir: null,
|
|
333
|
+
sessionsDir: null,
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
expect(result.success).toBe(true);
|
|
337
|
+
if (result.success) {
|
|
338
|
+
expect(result.data.commands.adrImplementStamp).toBe(
|
|
339
|
+
"pnpm exec forge adr.implement.stamp --id {id} --implementation-commit {commit}",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("forgeBindingsSchema retains consumer-defined command keys", () => {
|
|
345
|
+
const result = forgeBindingsSchema.safeParse({
|
|
346
|
+
schema: "forge/bindings@1",
|
|
347
|
+
commands: {
|
|
348
|
+
validateRfc: null,
|
|
349
|
+
createRfc: "pnpm exec forge rfc.create",
|
|
350
|
+
},
|
|
351
|
+
paths: {
|
|
352
|
+
invariantsFile: null,
|
|
353
|
+
compassDocs: [],
|
|
354
|
+
reviewsDir: null,
|
|
355
|
+
handoffsDir: null,
|
|
356
|
+
sessionsDir: null,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
expect(result.success).toBe(true);
|
|
360
|
+
if (result.success) {
|
|
361
|
+
expect((result.data.commands as Record<string, unknown>)["createRfc"]).toBe(
|
|
362
|
+
"pnpm exec forge rfc.create",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
</MODULE_CONTRACT>
|
|
5
5
|
<CHANGE_SUMMARY>
|
|
6
6
|
<item>RFC-0540: initial doctor binding notices tests.</item>
|
|
7
|
+
<item>RFC-1224: adrImplementStamp added to notice inventory and fixtures.</item>
|
|
8
|
+
<item>RFC-1224: preserve operator forge.yaml content on upgrade, promote adrImplementStamp binding</item>
|
|
7
9
|
</CHANGE_SUMMARY>
|
|
8
10
|
*/
|
|
9
11
|
|
|
@@ -58,7 +60,7 @@ async function setupMinimalProject(dir: string): Promise<void> {
|
|
|
58
60
|
await writeFile(join(dir, "PREFERENCES.md"), "# Test", "utf8");
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
test("doctor emits defaultable-binding-null notices for all
|
|
63
|
+
test("doctor emits defaultable-binding-null notices for all 6 forge-CLI bindings when null", async () => {
|
|
62
64
|
await setupMinimalProject(tempDir);
|
|
63
65
|
await writeForgeYaml(
|
|
64
66
|
tempDir,
|
|
@@ -96,11 +98,12 @@ bindings:
|
|
|
96
98
|
|
|
97
99
|
const result = await runDoctor({ argv: [], flags: {} }, mockContext(tempDir));
|
|
98
100
|
const notices = result.data?.bindings?.notices ?? [];
|
|
99
|
-
expect(notices).toHaveLength(
|
|
101
|
+
expect(notices).toHaveLength(6);
|
|
100
102
|
const keys = notices.map((n) => n.key);
|
|
101
103
|
expect(keys).toContain("commands.validateRfc");
|
|
102
104
|
expect(keys).toContain("commands.validateAdr");
|
|
103
105
|
expect(keys).toContain("commands.implementStamp");
|
|
106
|
+
expect(keys).toContain("commands.adrImplementStamp");
|
|
104
107
|
expect(keys).toContain("commands.specValidate");
|
|
105
108
|
expect(keys).toContain("commands.sessionSave");
|
|
106
109
|
for (const notice of notices) {
|
|
@@ -131,6 +134,7 @@ bindings:
|
|
|
131
134
|
validateRfc: "custom rfc validate command"
|
|
132
135
|
validateAdr: "custom adr validate command"
|
|
133
136
|
implementStamp: "custom stamp command"
|
|
137
|
+
adrImplementStamp: "custom adr stamp command"
|
|
134
138
|
typecheck: null
|
|
135
139
|
test: null
|
|
136
140
|
scopedBuild: null
|
|
@@ -172,6 +176,7 @@ bindings:
|
|
|
172
176
|
validateRfc: "npx forge rfc.validate --id {id} --json"
|
|
173
177
|
validateAdr: "npx forge adr.validate --id {id} --json"
|
|
174
178
|
implementStamp: "npx forge rfc.implement.stamp --id {id} --implementation-commit {commit}"
|
|
179
|
+
adrImplementStamp: "npx forge adr.implement.stamp --id {id} --implementation-commit {commit}"
|
|
175
180
|
typecheck: null
|
|
176
181
|
test: null
|
|
177
182
|
scopedBuild: null
|