@izkac/forgekit 0.3.11 → 0.3.13
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 +107 -107
- package/bin/forgekit.mjs +83 -83
- package/bin/review.mjs +81 -81
- package/package.json +1 -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/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 +682 -682
- package/src/integrity.test.mjs +566 -566
- package/src/lib.mjs +143 -143
- 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/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 -566
- package/src/score.test.mjs +366 -340
- 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 +234 -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 -225
- 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/lib.mjs
CHANGED
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Shared helpers for Forge session management.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import crypto from 'node:crypto';
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { registerSession } from './lib/fleet.mjs';
|
|
10
|
-
|
|
11
|
-
export const REPO_ROOT = process.cwd();
|
|
12
|
-
export const FORGE_DIR = path.join(REPO_ROOT, '.forge');
|
|
13
|
-
export const SESSIONS_DIR = path.join(FORGE_DIR, 'sessions');
|
|
14
|
-
export const ACTIVE_FILE = path.join(FORGE_DIR, 'active.json');
|
|
15
|
-
export const RETENTION_DAYS = 14;
|
|
16
|
-
|
|
17
|
-
export function ensureForgeLayout() {
|
|
18
|
-
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function slugify(input) {
|
|
22
|
-
return String(input)
|
|
23
|
-
.toLowerCase()
|
|
24
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
25
|
-
.replace(/^-+|-+$/g, '')
|
|
26
|
-
.slice(0, 48) || 'session';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function utcCompactNow() {
|
|
30
|
-
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function randomSuffix(bytes = 3) {
|
|
34
|
-
return crypto.randomBytes(bytes).toString('hex');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function makeSessionId(slug) {
|
|
38
|
-
return `${utcCompactNow()}-${slugify(slug)}-${randomSuffix()}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function sessionPath(sessionId) {
|
|
42
|
-
return path.join(SESSIONS_DIR, sessionId);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function readJson(filePath) {
|
|
46
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function writeJson(filePath, data) {
|
|
50
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
-
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function readActive() {
|
|
55
|
-
if (!fs.existsSync(ACTIVE_FILE)) return null;
|
|
56
|
-
try {
|
|
57
|
-
return readJson(ACTIVE_FILE);
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function writeActive(sessionId) {
|
|
64
|
-
writeJson(ACTIVE_FILE, {
|
|
65
|
-
sessionId,
|
|
66
|
-
sessionPath: path.relative(REPO_ROOT, sessionPath(sessionId)).replace(/\\/g, '/'),
|
|
67
|
-
updatedAt: new Date().toISOString(),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function clearActive() {
|
|
72
|
-
if (fs.existsSync(ACTIVE_FILE)) fs.unlinkSync(ACTIVE_FILE);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function defaultSession(sessionId, slug) {
|
|
76
|
-
const now = new Date().toISOString();
|
|
77
|
-
return {
|
|
78
|
-
id: sessionId,
|
|
79
|
-
slug: slugify(slug),
|
|
80
|
-
createdAt: now,
|
|
81
|
-
updatedAt: now,
|
|
82
|
-
phase: 'triage',
|
|
83
|
-
planType: null,
|
|
84
|
-
openspecChange: null,
|
|
85
|
-
forgeSkipped: false,
|
|
86
|
-
cursorChatId: null,
|
|
87
|
-
tasksTotal: 0,
|
|
88
|
-
tasksComplete: 0,
|
|
89
|
-
/** Requested pace: auto | thorough | standard | brisk | lite */
|
|
90
|
-
pace: 'auto',
|
|
91
|
-
/** Concrete pace after auto resolve or explicit pin */
|
|
92
|
-
resolvedPace: null,
|
|
93
|
-
paceReason: null,
|
|
94
|
-
paceSignal: null,
|
|
95
|
-
pacePinned: false,
|
|
96
|
-
preferencesOverride: null,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function defaultStatus(session) {
|
|
101
|
-
return {
|
|
102
|
-
sessionId: session.id,
|
|
103
|
-
phase: session.phase,
|
|
104
|
-
planType: session.planType,
|
|
105
|
-
openspecChange: session.openspecChange,
|
|
106
|
-
tasksTotal: session.tasksTotal,
|
|
107
|
-
tasksComplete: session.tasksComplete,
|
|
108
|
-
pace: session.pace ?? null,
|
|
109
|
-
resolvedPace: session.resolvedPace ?? null,
|
|
110
|
-
paceReason: session.paceReason ?? null,
|
|
111
|
-
updatedAt: session.updatedAt,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function scaffoldSessionDirs(dir) {
|
|
116
|
-
for (const sub of ['brainstorm', 'tasks', 'reviews']) {
|
|
117
|
-
fs.mkdirSync(path.join(dir, sub), { recursive: true });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function loadSession(sessionId) {
|
|
122
|
-
const dir = sessionPath(sessionId);
|
|
123
|
-
const file = path.join(dir, 'session.json');
|
|
124
|
-
if (!fs.existsSync(file)) {
|
|
125
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
126
|
-
}
|
|
127
|
-
return { dir, session: readJson(file) };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function saveSession(dir, session) {
|
|
131
|
-
session.updatedAt = new Date().toISOString();
|
|
132
|
-
writeJson(path.join(dir, 'session.json'), session);
|
|
133
|
-
writeJson(path.join(dir, 'status.json'), defaultStatus(session));
|
|
134
|
-
// Mirror into ~/.forgekit/fleet so `forge fleet` sees every project's
|
|
135
|
-
// sessions. Project root derived from dir (<root>/.forge/sessions/<id>),
|
|
136
|
-
// not REPO_ROOT, so callers with explicit dirs mirror correctly too.
|
|
137
|
-
registerSession(path.resolve(dir, '..', '..', '..'), session);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function sessionAgeDays(session) {
|
|
141
|
-
const created = new Date(session.createdAt).getTime();
|
|
142
|
-
return (Date.now() - created) / (1000 * 60 * 60 * 24);
|
|
143
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared helpers for Forge session management.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { registerSession } from './lib/fleet.mjs';
|
|
10
|
+
|
|
11
|
+
export const REPO_ROOT = process.cwd();
|
|
12
|
+
export const FORGE_DIR = path.join(REPO_ROOT, '.forge');
|
|
13
|
+
export const SESSIONS_DIR = path.join(FORGE_DIR, 'sessions');
|
|
14
|
+
export const ACTIVE_FILE = path.join(FORGE_DIR, 'active.json');
|
|
15
|
+
export const RETENTION_DAYS = 14;
|
|
16
|
+
|
|
17
|
+
export function ensureForgeLayout() {
|
|
18
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function slugify(input) {
|
|
22
|
+
return String(input)
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
25
|
+
.replace(/^-+|-+$/g, '')
|
|
26
|
+
.slice(0, 48) || 'session';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function utcCompactNow() {
|
|
30
|
+
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function randomSuffix(bytes = 3) {
|
|
34
|
+
return crypto.randomBytes(bytes).toString('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function makeSessionId(slug) {
|
|
38
|
+
return `${utcCompactNow()}-${slugify(slug)}-${randomSuffix()}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sessionPath(sessionId) {
|
|
42
|
+
return path.join(SESSIONS_DIR, sessionId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readJson(filePath) {
|
|
46
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function writeJson(filePath, data) {
|
|
50
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readActive() {
|
|
55
|
+
if (!fs.existsSync(ACTIVE_FILE)) return null;
|
|
56
|
+
try {
|
|
57
|
+
return readJson(ACTIVE_FILE);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function writeActive(sessionId) {
|
|
64
|
+
writeJson(ACTIVE_FILE, {
|
|
65
|
+
sessionId,
|
|
66
|
+
sessionPath: path.relative(REPO_ROOT, sessionPath(sessionId)).replace(/\\/g, '/'),
|
|
67
|
+
updatedAt: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function clearActive() {
|
|
72
|
+
if (fs.existsSync(ACTIVE_FILE)) fs.unlinkSync(ACTIVE_FILE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function defaultSession(sessionId, slug) {
|
|
76
|
+
const now = new Date().toISOString();
|
|
77
|
+
return {
|
|
78
|
+
id: sessionId,
|
|
79
|
+
slug: slugify(slug),
|
|
80
|
+
createdAt: now,
|
|
81
|
+
updatedAt: now,
|
|
82
|
+
phase: 'triage',
|
|
83
|
+
planType: null,
|
|
84
|
+
openspecChange: null,
|
|
85
|
+
forgeSkipped: false,
|
|
86
|
+
cursorChatId: null,
|
|
87
|
+
tasksTotal: 0,
|
|
88
|
+
tasksComplete: 0,
|
|
89
|
+
/** Requested pace: auto | thorough | standard | brisk | lite */
|
|
90
|
+
pace: 'auto',
|
|
91
|
+
/** Concrete pace after auto resolve or explicit pin */
|
|
92
|
+
resolvedPace: null,
|
|
93
|
+
paceReason: null,
|
|
94
|
+
paceSignal: null,
|
|
95
|
+
pacePinned: false,
|
|
96
|
+
preferencesOverride: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function defaultStatus(session) {
|
|
101
|
+
return {
|
|
102
|
+
sessionId: session.id,
|
|
103
|
+
phase: session.phase,
|
|
104
|
+
planType: session.planType,
|
|
105
|
+
openspecChange: session.openspecChange,
|
|
106
|
+
tasksTotal: session.tasksTotal,
|
|
107
|
+
tasksComplete: session.tasksComplete,
|
|
108
|
+
pace: session.pace ?? null,
|
|
109
|
+
resolvedPace: session.resolvedPace ?? null,
|
|
110
|
+
paceReason: session.paceReason ?? null,
|
|
111
|
+
updatedAt: session.updatedAt,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function scaffoldSessionDirs(dir) {
|
|
116
|
+
for (const sub of ['brainstorm', 'tasks', 'reviews']) {
|
|
117
|
+
fs.mkdirSync(path.join(dir, sub), { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function loadSession(sessionId) {
|
|
122
|
+
const dir = sessionPath(sessionId);
|
|
123
|
+
const file = path.join(dir, 'session.json');
|
|
124
|
+
if (!fs.existsSync(file)) {
|
|
125
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
126
|
+
}
|
|
127
|
+
return { dir, session: readJson(file) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function saveSession(dir, session) {
|
|
131
|
+
session.updatedAt = new Date().toISOString();
|
|
132
|
+
writeJson(path.join(dir, 'session.json'), session);
|
|
133
|
+
writeJson(path.join(dir, 'status.json'), defaultStatus(session));
|
|
134
|
+
// Mirror into ~/.forgekit/fleet so `forge fleet` sees every project's
|
|
135
|
+
// sessions. Project root derived from dir (<root>/.forge/sessions/<id>),
|
|
136
|
+
// not REPO_ROOT, so callers with explicit dirs mirror correctly too.
|
|
137
|
+
registerSession(path.resolve(dir, '..', '..', '..'), session);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function sessionAgeDays(session) {
|
|
141
|
+
const created = new Date(session.createdAt).getTime();
|
|
142
|
+
return (Date.now() - created) / (1000 * 60 * 60 * 24);
|
|
143
|
+
}
|
package/src/models.defaults.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"billing": "included",
|
|
3
|
-
"agents": {
|
|
4
|
-
"cursor": {
|
|
5
|
-
"included": {
|
|
6
|
-
"fast": "inherit",
|
|
7
|
-
"standard": "inherit",
|
|
8
|
-
"capable": "inherit"
|
|
9
|
-
},
|
|
10
|
-
"metered": {
|
|
11
|
-
"fast": "gpt-5.6-terra-medium",
|
|
12
|
-
"standard": "claude-sonnet-5-thinking-high",
|
|
13
|
-
"capable": "claude-opus-4-8-thinking-high"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"claude-code": {
|
|
17
|
-
"included": {
|
|
18
|
-
"fast": "haiku",
|
|
19
|
-
"standard": "sonnet",
|
|
20
|
-
"capable": "opus"
|
|
21
|
-
},
|
|
22
|
-
"metered": {
|
|
23
|
-
"fast": "haiku",
|
|
24
|
-
"standard": "sonnet",
|
|
25
|
-
"capable": "opus"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"codex": {
|
|
29
|
-
"included": {
|
|
30
|
-
"fast": "inherit",
|
|
31
|
-
"standard": "inherit",
|
|
32
|
-
"capable": "inherit"
|
|
33
|
-
},
|
|
34
|
-
"metered": {
|
|
35
|
-
"fast": "inherit",
|
|
36
|
-
"standard": "inherit",
|
|
37
|
-
"capable": "inherit"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"billing": "included",
|
|
3
|
+
"agents": {
|
|
4
|
+
"cursor": {
|
|
5
|
+
"included": {
|
|
6
|
+
"fast": "inherit",
|
|
7
|
+
"standard": "inherit",
|
|
8
|
+
"capable": "inherit"
|
|
9
|
+
},
|
|
10
|
+
"metered": {
|
|
11
|
+
"fast": "gpt-5.6-terra-medium",
|
|
12
|
+
"standard": "claude-sonnet-5-thinking-high",
|
|
13
|
+
"capable": "claude-opus-4-8-thinking-high"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"claude-code": {
|
|
17
|
+
"included": {
|
|
18
|
+
"fast": "haiku",
|
|
19
|
+
"standard": "sonnet",
|
|
20
|
+
"capable": "opus"
|
|
21
|
+
},
|
|
22
|
+
"metered": {
|
|
23
|
+
"fast": "haiku",
|
|
24
|
+
"standard": "sonnet",
|
|
25
|
+
"capable": "opus"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"codex": {
|
|
29
|
+
"included": {
|
|
30
|
+
"fast": "inherit",
|
|
31
|
+
"standard": "inherit",
|
|
32
|
+
"capable": "inherit"
|
|
33
|
+
},
|
|
34
|
+
"metered": {
|
|
35
|
+
"fast": "inherit",
|
|
36
|
+
"standard": "inherit",
|
|
37
|
+
"capable": "inherit"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/new-session.mjs
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Create a new Forge session under .forge/sessions/ and set it active.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* forge new mercury-console-validation
|
|
7
|
-
* forge new <slug> [--chat-id <id>] [--signal <text>]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
defaultSession,
|
|
12
|
-
defaultStatus,
|
|
13
|
-
ensureForgeLayout,
|
|
14
|
-
FORGE_DIR,
|
|
15
|
-
makeSessionId,
|
|
16
|
-
saveSession,
|
|
17
|
-
scaffoldSessionDirs,
|
|
18
|
-
sessionPath,
|
|
19
|
-
writeActive,
|
|
20
|
-
} from './lib.mjs';
|
|
21
|
-
import { resolveSessionPaceFields } from './preferences.mjs';
|
|
22
|
-
import { warnIfDoctorFails } from './doctor.mjs';
|
|
23
|
-
import { liveOverlaps, queueMessage, sessionDirFor } from './lib/fleet.mjs';
|
|
24
|
-
|
|
25
|
-
function usage() {
|
|
26
|
-
process.stderr.write(
|
|
27
|
-
'Usage: forge new <slug> [--chat-id <id>] [--signal <text>]\n',
|
|
28
|
-
);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const args = process.argv.slice(2);
|
|
33
|
-
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') usage();
|
|
34
|
-
|
|
35
|
-
const slug = args[0];
|
|
36
|
-
let cursorChatId = null;
|
|
37
|
-
let signalText = null;
|
|
38
|
-
for (let i = 1; i < args.length; i += 1) {
|
|
39
|
-
if (args[i] === '--chat-id' && args[i + 1]) {
|
|
40
|
-
cursorChatId = args[i + 1];
|
|
41
|
-
i += 1;
|
|
42
|
-
} else if (args[i] === '--signal' && args[i + 1]) {
|
|
43
|
-
signalText = args[i + 1];
|
|
44
|
-
i += 1;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
ensureForgeLayout();
|
|
49
|
-
warnIfDoctorFails({ cwd: process.cwd() });
|
|
50
|
-
|
|
51
|
-
const sessionId = makeSessionId(slug);
|
|
52
|
-
const dir = sessionPath(sessionId);
|
|
53
|
-
scaffoldSessionDirs(dir);
|
|
54
|
-
|
|
55
|
-
const session = defaultSession(sessionId, slug);
|
|
56
|
-
if (cursorChatId) session.cursorChatId = cursorChatId;
|
|
57
|
-
|
|
58
|
-
const paceFields = resolveSessionPaceFields({
|
|
59
|
-
forgeDir: FORGE_DIR,
|
|
60
|
-
slug: session.slug,
|
|
61
|
-
signalText: signalText || session.slug,
|
|
62
|
-
});
|
|
63
|
-
Object.assign(session, paceFields);
|
|
64
|
-
|
|
65
|
-
saveSession(dir, session);
|
|
66
|
-
writeActive(sessionId);
|
|
67
|
-
|
|
68
|
-
// Fleet coordination: another live session in this working tree risks
|
|
69
|
-
// conflicting edits — surface it here and notify the other sessions' inboxes.
|
|
70
|
-
const overlaps = liveOverlaps(process.cwd(), sessionId);
|
|
71
|
-
for (const o of overlaps) {
|
|
72
|
-
queueMessage(
|
|
73
|
-
sessionDirFor(o),
|
|
74
|
-
`Fleet overlap: session "${session.slug}" (${sessionId}) just started in this project. Coordinate with the user to avoid conflicting edits.`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const out = {
|
|
79
|
-
sessionId,
|
|
80
|
-
dir,
|
|
81
|
-
session: defaultStatus(session),
|
|
82
|
-
pace: {
|
|
83
|
-
requested: session.pace,
|
|
84
|
-
resolved: session.resolvedPace,
|
|
85
|
-
reason: session.paceReason,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
if (overlaps.length > 0) {
|
|
89
|
-
out.overlaps = overlaps.map((o) => ({
|
|
90
|
-
sessionId: o.sessionId,
|
|
91
|
-
slug: o.slug,
|
|
92
|
-
phase: o.phase,
|
|
93
|
-
engine: o.engine,
|
|
94
|
-
lastSeen: o.lastSeen ?? o.updatedAt,
|
|
95
|
-
}));
|
|
96
|
-
out.overlapAdvice =
|
|
97
|
-
'Other live sessions are working in this project. Tell the user and ask: continue anyway, use a git worktree, or pause one session.';
|
|
98
|
-
}
|
|
99
|
-
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Create a new Forge session under .forge/sessions/ and set it active.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* forge new mercury-console-validation
|
|
7
|
+
* forge new <slug> [--chat-id <id>] [--signal <text>]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defaultSession,
|
|
12
|
+
defaultStatus,
|
|
13
|
+
ensureForgeLayout,
|
|
14
|
+
FORGE_DIR,
|
|
15
|
+
makeSessionId,
|
|
16
|
+
saveSession,
|
|
17
|
+
scaffoldSessionDirs,
|
|
18
|
+
sessionPath,
|
|
19
|
+
writeActive,
|
|
20
|
+
} from './lib.mjs';
|
|
21
|
+
import { resolveSessionPaceFields } from './preferences.mjs';
|
|
22
|
+
import { warnIfDoctorFails } from './doctor.mjs';
|
|
23
|
+
import { liveOverlaps, queueMessage, sessionDirFor } from './lib/fleet.mjs';
|
|
24
|
+
|
|
25
|
+
function usage() {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
'Usage: forge new <slug> [--chat-id <id>] [--signal <text>]\n',
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') usage();
|
|
34
|
+
|
|
35
|
+
const slug = args[0];
|
|
36
|
+
let cursorChatId = null;
|
|
37
|
+
let signalText = null;
|
|
38
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
39
|
+
if (args[i] === '--chat-id' && args[i + 1]) {
|
|
40
|
+
cursorChatId = args[i + 1];
|
|
41
|
+
i += 1;
|
|
42
|
+
} else if (args[i] === '--signal' && args[i + 1]) {
|
|
43
|
+
signalText = args[i + 1];
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ensureForgeLayout();
|
|
49
|
+
warnIfDoctorFails({ cwd: process.cwd() });
|
|
50
|
+
|
|
51
|
+
const sessionId = makeSessionId(slug);
|
|
52
|
+
const dir = sessionPath(sessionId);
|
|
53
|
+
scaffoldSessionDirs(dir);
|
|
54
|
+
|
|
55
|
+
const session = defaultSession(sessionId, slug);
|
|
56
|
+
if (cursorChatId) session.cursorChatId = cursorChatId;
|
|
57
|
+
|
|
58
|
+
const paceFields = resolveSessionPaceFields({
|
|
59
|
+
forgeDir: FORGE_DIR,
|
|
60
|
+
slug: session.slug,
|
|
61
|
+
signalText: signalText || session.slug,
|
|
62
|
+
});
|
|
63
|
+
Object.assign(session, paceFields);
|
|
64
|
+
|
|
65
|
+
saveSession(dir, session);
|
|
66
|
+
writeActive(sessionId);
|
|
67
|
+
|
|
68
|
+
// Fleet coordination: another live session in this working tree risks
|
|
69
|
+
// conflicting edits — surface it here and notify the other sessions' inboxes.
|
|
70
|
+
const overlaps = liveOverlaps(process.cwd(), sessionId);
|
|
71
|
+
for (const o of overlaps) {
|
|
72
|
+
queueMessage(
|
|
73
|
+
sessionDirFor(o),
|
|
74
|
+
`Fleet overlap: session "${session.slug}" (${sessionId}) just started in this project. Coordinate with the user to avoid conflicting edits.`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const out = {
|
|
79
|
+
sessionId,
|
|
80
|
+
dir,
|
|
81
|
+
session: defaultStatus(session),
|
|
82
|
+
pace: {
|
|
83
|
+
requested: session.pace,
|
|
84
|
+
resolved: session.resolvedPace,
|
|
85
|
+
reason: session.paceReason,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
if (overlaps.length > 0) {
|
|
89
|
+
out.overlaps = overlaps.map((o) => ({
|
|
90
|
+
sessionId: o.sessionId,
|
|
91
|
+
slug: o.slug,
|
|
92
|
+
phase: o.phase,
|
|
93
|
+
engine: o.engine,
|
|
94
|
+
lastSeen: o.lastSeen ?? o.updatedAt,
|
|
95
|
+
}));
|
|
96
|
+
out.overlapAdvice =
|
|
97
|
+
'Other live sessions are working in this project. Tell the user and ask: continue anyway, use a git worktree, or pause one session.';
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# OpenSpec Forge overlays
|
|
2
|
-
|
|
3
|
-
Vendor OpenSpec skills and `/opsx:*` commands are upgraded in place. **Do not hand-edit** them.
|
|
4
|
-
|
|
5
|
-
Forge adaptations live here and are applied by:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
forge overlay
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Run after refreshing OpenSpec skills/commands from upstream (usually via `forge init --overlay`).
|
|
12
|
-
|
|
13
|
-
| Overlay | Targets |
|
|
14
|
-
| ------- | ------- |
|
|
15
|
-
| `opsx-apply-implement-step.md` | Step 6 in `opsx-apply` / `opsx/apply` commands |
|
|
16
|
-
| `opsx-apply-completion-step.md` | Step 7 “all done” bullet in same commands |
|
|
17
|
-
| `openspec-apply-change-footer.md` | Footer on `openspec-apply-change/SKILL.md` (all agents) |
|
|
18
|
-
|
|
19
|
-
**Forge-owned (never overwritten):** `/forge:apply` command, `forge/` skill tree.
|
|
1
|
+
# OpenSpec Forge overlays
|
|
2
|
+
|
|
3
|
+
Vendor OpenSpec skills and `/opsx:*` commands are upgraded in place. **Do not hand-edit** them.
|
|
4
|
+
|
|
5
|
+
Forge adaptations live here and are applied by:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
forge overlay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run after refreshing OpenSpec skills/commands from upstream (usually via `forge init --overlay`).
|
|
12
|
+
|
|
13
|
+
| Overlay | Targets |
|
|
14
|
+
| ------- | ------- |
|
|
15
|
+
| `opsx-apply-implement-step.md` | Step 6 in `opsx-apply` / `opsx/apply` commands |
|
|
16
|
+
| `opsx-apply-completion-step.md` | Step 7 “all done” bullet in same commands |
|
|
17
|
+
| `openspec-apply-change-footer.md` | Footer on `openspec-apply-change/SKILL.md` (all agents) |
|
|
18
|
+
|
|
19
|
+
**Forge-owned (never overwritten):** `/forge:apply` command, `forge/` skill tree.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
---
|
|
3
|
-
|
|
4
|
-
<!-- forgekit:openspec-overlay:start -->
|
|
5
|
-
|
|
6
|
-
## Forge overlay (re-applied by `forge overlay`)
|
|
7
|
-
|
|
8
|
-
**Do not implement tasks inline in coordinator context.**
|
|
9
|
-
|
|
10
|
-
1. Prefer **`/forge:apply`** for OpenSpec implement + verify + review.
|
|
11
|
-
2. Otherwise follow vendor steps 1–5 above, then **`{{PHASES_IMPLEMENT}}`** for step 6+.
|
|
12
|
-
3. Re-apply this overlay after upgrading OpenSpec skills: `forge overlay`
|
|
13
|
-
|
|
14
|
-
<!-- forgekit:openspec-overlay:end -->
|
|
1
|
+
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
<!-- forgekit:openspec-overlay:start -->
|
|
5
|
+
|
|
6
|
+
## Forge overlay (re-applied by `forge overlay`)
|
|
7
|
+
|
|
8
|
+
**Do not implement tasks inline in coordinator context.**
|
|
9
|
+
|
|
10
|
+
1. Prefer **`/forge:apply`** for OpenSpec implement + verify + review.
|
|
11
|
+
2. Otherwise follow vendor steps 1–5 above, then **`{{PHASES_IMPLEMENT}}`** for step 6+.
|
|
12
|
+
3. Re-apply this overlay after upgrading OpenSpec skills: `forge overlay`
|
|
13
|
+
|
|
14
|
+
<!-- forgekit:openspec-overlay:end -->
|
|
@@ -1 +1 @@
|
|
|
1
|
-
- If all done: proceed to Forge [verify]({{PHASES_VERIFY}}) and [review]({{PHASES_REVIEW}}) when in a Forge session (or user invoked `/forge:apply`), then suggest `/opsx:archive`
|
|
1
|
+
- If all done: proceed to Forge [verify]({{PHASES_VERIFY}}) and [review]({{PHASES_REVIEW}}) when in a Forge session (or user invoked `/forge:apply`), then suggest `/opsx:archive`
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
**REQUIRED (Forge):** Wrap this loop in the implement phase —
|
|
2
|
-
`{{PHASES_IMPLEMENT}}`.
|
|
3
|
-
Use bundled `skills/subagent-driven-development` + `skills/test-driven-development` per task.
|
|
4
|
-
Prefer **`/forge:apply`** over bare `/opsx:apply` — same OpenSpec CLI steps plus verify and review.
|
|
5
|
-
|
|
6
|
-
For each pending task:
|
|
7
|
-
- Show which task is being worked on
|
|
8
|
-
- Dispatch **implementer** subagent (TDD first), then **spec** + **quality** reviewers
|
|
9
|
-
- Keep changes minimal and focused
|
|
10
|
-
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
|
11
|
-
- Continue to next task
|
|
1
|
+
**REQUIRED (Forge):** Wrap this loop in the implement phase —
|
|
2
|
+
`{{PHASES_IMPLEMENT}}`.
|
|
3
|
+
Use bundled `skills/subagent-driven-development` + `skills/test-driven-development` per task.
|
|
4
|
+
Prefer **`/forge:apply`** over bare `/opsx:apply` — same OpenSpec CLI steps plus verify and review.
|
|
5
|
+
|
|
6
|
+
For each pending task:
|
|
7
|
+
- Show which task is being worked on
|
|
8
|
+
- Dispatch **implementer** subagent (TDD first), then **spec** + **quality** reviewers
|
|
9
|
+
- Keep changes minimal and focused
|
|
10
|
+
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
|
11
|
+
- Continue to next task
|