@izkac/forgekit 0.3.12 → 0.3.14
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/bin/forge.mjs +119 -107
- package/bin/forgekit.mjs +83 -83
- package/bin/review.mjs +81 -81
- package/package.json +2 -1
- package/scripts/prepack.mjs +78 -78
- package/scripts/run-tests.mjs +50 -50
- package/src/adr.mjs +236 -236
- package/src/adr.test.mjs +170 -170
- package/src/change.mjs +327 -234
- package/src/change.test.mjs +145 -83
- package/src/cleanup-sessions.mjs +84 -84
- package/src/config.mjs +103 -103
- package/src/defer.mjs +75 -75
- package/src/doctor.mjs +350 -341
- package/src/doctor.test.mjs +114 -114
- package/src/fleet.test.mjs +50 -0
- package/src/init.mjs +680 -621
- package/src/install.mjs +815 -815
- package/src/install.test.mjs +180 -180
- package/src/integrity-check.mjs +60 -60
- package/src/integrity.mjs +688 -682
- package/src/integrity.test.mjs +612 -566
- package/src/lib/fleet.mjs +61 -4
- package/src/lib.mjs +160 -143
- package/src/lib.test.mjs +128 -0
- package/src/models.defaults.json +41 -41
- package/src/new-session.mjs +99 -99
- package/src/openspec-overlays/README.md +19 -19
- package/src/openspec-overlays/openspec-apply-change-footer.md +14 -14
- package/src/openspec-overlays/opsx-apply-completion-step.md +1 -1
- package/src/openspec-overlays/opsx-apply-implement-step.md +11 -11
- package/src/paths.mjs +92 -92
- package/src/plan-engine.mjs +321 -278
- package/src/plan-engine.test.mjs +447 -283
- package/src/preferences.defaults.json +78 -78
- package/src/preferences.mjs +438 -438
- package/src/preferences.test.mjs +174 -174
- package/src/record-evidence.mjs +204 -204
- package/src/repo-root.mjs +33 -0
- package/src/resolve-model.mjs +312 -312
- package/src/resolve-model.test.mjs +194 -194
- package/src/review/cli.test.mjs +117 -117
- package/src/review/export.mjs +172 -172
- package/src/review/export.test.mjs +197 -197
- package/src/review/fixtures/valid-review.json +42 -42
- package/src/review/lib.mjs +894 -894
- package/src/review/lib.test.mjs +266 -266
- package/src/review/schema.json +196 -196
- package/src/review/signals.test.mjs +62 -62
- package/src/score-cli.mjs +68 -68
- package/src/score.mjs +568 -568
- package/src/score.test.mjs +366 -366
- package/src/session-reminder.mjs +207 -207
- package/src/session-status.mjs +70 -70
- package/src/set-models.mjs +186 -186
- package/src/set-phase.mjs +205 -205
- package/src/set-prefs.mjs +294 -294
- package/src/specs-sync.mjs +232 -0
- package/src/specs-sync.test.mjs +114 -0
- package/src/spine.mjs +93 -93
- package/src/triage-prompt.mjs +175 -175
- package/src/triage-prompt.test.mjs +50 -50
- package/src/vendor-openspec-overlays.mjs +176 -176
- package/src/vendor-openspec-overlays.test.mjs +62 -62
- package/vendor/skills/archive-to-adr/SKILL.md +149 -149
- package/vendor/skills/forge/SKILL.md +136 -136
- package/vendor/skills/forge/docs/forge.md +650 -647
- package/vendor/skills/forge/phases/brainstorm.md +23 -23
- package/vendor/skills/forge/phases/finish.md +90 -87
- package/vendor/skills/forge/phases/implement.md +77 -77
- package/vendor/skills/forge/phases/plan-openspec.md +60 -60
- package/vendor/skills/forge/phases/plan-specs.md +163 -117
- package/vendor/skills/forge/phases/review.md +25 -25
- package/vendor/skills/forge/phases/verify.md +124 -124
- package/vendor/skills/forge/references/forge-layout.md +85 -85
- package/vendor/skills/forge/references/pace.md +115 -115
- package/vendor/skills/forge/references/plan-routing.md +52 -51
- package/vendor/skills/forge/references/runtime-integrity.md +232 -232
- package/vendor/skills/forge/references/substantial-work.md +37 -37
- package/vendor/skills/forge/references/test-evidence.md +30 -30
- package/vendor/skills/forge/references/test-strategy.md +68 -68
- package/vendor/skills/forge/skills/subagent-driven-development/SKILL.md +87 -87
- package/vendor/skills/forge/subagents/final-reviewer-prompt.md +56 -56
- package/vendor/skills/forge/subagents/implementer-prompt.md +38 -38
- package/vendor/skills/git-resolve-adr-conflict/SKILL.md +132 -132
- package/vendor/skills/thorough-code-review/SKILL.md +290 -290
- package/vendor/skills/thorough-code-review/examples.md +133 -133
- package/vendor/skills/thorough-code-review/reference/accepted-risks.md +26 -26
- package/vendor/skills/thorough-code-review/reference/lenses.md +96 -96
- package/vendor/skills/thorough-code-review/reference/phase1-scout.md +62 -62
- package/vendor/skills/thorough-code-review/reference/phase2-skeptic.md +105 -105
- package/vendor/skills/thorough-code-review/reference/report-schema.json +222 -222
- package/vendor/skills/thorough-code-review/reference/report-template.md +115 -115
- package/vendor/skills/thorough-code-review/reference/severity-rubric.md +49 -49
- package/vendor/skills/thorough-code-review/reference/signals-preflight.md +55 -55
- package/vendor/templates/adr/README.md +7 -7
- package/vendor/templates/adr/decisions.md +141 -141
- package/vendor/templates/adr/hooks/check-pending-adrs.mjs +74 -74
- package/vendor/templates/adr/hooks/check-pending-adrs.sh +3 -3
- package/vendor/templates/adr/hooks/openspec-archive-agent-message.mjs +52 -52
- package/vendor/templates/adr/hooks/openspec-archive-agent-message.sh +3 -3
- package/vendor/templates/project/claude/commands/forge-apply.md +75 -75
- package/vendor/templates/project/claude/commands/forge-brainstorm.md +7 -7
- package/vendor/templates/project/claude/commands/forge-build.md +17 -17
- package/vendor/templates/project/claude/commands/forge-plan.md +12 -12
- package/vendor/templates/project/claude/commands/forge-skip.md +14 -14
- package/vendor/templates/project/claude/commands/forge-status.md +16 -16
- package/vendor/templates/project/claude/commands/forge.md +16 -16
- package/vendor/templates/project/claude/hooks/forge-prompt-hook.mjs +73 -73
- package/vendor/templates/project/claude/hooks/forge-session-start.mjs +19 -19
- package/vendor/templates/project/claude/hooks/forge-triage-hook.mjs +77 -77
- package/vendor/templates/project/cursor/commands/forge-apply.md +75 -75
- package/vendor/templates/project/cursor/commands/forge-brainstorm.md +10 -10
- package/vendor/templates/project/cursor/commands/forge-build.md +17 -17
- package/vendor/templates/project/cursor/commands/forge-plan.md +15 -15
- package/vendor/templates/project/cursor/commands/forge-skip.md +14 -14
- package/vendor/templates/project/cursor/commands/forge-status.md +16 -16
- package/vendor/templates/project/cursor/commands/forge.md +16 -16
- package/vendor/templates/project/cursor/hooks/forge-session-start.mjs +30 -30
- package/vendor/templates/project/cursor/hooks/forge-session-start.sh +3 -3
package/src/doctor.test.mjs
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { Writable } from 'node:stream';
|
|
5
|
-
import {
|
|
6
|
-
OPENSPEC_INSTALL_CMD,
|
|
7
|
-
checkOpenSpecCli,
|
|
8
|
-
checkOpenSpecProject,
|
|
9
|
-
runDoctor,
|
|
10
|
-
runDoctorChecks,
|
|
11
|
-
warnIfDoctorFails,
|
|
12
|
-
} from './doctor.mjs';
|
|
13
|
-
|
|
14
|
-
function capture() {
|
|
15
|
-
let text = '';
|
|
16
|
-
const stream = new Writable({
|
|
17
|
-
write(chunk, _enc, cb) {
|
|
18
|
-
text += String(chunk);
|
|
19
|
-
cb();
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
return { stream, text: () => text };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test('checkOpenSpecProject ok when config exists', () => {
|
|
26
|
-
const result = checkOpenSpecProject({
|
|
27
|
-
cwd: '/repo',
|
|
28
|
-
existsSync: (p) => p.replace(/\\/g, '/') === '/repo/openspec/config.yaml',
|
|
29
|
-
});
|
|
30
|
-
assert.equal(result.ok, true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('checkOpenSpecProject fails when missing', () => {
|
|
34
|
-
const result = checkOpenSpecProject({
|
|
35
|
-
cwd: '/repo',
|
|
36
|
-
existsSync: () => false,
|
|
37
|
-
});
|
|
38
|
-
assert.equal(result.ok, false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('checkOpenSpecCli ok when version exits 0', () => {
|
|
42
|
-
const result = checkOpenSpecCli({
|
|
43
|
-
runCommand: () => ({ status: 0, stdout: '1.2.0\n', stderr: '' }),
|
|
44
|
-
});
|
|
45
|
-
assert.equal(result.ok, true);
|
|
46
|
-
assert.equal(result.version, '1.2.0');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('checkOpenSpecCli missing offers install', () => {
|
|
50
|
-
const result = checkOpenSpecCli({
|
|
51
|
-
runCommand: () => ({ status: 1, stdout: '', stderr: 'not found' }),
|
|
52
|
-
});
|
|
53
|
-
assert.equal(result.ok, false);
|
|
54
|
-
assert.match(result.message, /@fission-ai\/openspec/);
|
|
55
|
-
assert.equal(result.installCommand, OPENSPEC_INSTALL_CMD);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('runDoctorChecks aggregates', () => {
|
|
59
|
-
const report = runDoctorChecks({
|
|
60
|
-
cwd: '/repo',
|
|
61
|
-
existsSync: () => true,
|
|
62
|
-
runCommand: () => ({ status: 0, stdout: '1.0.0', stderr: '' }),
|
|
63
|
-
});
|
|
64
|
-
assert.equal(report.ok, true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('runDoctor --warn-only exits 0 on failure', () => {
|
|
68
|
-
const out = capture();
|
|
69
|
-
const err = capture();
|
|
70
|
-
const code = runDoctor(['--warn-only'], {
|
|
71
|
-
cwd: '/repo',
|
|
72
|
-
stdout: out.stream,
|
|
73
|
-
stderr: err.stream,
|
|
74
|
-
existsSync: () => true,
|
|
75
|
-
runCommand: () => ({ status: 1, stdout: '', stderr: 'missing' }),
|
|
76
|
-
});
|
|
77
|
-
assert.equal(code, 0);
|
|
78
|
-
assert.match(out.text(), /FAIL|not found|Install/i);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('runDoctor exits 1 when CLI missing', () => {
|
|
82
|
-
const out = capture();
|
|
83
|
-
const err = capture();
|
|
84
|
-
const code = runDoctor([], {
|
|
85
|
-
cwd: '/repo',
|
|
86
|
-
stdout: out.stream,
|
|
87
|
-
stderr: err.stream,
|
|
88
|
-
existsSync: () => true,
|
|
89
|
-
runCommand: () => ({ status: 1, stdout: '', stderr: 'missing' }),
|
|
90
|
-
});
|
|
91
|
-
assert.equal(code, 1);
|
|
92
|
-
assert.match(out.text(), /npm install -g @fission-ai\/openspec/);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('warnIfDoctorFails writes to stderr', () => {
|
|
96
|
-
const err = capture();
|
|
97
|
-
const report = warnIfDoctorFails({
|
|
98
|
-
cwd: '/repo',
|
|
99
|
-
stderr: err.stream,
|
|
100
|
-
existsSync: () => false,
|
|
101
|
-
runCommand: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
102
|
-
});
|
|
103
|
-
assert.equal(report.ok, false);
|
|
104
|
-
assert.match(err.text(), /forge:doctor/);
|
|
105
|
-
assert.match(err.text(), /install/i);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('path join uses openspec/config.yaml', () => {
|
|
109
|
-
const result = checkOpenSpecProject({
|
|
110
|
-
cwd: path.join('S:', 'Projects', 'janus'),
|
|
111
|
-
existsSync: (p) => p.endsWith(path.join('openspec', 'config.yaml')),
|
|
112
|
-
});
|
|
113
|
-
assert.equal(result.ok, true);
|
|
114
|
-
});
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Writable } from 'node:stream';
|
|
5
|
+
import {
|
|
6
|
+
OPENSPEC_INSTALL_CMD,
|
|
7
|
+
checkOpenSpecCli,
|
|
8
|
+
checkOpenSpecProject,
|
|
9
|
+
runDoctor,
|
|
10
|
+
runDoctorChecks,
|
|
11
|
+
warnIfDoctorFails,
|
|
12
|
+
} from './doctor.mjs';
|
|
13
|
+
|
|
14
|
+
function capture() {
|
|
15
|
+
let text = '';
|
|
16
|
+
const stream = new Writable({
|
|
17
|
+
write(chunk, _enc, cb) {
|
|
18
|
+
text += String(chunk);
|
|
19
|
+
cb();
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
return { stream, text: () => text };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('checkOpenSpecProject ok when config exists', () => {
|
|
26
|
+
const result = checkOpenSpecProject({
|
|
27
|
+
cwd: '/repo',
|
|
28
|
+
existsSync: (p) => p.replace(/\\/g, '/') === '/repo/openspec/config.yaml',
|
|
29
|
+
});
|
|
30
|
+
assert.equal(result.ok, true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('checkOpenSpecProject fails when missing', () => {
|
|
34
|
+
const result = checkOpenSpecProject({
|
|
35
|
+
cwd: '/repo',
|
|
36
|
+
existsSync: () => false,
|
|
37
|
+
});
|
|
38
|
+
assert.equal(result.ok, false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('checkOpenSpecCli ok when version exits 0', () => {
|
|
42
|
+
const result = checkOpenSpecCli({
|
|
43
|
+
runCommand: () => ({ status: 0, stdout: '1.2.0\n', stderr: '' }),
|
|
44
|
+
});
|
|
45
|
+
assert.equal(result.ok, true);
|
|
46
|
+
assert.equal(result.version, '1.2.0');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('checkOpenSpecCli missing offers install', () => {
|
|
50
|
+
const result = checkOpenSpecCli({
|
|
51
|
+
runCommand: () => ({ status: 1, stdout: '', stderr: 'not found' }),
|
|
52
|
+
});
|
|
53
|
+
assert.equal(result.ok, false);
|
|
54
|
+
assert.match(result.message, /@fission-ai\/openspec/);
|
|
55
|
+
assert.equal(result.installCommand, OPENSPEC_INSTALL_CMD);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('runDoctorChecks aggregates', () => {
|
|
59
|
+
const report = runDoctorChecks({
|
|
60
|
+
cwd: '/repo',
|
|
61
|
+
existsSync: () => true,
|
|
62
|
+
runCommand: () => ({ status: 0, stdout: '1.0.0', stderr: '' }),
|
|
63
|
+
});
|
|
64
|
+
assert.equal(report.ok, true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('runDoctor --warn-only exits 0 on failure', () => {
|
|
68
|
+
const out = capture();
|
|
69
|
+
const err = capture();
|
|
70
|
+
const code = runDoctor(['--warn-only'], {
|
|
71
|
+
cwd: '/repo',
|
|
72
|
+
stdout: out.stream,
|
|
73
|
+
stderr: err.stream,
|
|
74
|
+
existsSync: () => true,
|
|
75
|
+
runCommand: () => ({ status: 1, stdout: '', stderr: 'missing' }),
|
|
76
|
+
});
|
|
77
|
+
assert.equal(code, 0);
|
|
78
|
+
assert.match(out.text(), /FAIL|not found|Install/i);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('runDoctor exits 1 when CLI missing', () => {
|
|
82
|
+
const out = capture();
|
|
83
|
+
const err = capture();
|
|
84
|
+
const code = runDoctor([], {
|
|
85
|
+
cwd: '/repo',
|
|
86
|
+
stdout: out.stream,
|
|
87
|
+
stderr: err.stream,
|
|
88
|
+
existsSync: () => true,
|
|
89
|
+
runCommand: () => ({ status: 1, stdout: '', stderr: 'missing' }),
|
|
90
|
+
});
|
|
91
|
+
assert.equal(code, 1);
|
|
92
|
+
assert.match(out.text(), /npm install -g @fission-ai\/openspec/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('warnIfDoctorFails writes to stderr', () => {
|
|
96
|
+
const err = capture();
|
|
97
|
+
const report = warnIfDoctorFails({
|
|
98
|
+
cwd: '/repo',
|
|
99
|
+
stderr: err.stream,
|
|
100
|
+
existsSync: () => false,
|
|
101
|
+
runCommand: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
102
|
+
});
|
|
103
|
+
assert.equal(report.ok, false);
|
|
104
|
+
assert.match(err.text(), /forge:doctor/);
|
|
105
|
+
assert.match(err.text(), /install/i);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('path join uses openspec/config.yaml', () => {
|
|
109
|
+
const result = checkOpenSpecProject({
|
|
110
|
+
cwd: path.join('S:', 'Projects', 'janus'),
|
|
111
|
+
existsSync: (p) => p.endsWith(path.join('openspec', 'config.yaml')),
|
|
112
|
+
});
|
|
113
|
+
assert.equal(result.ok, true);
|
|
114
|
+
});
|
package/src/fleet.test.mjs
CHANGED
|
@@ -80,6 +80,56 @@ test('listFleet self-heals entries whose session dir is gone', () => {
|
|
|
80
80
|
assert.equal(fs.existsSync(entryFile(project, 'gone')), false);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
test('listFleet reconciles a stale entry against session.json on disk', () => {
|
|
84
|
+
// Regression: the registry is a cache, not a source of truth. A session
|
|
85
|
+
// whose phase advanced without a mirroring write (older CLI, a crash, a
|
|
86
|
+
// hand-edited record) showed its first-registered phase forever — helm's
|
|
87
|
+
// phase-0 sat at `brainstorm` in `forge fleet list` after reaching `done`.
|
|
88
|
+
process.env.FORGEKIT_FLEET_DIR = path.join(tmp('fleet-reconcile-'), 'sessions');
|
|
89
|
+
const project = tmp('fleet-proj-');
|
|
90
|
+
const sessionDir = makeProject(project, 's5');
|
|
91
|
+
|
|
92
|
+
registerSession(project, makeSession('s5', { phase: 'brainstorm', tasksComplete: 0 }));
|
|
93
|
+
// Disk moves on without the registry.
|
|
94
|
+
const later = new Date(Date.now() + 60_000).toISOString();
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(sessionDir, 'session.json'),
|
|
97
|
+
`${JSON.stringify(
|
|
98
|
+
makeSession('s5', {
|
|
99
|
+
phase: 'done',
|
|
100
|
+
tasksTotal: 20,
|
|
101
|
+
tasksComplete: 20,
|
|
102
|
+
openspecChange: 'phase-0',
|
|
103
|
+
updatedAt: later,
|
|
104
|
+
}),
|
|
105
|
+
)}\n`,
|
|
106
|
+
'utf8',
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const [entry] = listFleet();
|
|
110
|
+
assert.equal(entry.phase, 'done');
|
|
111
|
+
assert.equal(entry.tasksComplete, 20);
|
|
112
|
+
assert.equal(entry.tasksTotal, 20);
|
|
113
|
+
assert.equal(entry.openspecChange, 'phase-0');
|
|
114
|
+
assert.equal(entry.updatedAt, later);
|
|
115
|
+
// Reconciliation is persisted, so the next read is already correct.
|
|
116
|
+
assert.equal(JSON.parse(fs.readFileSync(entryFile(project, 's5'), 'utf8')).phase, 'done');
|
|
117
|
+
// lastSeen is a heartbeat, not a session field — reconciling must not forge one.
|
|
118
|
+
assert.ok(entry.lastSeen <= new Date().toISOString());
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('listFleet keeps registry-only fields when session.json is unreadable', () => {
|
|
122
|
+
process.env.FORGEKIT_FLEET_DIR = path.join(tmp('fleet-unreadable-'), 'sessions');
|
|
123
|
+
const project = tmp('fleet-proj-');
|
|
124
|
+
const sessionDir = makeProject(project, 's6');
|
|
125
|
+
registerSession(project, makeSession('s6', { phase: 'verify' }));
|
|
126
|
+
fs.writeFileSync(path.join(sessionDir, 'session.json'), '{ not json', 'utf8');
|
|
127
|
+
|
|
128
|
+
const [entry] = listFleet();
|
|
129
|
+
assert.equal(entry.phase, 'verify');
|
|
130
|
+
assert.equal(entry.missing, false);
|
|
131
|
+
});
|
|
132
|
+
|
|
83
133
|
test('saveSession mirrors into the fleet registry', () => {
|
|
84
134
|
process.env.FORGEKIT_FLEET_DIR = path.join(tmp('fleet-mirror-'), 'sessions');
|
|
85
135
|
const project = tmp('fleet-proj-');
|