@warpgogol/forge 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for forge.upgrade handler (RFC-0543) and VERSION sourcing.</purpose>
|
|
4
|
+
</MODULE_CONTRACT>
|
|
5
|
+
<CHANGE_SUMMARY>
|
|
6
|
+
<item>RFC-0543: initial upgrade handler and VERSION sourcing tests.</item>
|
|
7
|
+
<item>RFC-0552: add test for Forge-vs-pack skill name conflict detection in upgrade.</item>
|
|
8
|
+
</CHANGE_SUMMARY>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
+
import { mkdtemp, mkdir, writeFile, rm, readFile } from "node:fs/promises";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { runUpgrade } from "../onboarding/upgrade.ts";
|
|
17
|
+
import { runInit } from "../onboarding/init.ts";
|
|
18
|
+
import {
|
|
19
|
+
loadForgeConfig,
|
|
20
|
+
FORGE_CLI_BINDING_DEFAULTS,
|
|
21
|
+
resolvePmRunner,
|
|
22
|
+
} from "../config/forge-config.ts";
|
|
23
|
+
|
|
24
|
+
let tempDir: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
tempDir = await mkdtemp(join(tmpdir(), "forge-upgrade-test-"));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
async function setupForgeSource(dir: string, version = "0.2.0"): Promise<void> {
|
|
35
|
+
await mkdir(join(dir, "packages", "forge", "skills", "fo", "fo-idea"), { recursive: true });
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(dir, "packages", "forge", "skills", "fo", "fo-idea", "SKILL.md"),
|
|
38
|
+
"---\nname: fo-idea\n---\n# fo-idea\n",
|
|
39
|
+
"utf8",
|
|
40
|
+
);
|
|
41
|
+
await writeFile(
|
|
42
|
+
join(dir, "packages", "forge", "package.json"),
|
|
43
|
+
JSON.stringify({ name: "@warpgogol/forge", version }),
|
|
44
|
+
"utf8",
|
|
45
|
+
);
|
|
46
|
+
await mkdir(join(dir, "packages", "forge", "os", "rfc"), { recursive: true });
|
|
47
|
+
await writeFile(
|
|
48
|
+
join(dir, "packages", "forge", "os", "rfc", "rfc-0000-template.md"),
|
|
49
|
+
"# RFC Template",
|
|
50
|
+
"utf8",
|
|
51
|
+
);
|
|
52
|
+
await mkdir(join(dir, "packages", "forge", "profiles"), { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function setupConsumerForgeYaml(
|
|
56
|
+
dir: string,
|
|
57
|
+
syncedVersion: string | null,
|
|
58
|
+
customBindings?: Record<string, string | null>,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const commands: Record<string, string | null> = {
|
|
61
|
+
validateRfc: null,
|
|
62
|
+
validateAdr: null,
|
|
63
|
+
implementStamp: null,
|
|
64
|
+
typecheck: null,
|
|
65
|
+
test: null,
|
|
66
|
+
scopedBuild: null,
|
|
67
|
+
specValidate: null,
|
|
68
|
+
sessionSave: null,
|
|
69
|
+
...customBindings,
|
|
70
|
+
};
|
|
71
|
+
const forgeSection =
|
|
72
|
+
syncedVersion !== null ? `\nforge:\n syncedVersion: ${syncedVersion}\n` : "";
|
|
73
|
+
|
|
74
|
+
const yaml = `schema: forge/config@1
|
|
75
|
+
project:
|
|
76
|
+
name: consumer-project
|
|
77
|
+
stack: []
|
|
78
|
+
packageManager: pnpm
|
|
79
|
+
paths:
|
|
80
|
+
rfcsDir: docs/rfcs
|
|
81
|
+
adrsDir: docs/adrs
|
|
82
|
+
plansDir: docs/plans
|
|
83
|
+
auditsDir: docs/audits
|
|
84
|
+
specsDir: docs/specs
|
|
85
|
+
skillsDir: .agents/skills
|
|
86
|
+
sessionsDir: docs/sessions
|
|
87
|
+
bindings:
|
|
88
|
+
schema: forge/bindings@1
|
|
89
|
+
commands:
|
|
90
|
+
validateRfc: ${commands.validateRfc ? `"${commands.validateRfc}"` : "null"}
|
|
91
|
+
validateAdr: ${commands.validateAdr ? `"${commands.validateAdr}"` : "null"}
|
|
92
|
+
implementStamp: ${commands.implementStamp ? `"${commands.implementStamp}"` : "null"}
|
|
93
|
+
typecheck: ${commands.typecheck ? `"${commands.typecheck}"` : "null"}
|
|
94
|
+
test: ${commands.test ? `"${commands.test}"` : "null"}
|
|
95
|
+
scopedBuild: ${commands.scopedBuild ? `"${commands.scopedBuild}"` : "null"}
|
|
96
|
+
specValidate: ${commands.specValidate ? `"${commands.specValidate}"` : "null"}
|
|
97
|
+
sessionSave: ${commands.sessionSave ? `"${commands.sessionSave}"` : "null"}
|
|
98
|
+
paths:
|
|
99
|
+
invariantsFile: null
|
|
100
|
+
compassDocs: []
|
|
101
|
+
reviewsDir: null
|
|
102
|
+
handoffsDir: null
|
|
103
|
+
sessionsDir: null${forgeSection}`;
|
|
104
|
+
await writeFile(join(dir, "forge.yaml"), yaml, "utf8");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function makeContext(workspaceRoot: string, dryRun = false) {
|
|
108
|
+
return {
|
|
109
|
+
workspaceRoot,
|
|
110
|
+
logger: {
|
|
111
|
+
section: () => {},
|
|
112
|
+
info: () => {},
|
|
113
|
+
warn: () => {},
|
|
114
|
+
error: () => {},
|
|
115
|
+
success: () => {},
|
|
116
|
+
},
|
|
117
|
+
dryRun,
|
|
118
|
+
outputFormat: "json" as const,
|
|
119
|
+
commandRegistry: undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
test("forge.upgrade --dry-run writes no files", async () => {
|
|
124
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
125
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
126
|
+
|
|
127
|
+
const result = await runUpgrade(
|
|
128
|
+
{ argv: [], flags: { "dry-run": true } },
|
|
129
|
+
makeContext(tempDir, true),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(result.data?.status).toBe("pass");
|
|
133
|
+
expect(result.data?.skillsUpdated.length).toBeGreaterThan(0);
|
|
134
|
+
// No skills directory should have been created
|
|
135
|
+
expect(existsSync(join(tempDir, ".agents", "skills"))).toBe(false);
|
|
136
|
+
// forge.yaml should still have no forge section
|
|
137
|
+
const yaml = await readFile(join(tempDir, "forge.yaml"), "utf8");
|
|
138
|
+
expect(yaml).not.toContain("syncedVersion: 0.2.0");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("forge.upgrade with syncedVersion: null performs full sync", async () => {
|
|
142
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
143
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
144
|
+
|
|
145
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
146
|
+
|
|
147
|
+
expect(result.data?.status).toBe("pass");
|
|
148
|
+
expect(result.data?.fromVersion).toBe("never-synced");
|
|
149
|
+
expect(result.data?.toVersion).toBe("0.2.0");
|
|
150
|
+
expect(result.data?.skillsUpdated.length).toBeGreaterThan(0);
|
|
151
|
+
|
|
152
|
+
// syncedVersion should be updated in forge.yaml
|
|
153
|
+
const config = loadForgeConfig(tempDir);
|
|
154
|
+
expect(config.forge?.syncedVersion).toBe("0.2.0");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("forge.upgrade with syncedVersion matching installed version returns noop", async () => {
|
|
158
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
159
|
+
await setupConsumerForgeYaml(tempDir, "0.2.0");
|
|
160
|
+
|
|
161
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
162
|
+
|
|
163
|
+
expect(result.data?.status).toBe("noop");
|
|
164
|
+
expect(result.data?.fromVersion).toBe("0.2.0");
|
|
165
|
+
expect(result.data?.toVersion).toBe("0.2.0");
|
|
166
|
+
expect(result.data?.skillsUpdated).toEqual([]);
|
|
167
|
+
expect(result.exitCode).toBe(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("forge.upgrade never overwrites a non-null operator-set binding", async () => {
|
|
171
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
172
|
+
await setupConsumerForgeYaml(tempDir, null, {
|
|
173
|
+
validateRfc: "my-custom-validate-command",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
177
|
+
|
|
178
|
+
expect(result.data?.status).toBe("pass");
|
|
179
|
+
|
|
180
|
+
// The custom binding should be preserved
|
|
181
|
+
const config = loadForgeConfig(tempDir);
|
|
182
|
+
expect(config.bindings?.commands.validateRfc).toBe("my-custom-validate-command");
|
|
183
|
+
|
|
184
|
+
// Other null bindings should have been filled with defaults
|
|
185
|
+
const runner = resolvePmRunner("pnpm");
|
|
186
|
+
expect(config.bindings?.commands.validateAdr).toBe(
|
|
187
|
+
`${runner} forge adr.validate --id {id} --json`,
|
|
188
|
+
);
|
|
189
|
+
expect(config.bindings?.commands.implementStamp).toBe(
|
|
190
|
+
`${runner} forge rfc.implement.stamp --id {id} --implementation-commit {commit}`,
|
|
191
|
+
);
|
|
192
|
+
expect(config.bindings?.commands.specValidate).toBe(
|
|
193
|
+
`${runner} forge spec.validate --spec={id} --json`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// The added bindings should be reported
|
|
197
|
+
const addedKeys = result.data?.bindingsAdded.map((b) => b.key);
|
|
198
|
+
expect(addedKeys).not.toContain("commands.validateRfc");
|
|
199
|
+
expect(addedKeys).toContain("commands.validateAdr");
|
|
200
|
+
expect(addedKeys).toContain("commands.implementStamp");
|
|
201
|
+
expect(addedKeys).toContain("commands.specValidate");
|
|
202
|
+
expect(addedKeys).toContain("commands.sessionSave");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("forge.upgrade refuses when forge.yaml is missing", async () => {
|
|
206
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
207
|
+
|
|
208
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
209
|
+
|
|
210
|
+
expect(result.data?.status).toBe("fail");
|
|
211
|
+
expect(result.exitCode).toBe(1);
|
|
212
|
+
expect(result.nextSteps?.[0]?.action).toContain("forge create");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("forge.upgrade adds all FORGE_CLI_BINDING_DEFAULTS keys when all are null", async () => {
|
|
216
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
217
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
218
|
+
|
|
219
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
220
|
+
|
|
221
|
+
expect(result.data?.bindingsAdded.length).toBe(FORGE_CLI_BINDING_DEFAULTS.length);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("forge.init writes forge.syncedVersion on first init", async () => {
|
|
225
|
+
await setupForgeSource(tempDir, "0.3.0");
|
|
226
|
+
|
|
227
|
+
runInit({ flags: {} }, { workspaceRoot: tempDir });
|
|
228
|
+
|
|
229
|
+
const config = loadForgeConfig(tempDir);
|
|
230
|
+
expect(config.forge?.syncedVersion).toBe("0.3.0");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("forge.upgrade syncs skills to .agents/skills/", async () => {
|
|
234
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
235
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
236
|
+
|
|
237
|
+
await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
238
|
+
|
|
239
|
+
// Check that fo-idea skill was copied
|
|
240
|
+
const skillPath = join(tempDir, ".agents", "skills", "fo-idea", "SKILL.md");
|
|
241
|
+
expect(existsSync(skillPath)).toBe(true);
|
|
242
|
+
const content = await readFile(skillPath, "utf8");
|
|
243
|
+
expect(content).toContain("fo-idea");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("RFC-0552: forge.upgrade returns skippedSkills field and syncs non-conflicting pack skills", async () => {
|
|
247
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
248
|
+
|
|
249
|
+
// Create a pack with prefix 'wg' containing a non-conflicting skill
|
|
250
|
+
await mkdir(join(tempDir, "packages", "wg-skills", "skills", "wg-custom"), { recursive: true });
|
|
251
|
+
await writeFile(
|
|
252
|
+
join(tempDir, "packages", "wg-skills", "skills", "wg-custom", "SKILL.md"),
|
|
253
|
+
"---\nname: wg-custom\n---\n# wg-custom (should be synced)\n",
|
|
254
|
+
"utf8",
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Write forge.yaml with skillPacks config and null syncedVersion
|
|
258
|
+
const yaml = `schema: forge/config@1
|
|
259
|
+
project:
|
|
260
|
+
name: consumer-project
|
|
261
|
+
stack: []
|
|
262
|
+
packageManager: pnpm
|
|
263
|
+
paths:
|
|
264
|
+
rfcsDir: docs/rfcs
|
|
265
|
+
adrsDir: docs/adrs
|
|
266
|
+
plansDir: docs/plans
|
|
267
|
+
auditsDir: docs/audits
|
|
268
|
+
specsDir: docs/specs
|
|
269
|
+
skillsDir: .agents/skills
|
|
270
|
+
sessionsDir: docs/sessions
|
|
271
|
+
skillPacks:
|
|
272
|
+
- prefix: wg
|
|
273
|
+
dir: packages/wg-skills/skills
|
|
274
|
+
bindings:
|
|
275
|
+
schema: forge/bindings@1
|
|
276
|
+
commands:
|
|
277
|
+
validateRfc: null
|
|
278
|
+
validateAdr: null
|
|
279
|
+
implementStamp: null
|
|
280
|
+
typecheck: null
|
|
281
|
+
test: null
|
|
282
|
+
scopedBuild: null
|
|
283
|
+
specValidate: null
|
|
284
|
+
paths:
|
|
285
|
+
invariantsFile: null
|
|
286
|
+
compassDocs: []
|
|
287
|
+
reviewsDir: null
|
|
288
|
+
handoffsDir: null
|
|
289
|
+
sessionsDir: null
|
|
290
|
+
`;
|
|
291
|
+
await writeFile(join(tempDir, "forge.yaml"), yaml, "utf8");
|
|
292
|
+
|
|
293
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
294
|
+
|
|
295
|
+
expect(result.data?.status).toBe("pass");
|
|
296
|
+
|
|
297
|
+
// skippedSkills field should exist and be empty (no conflicts)
|
|
298
|
+
expect(result.data?.skippedSkills).toBeDefined();
|
|
299
|
+
expect(result.data?.skippedSkills).toEqual([]);
|
|
300
|
+
|
|
301
|
+
// The non-conflicting pack skill should be synced
|
|
302
|
+
const wgSkillPath = join(tempDir, ".agents", "skills", "wg-custom", "SKILL.md");
|
|
303
|
+
expect(existsSync(wgSkillPath)).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
async function setupNpmConsumerForge(dir: string, version = "0.2.0"): Promise<void> {
|
|
307
|
+
const forgeSrcDir = join(dir, "node_modules", "@warpgogol", "forge");
|
|
308
|
+
await mkdir(join(forgeSrcDir, "skills", "fo", "fo-idea"), { recursive: true });
|
|
309
|
+
await writeFile(
|
|
310
|
+
join(forgeSrcDir, "skills", "fo", "fo-idea", "SKILL.md"),
|
|
311
|
+
"---\nname: fo-idea\n---\n# fo-idea\n",
|
|
312
|
+
"utf8",
|
|
313
|
+
);
|
|
314
|
+
await writeFile(
|
|
315
|
+
join(forgeSrcDir, "package.json"),
|
|
316
|
+
JSON.stringify({ name: "@warpgogol/forge", version }),
|
|
317
|
+
"utf8",
|
|
318
|
+
);
|
|
319
|
+
await mkdir(join(forgeSrcDir, "os", "rfc"), { recursive: true });
|
|
320
|
+
await writeFile(join(forgeSrcDir, "os", "rfc", "rfc-0000-template.md"), "# RFC Template", "utf8");
|
|
321
|
+
await mkdir(join(forgeSrcDir, "profiles"), { recursive: true });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
test("forge.upgrade --update-npm skips npm install in monorepo (packages/forge exists)", async () => {
|
|
325
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
326
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
327
|
+
|
|
328
|
+
const result = await runUpgrade(
|
|
329
|
+
{ argv: [], flags: { "update-npm": true } },
|
|
330
|
+
makeContext(tempDir, false),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
expect(result.data?.status).toBe("pass");
|
|
334
|
+
expect(result.data?.npmUpdated).toBe(false);
|
|
335
|
+
expect(result.data?.npmUpdateSkipped).toBe("monorepo (local package, not npm-installed)");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("forge.upgrade --update-npm --dry-run skips npm install (dry-run)", async () => {
|
|
339
|
+
await setupNpmConsumerForge(tempDir, "0.2.0");
|
|
340
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
341
|
+
|
|
342
|
+
const result = await runUpgrade(
|
|
343
|
+
{ argv: [], flags: { "update-npm": true, "dry-run": true } },
|
|
344
|
+
makeContext(tempDir, true),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
expect(result.data?.status).toBe("pass");
|
|
348
|
+
expect(result.data?.npmUpdated).toBe(false);
|
|
349
|
+
expect(result.data?.npmUpdateSkipped).toBe("dry-run");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("forge.upgrade --update-npm attempts npm install for npm consumer", async () => {
|
|
353
|
+
await setupNpmConsumerForge(tempDir, "0.2.0");
|
|
354
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
355
|
+
|
|
356
|
+
// Mock execSync to avoid real npm install
|
|
357
|
+
const execSyncMock = vi.fn(() => "");
|
|
358
|
+
vi.resetModules();
|
|
359
|
+
vi.doMock("node:child_process", () => ({
|
|
360
|
+
execSync: execSyncMock,
|
|
361
|
+
exec: vi.fn(),
|
|
362
|
+
execFile: vi.fn(),
|
|
363
|
+
spawn: vi.fn(),
|
|
364
|
+
fork: vi.fn(),
|
|
365
|
+
}));
|
|
366
|
+
|
|
367
|
+
// Re-import with mock
|
|
368
|
+
const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
|
|
369
|
+
|
|
370
|
+
const result = await runUpgradeMocked(
|
|
371
|
+
{ argv: [], flags: { "update-npm": true } },
|
|
372
|
+
makeContext(tempDir, false),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(result.data?.status).toBe("pass");
|
|
376
|
+
expect(execSyncMock).toHaveBeenCalled();
|
|
377
|
+
expect(result.data?.npmUpdated).toBe(true);
|
|
378
|
+
expect(result.data?.npmUpdateSkipped).toBe(null);
|
|
379
|
+
|
|
380
|
+
vi.doUnmock("node:child_process");
|
|
381
|
+
vi.resetModules();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("forge.upgrade --update-npm reports failure when npm install throws", async () => {
|
|
385
|
+
await setupNpmConsumerForge(tempDir, "0.2.0");
|
|
386
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
387
|
+
|
|
388
|
+
// Mock execSync to throw (simulates network error / registry unavailable)
|
|
389
|
+
const execSyncMock = vi.fn(() => {
|
|
390
|
+
throw new Error("network error");
|
|
391
|
+
});
|
|
392
|
+
vi.resetModules();
|
|
393
|
+
vi.doMock("node:child_process", () => ({
|
|
394
|
+
execSync: execSyncMock,
|
|
395
|
+
exec: vi.fn(),
|
|
396
|
+
execFile: vi.fn(),
|
|
397
|
+
spawn: vi.fn(),
|
|
398
|
+
fork: vi.fn(),
|
|
399
|
+
}));
|
|
400
|
+
|
|
401
|
+
const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
|
|
402
|
+
|
|
403
|
+
const result = await runUpgradeMocked(
|
|
404
|
+
{ argv: [], flags: { "update-npm": true } },
|
|
405
|
+
makeContext(tempDir, false),
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect(result.data?.status).toBe("pass");
|
|
409
|
+
expect(execSyncMock).toHaveBeenCalled();
|
|
410
|
+
expect(result.data?.npmUpdated).toBe(false);
|
|
411
|
+
expect(result.data?.npmUpdateSkipped).toBe(
|
|
412
|
+
"install failed (network error or registry unavailable)",
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
vi.doUnmock("node:child_process");
|
|
416
|
+
vi.resetModules();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("forge.upgrade without --update-npm does not attempt npm install", async () => {
|
|
420
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
421
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
422
|
+
|
|
423
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
424
|
+
|
|
425
|
+
expect(result.data?.status).toBe("pass");
|
|
426
|
+
expect(result.data?.npmUpdated).toBe(false);
|
|
427
|
+
expect(result.data?.npmUpdateSkipped).toBe(null);
|
|
428
|
+
});
|