gsdd-cli 0.18.0 → 0.18.1
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/README.md +7 -6
- package/bin/gsdd.mjs +17 -14
- package/bin/lib/health-truth.mjs +8 -8
- package/bin/lib/health.mjs +29 -26
- package/bin/lib/init-flow.mjs +91 -34
- package/bin/lib/init-runtime.mjs +5 -4
- package/bin/lib/lifecycle-state.mjs +211 -28
- package/bin/lib/manifest.mjs +24 -20
- package/bin/lib/provenance.mjs +281 -59
- package/bin/lib/rendering.mjs +69 -19
- package/bin/lib/runtime-freshness.mjs +46 -21
- package/distilled/DESIGN.md +314 -6
- package/distilled/EVIDENCE-INDEX.md +70 -10
- package/distilled/README.md +2 -2
- package/distilled/workflows/audit-milestone.md +1 -1
- package/distilled/workflows/complete-milestone.md +1 -1
- package/distilled/workflows/execute.md +5 -5
- package/distilled/workflows/map-codebase.md +11 -11
- package/distilled/workflows/new-milestone.md +40 -24
- package/distilled/workflows/new-project.md +43 -26
- package/distilled/workflows/pause.md +1 -1
- package/distilled/workflows/plan.md +184 -73
- package/distilled/workflows/progress.md +103 -54
- package/distilled/workflows/quick.md +11 -10
- package/distilled/workflows/resume.md +140 -66
- package/distilled/workflows/verify.md +5 -5
- package/docs/RUNTIME-SUPPORT.md +2 -2
- package/docs/USER-GUIDE.md +9 -5
- package/package.json +19 -20
package/bin/lib/manifest.mjs
CHANGED
|
@@ -39,9 +39,10 @@ export function hashDirectory(dir, baseDir = dir) {
|
|
|
39
39
|
/**
|
|
40
40
|
* Build a full manifest snapshot from installed project files.
|
|
41
41
|
*/
|
|
42
|
-
export function buildManifest({ planningDir, frameworkVersion }) {
|
|
43
|
-
const templatesDir = join(planningDir, 'templates');
|
|
44
|
-
const rolesDir = join(templatesDir, 'roles');
|
|
42
|
+
export function buildManifest({ planningDir, frameworkVersion }) {
|
|
43
|
+
const templatesDir = join(planningDir, 'templates');
|
|
44
|
+
const rolesDir = join(templatesDir, 'roles');
|
|
45
|
+
const runtimeHelpersDir = join(planningDir, 'bin');
|
|
45
46
|
|
|
46
47
|
// Template subcategories
|
|
47
48
|
const delegatesHashes = hashDirectory(join(templatesDir, 'delegates'), join(templatesDir, 'delegates'));
|
|
@@ -59,28 +60,31 @@ export function buildManifest({ planningDir, frameworkVersion }) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
// Role contracts
|
|
63
|
-
const rolesHashes = {};
|
|
64
|
-
if (existsSync(rolesDir)) {
|
|
65
|
-
for (const entry of readdirSync(rolesDir)) {
|
|
66
|
-
if (entry.endsWith('.md')) {
|
|
67
|
-
rolesHashes[entry] = fileHash(join(rolesDir, entry));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
// Role contracts
|
|
64
|
+
const rolesHashes = {};
|
|
65
|
+
if (existsSync(rolesDir)) {
|
|
66
|
+
for (const entry of readdirSync(rolesDir)) {
|
|
67
|
+
if (entry.endsWith('.md')) {
|
|
68
|
+
rolesHashes[entry] = fileHash(join(rolesDir, entry));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const runtimeHelpersHashes = hashDirectory(runtimeHelpersDir, planningDir);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
frameworkVersion,
|
|
77
|
+
generatedAt: new Date().toISOString(),
|
|
75
78
|
templates: {
|
|
76
79
|
delegates: delegatesHashes,
|
|
77
80
|
research: researchHashes,
|
|
78
81
|
codebase: codebaseHashes,
|
|
79
82
|
root: rootHashes,
|
|
80
|
-
},
|
|
81
|
-
roles: rolesHashes,
|
|
82
|
-
|
|
83
|
-
}
|
|
83
|
+
},
|
|
84
|
+
roles: rolesHashes,
|
|
85
|
+
runtimeHelpers: runtimeHelpersHashes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
84
88
|
|
|
85
89
|
/**
|
|
86
90
|
* Read existing manifest from planningDir, or return null if missing/corrupt.
|
package/bin/lib/provenance.mjs
CHANGED
|
@@ -14,9 +14,9 @@ function normalizeCheckpointWorkflow(workflow) {
|
|
|
14
14
|
return ['phase', 'quick', 'generic'].includes(normalized) ? normalized : 'generic';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function classifyCheckpointRouting(workflow) {
|
|
18
|
-
const normalizedWorkflow = normalizeCheckpointWorkflow(workflow);
|
|
19
|
-
const progressBlocks = normalizedWorkflow === 'phase' || normalizedWorkflow === 'quick';
|
|
17
|
+
export function classifyCheckpointRouting(workflow) {
|
|
18
|
+
const normalizedWorkflow = normalizeCheckpointWorkflow(workflow);
|
|
19
|
+
const progressBlocks = normalizedWorkflow === 'phase' || normalizedWorkflow === 'quick';
|
|
20
20
|
|
|
21
21
|
return {
|
|
22
22
|
workflow: normalizedWorkflow,
|
|
@@ -26,7 +26,7 @@ export function classifyCheckpointRouting(workflow) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function parseGitStatusShort(statusText = '') {
|
|
29
|
+
export function parseGitStatusShort(statusText = '') {
|
|
30
30
|
const lines = statusText
|
|
31
31
|
.replace(/\r\n/g, '\n')
|
|
32
32
|
.split('\n')
|
|
@@ -51,27 +51,161 @@ export function parseGitStatusShort(statusText = '') {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
return {
|
|
55
|
-
files,
|
|
56
|
-
stagedCount: files.filter((file) => file.staged).length,
|
|
57
|
-
unstagedCount: files.filter((file) => file.unstaged).length,
|
|
58
|
-
untrackedCount: files.filter((file) => file.untracked).length,
|
|
59
|
-
dirty: files.length > 0,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function
|
|
64
|
-
checkpoint = {},
|
|
65
|
-
planning = {},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
54
|
+
return {
|
|
55
|
+
files,
|
|
56
|
+
stagedCount: files.filter((file) => file.staged).length,
|
|
57
|
+
unstagedCount: files.filter((file) => file.unstaged).length,
|
|
58
|
+
untrackedCount: files.filter((file) => file.untracked).length,
|
|
59
|
+
dirty: files.length > 0,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function classifyBrownfieldCheckpointPrecedence({
|
|
64
|
+
checkpoint = {},
|
|
65
|
+
planning = {},
|
|
66
|
+
quick = {},
|
|
67
|
+
brownfieldChange = {},
|
|
68
|
+
git = {},
|
|
69
|
+
status = parseGitStatusShort(git.statusShort || ''),
|
|
70
|
+
} = {}) {
|
|
71
|
+
const checkpointRouting = classifyCheckpointRouting(checkpoint.workflow);
|
|
72
|
+
if (!brownfieldChange?.exists) {
|
|
73
|
+
return {
|
|
74
|
+
primary: checkpointRouting.progressBlocks ? 'checkpoint' : 'none',
|
|
75
|
+
checkpointRouting,
|
|
76
|
+
branchAligned: false,
|
|
77
|
+
scopeAligned: false,
|
|
78
|
+
executionActive: false,
|
|
79
|
+
checkpointCanOverrideBrownfield: false,
|
|
80
|
+
strictMatchRequired: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (checkpointRouting.workflow === 'generic') {
|
|
85
|
+
return {
|
|
86
|
+
primary: 'brownfield_change',
|
|
87
|
+
checkpointRouting,
|
|
88
|
+
branchAligned: false,
|
|
89
|
+
scopeAligned: false,
|
|
90
|
+
executionActive: false,
|
|
91
|
+
checkpointCanOverrideBrownfield: false,
|
|
92
|
+
strictMatchRequired: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const brownfieldMismatch = classifyBrownfieldArtifactMismatch({
|
|
97
|
+
brownfieldChange,
|
|
98
|
+
git,
|
|
99
|
+
status,
|
|
100
|
+
});
|
|
101
|
+
const currentBranch = normalizeBranchName(git.branch);
|
|
102
|
+
const checkpointBranch = normalizeBranchName(checkpoint.branch);
|
|
103
|
+
const brownfieldBranch = normalizeBranchName(brownfieldChange.currentIntegrationSurface);
|
|
104
|
+
const branchAligned = Boolean(
|
|
105
|
+
currentBranch
|
|
106
|
+
&& checkpointBranch
|
|
107
|
+
&& brownfieldBranch
|
|
108
|
+
&& checkpointBranch === currentBranch
|
|
109
|
+
&& brownfieldBranch === currentBranch
|
|
110
|
+
);
|
|
111
|
+
const scopeAligned = !brownfieldMismatch.warnings.some((warning) =>
|
|
112
|
+
warning.id === 'brownfield_scope_mismatch' || warning.id === 'brownfield_status_mismatch'
|
|
113
|
+
);
|
|
114
|
+
const executionActive = checkpointExecutionIsActive({
|
|
115
|
+
checkpoint,
|
|
116
|
+
checkpointRouting,
|
|
117
|
+
planning,
|
|
118
|
+
quick,
|
|
119
|
+
});
|
|
120
|
+
const checkpointCanOverrideBrownfield = branchAligned && scopeAligned && executionActive;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
primary: checkpointCanOverrideBrownfield ? 'checkpoint' : 'brownfield_change',
|
|
124
|
+
checkpointRouting,
|
|
125
|
+
branchAligned,
|
|
126
|
+
scopeAligned,
|
|
127
|
+
executionActive,
|
|
128
|
+
checkpointCanOverrideBrownfield,
|
|
129
|
+
strictMatchRequired: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function classifyBrownfieldArtifactMismatch({
|
|
134
|
+
brownfieldChange = {},
|
|
135
|
+
git = {},
|
|
136
|
+
status = parseGitStatusShort(git.statusShort || ''),
|
|
137
|
+
} = {}) {
|
|
138
|
+
if (!brownfieldChange?.exists) {
|
|
139
|
+
return {
|
|
140
|
+
warnings: [],
|
|
141
|
+
requiresAcknowledgement: false,
|
|
142
|
+
outsideOwnedPaths: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const warnings = [];
|
|
147
|
+
const declaredBranch = normalizeDeclaredBranch(brownfieldChange.currentIntegrationSurface);
|
|
148
|
+
const branch = String(git.branch || '').trim().toLowerCase();
|
|
149
|
+
if (declaredBranch && branch && declaredBranch !== branch) {
|
|
150
|
+
warnings.push({
|
|
151
|
+
id: 'brownfield_branch_mismatch',
|
|
152
|
+
severity: 'acknowledgement_required',
|
|
153
|
+
summary: `CHANGE.md says the active integration surface is "${brownfieldChange.currentIntegrationSurface}", but git reports "${git.branch}".`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const ownedPaths = normalizeOwnedPaths(brownfieldChange.declaredOwnedPaths || []);
|
|
158
|
+
const outsideOwnedPaths = ownedPaths.length === 0
|
|
159
|
+
? []
|
|
160
|
+
: status.files
|
|
161
|
+
.map((file) => file.path)
|
|
162
|
+
.filter((filePath) => !ownedPaths.some((ownedPath) => matchesOwnedPath(filePath, ownedPath)));
|
|
163
|
+
if (outsideOwnedPaths.length > 0) {
|
|
164
|
+
warnings.push({
|
|
165
|
+
id: 'brownfield_scope_mismatch',
|
|
166
|
+
severity: 'acknowledgement_required',
|
|
167
|
+
summary: `Dirty files fall outside the CHANGE.md write scope: ${outsideOwnedPaths.join(', ')}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if ((brownfieldChange.currentStatus === 'closed' || brownfieldChange.currentStatus === 'ready_for_verification') && status.dirty) {
|
|
172
|
+
warnings.push({
|
|
173
|
+
id: 'brownfield_status_mismatch',
|
|
174
|
+
severity: 'acknowledgement_required',
|
|
175
|
+
summary: `CHANGE.md marks the change as "${brownfieldChange.currentStatus}", but the live worktree is still dirty.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
warnings,
|
|
181
|
+
requiresAcknowledgement: warnings.some((warning) => warning.severity === 'acknowledgement_required'),
|
|
182
|
+
outsideOwnedPaths,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function buildProvenanceSnapshot({
|
|
187
|
+
checkpoint = {},
|
|
188
|
+
planning = {},
|
|
189
|
+
quick = {},
|
|
190
|
+
brownfieldChange = {},
|
|
191
|
+
git = {},
|
|
192
|
+
} = {}) {
|
|
193
|
+
const checkpointRouting = classifyCheckpointRouting(checkpoint.workflow);
|
|
194
|
+
const status = parseGitStatusShort(git.statusShort || '');
|
|
195
|
+
const commitsAheadOfMain = normalizeCount(git.commitsAheadOfMain);
|
|
196
|
+
const commitsAheadOfRemote = normalizeCount(git.commitsAheadOfRemote);
|
|
197
|
+
const prState = normalizePrState(git.prState);
|
|
198
|
+
const brownfieldMismatch = classifyBrownfieldArtifactMismatch({ brownfieldChange, git, status });
|
|
199
|
+
const brownfieldRouting = classifyBrownfieldCheckpointPrecedence({
|
|
200
|
+
checkpoint,
|
|
201
|
+
planning,
|
|
202
|
+
quick,
|
|
203
|
+
brownfieldChange,
|
|
204
|
+
git,
|
|
205
|
+
status,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const warnings = [];
|
|
75
209
|
|
|
76
210
|
if (status.dirty) {
|
|
77
211
|
warnings.push({
|
|
@@ -121,31 +255,50 @@ export function buildProvenanceSnapshot({
|
|
|
121
255
|
});
|
|
122
256
|
}
|
|
123
257
|
|
|
124
|
-
if (git.materialCheckpointMismatch) {
|
|
125
|
-
warnings.push({
|
|
126
|
-
id: 'checkpoint_mismatch',
|
|
127
|
-
severity: 'acknowledgement_required',
|
|
128
|
-
summary: 'Checkpoint narrative truth materially understates or conflicts with the live branch/worktree truth.',
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
258
|
+
if (git.materialCheckpointMismatch) {
|
|
259
|
+
warnings.push({
|
|
260
|
+
id: 'checkpoint_mismatch',
|
|
261
|
+
severity: 'acknowledgement_required',
|
|
262
|
+
summary: 'Checkpoint narrative truth materially understates or conflicts with the live branch/worktree truth.',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
warnings.push(...brownfieldMismatch.warnings);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
checkpoint: {
|
|
270
|
+
workflow: checkpointRouting.workflow,
|
|
271
|
+
phase: checkpoint.phase ?? null,
|
|
272
|
+
branch: checkpoint.branch || null,
|
|
273
|
+
runtime: checkpoint.runtime || 'unknown',
|
|
274
|
+
hasNarrative: Boolean(checkpoint.hasNarrative),
|
|
275
|
+
routing: checkpointRouting,
|
|
276
|
+
},
|
|
277
|
+
planning: {
|
|
278
|
+
currentPhase: planning.currentPhase || null,
|
|
279
|
+
nextPhase: planning.nextPhase || null,
|
|
280
|
+
completedPhaseCount: Number.isFinite(Number(planning.completedPhaseCount))
|
|
281
|
+
? Number(planning.completedPhaseCount)
|
|
282
|
+
: 0,
|
|
283
|
+
},
|
|
284
|
+
brownfieldChange: {
|
|
285
|
+
exists: Boolean(brownfieldChange?.exists),
|
|
286
|
+
title: brownfieldChange?.title || null,
|
|
287
|
+
currentStatus: brownfieldChange?.currentStatus || null,
|
|
288
|
+
currentIntegrationSurface: brownfieldChange?.currentIntegrationSurface || null,
|
|
289
|
+
nextAction: brownfieldChange?.nextAction || null,
|
|
290
|
+
declaredOwnedPaths: brownfieldChange?.declaredOwnedPaths || [],
|
|
291
|
+
},
|
|
292
|
+
routing: {
|
|
293
|
+
primary: brownfieldRouting.primary,
|
|
294
|
+
strictMatchRequired: brownfieldRouting.strictMatchRequired,
|
|
295
|
+
checkpointCanOverrideBrownfield: brownfieldRouting.checkpointCanOverrideBrownfield,
|
|
296
|
+
branchAligned: brownfieldRouting.branchAligned,
|
|
297
|
+
scopeAligned: brownfieldRouting.scopeAligned,
|
|
298
|
+
executionActive: brownfieldRouting.executionActive,
|
|
299
|
+
},
|
|
300
|
+
git: {
|
|
301
|
+
branch: git.branch || 'unknown',
|
|
149
302
|
prState,
|
|
150
303
|
commitsAheadOfMain,
|
|
151
304
|
commitsAheadOfRemote,
|
|
@@ -154,12 +307,81 @@ export function buildProvenanceSnapshot({
|
|
|
154
307
|
untrackedCount: status.untrackedCount,
|
|
155
308
|
dirty: status.dirty,
|
|
156
309
|
},
|
|
157
|
-
integrationSurface: {
|
|
158
|
-
staleBranch: Boolean(git.staleBranch),
|
|
159
|
-
mixedScope: Boolean(git.mixedScope),
|
|
160
|
-
materialCheckpointMismatch: Boolean(git.materialCheckpointMismatch),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
310
|
+
integrationSurface: {
|
|
311
|
+
staleBranch: Boolean(git.staleBranch),
|
|
312
|
+
mixedScope: Boolean(git.mixedScope),
|
|
313
|
+
materialCheckpointMismatch: Boolean(git.materialCheckpointMismatch),
|
|
314
|
+
materialBrownfieldMismatch: brownfieldMismatch.requiresAcknowledgement,
|
|
315
|
+
},
|
|
316
|
+
warnings,
|
|
317
|
+
requiresAcknowledgement: warnings.some((warning) => warning.severity === 'acknowledgement_required'),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function normalizeBranchName(value) {
|
|
322
|
+
return String(value || '')
|
|
323
|
+
.trim()
|
|
324
|
+
.toLowerCase()
|
|
325
|
+
.replace(/\\/g, '/');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function normalizeDeclaredBranch(value) {
|
|
329
|
+
return normalizeBranchName(value);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeOwnedPaths(values) {
|
|
333
|
+
return values
|
|
334
|
+
.map((value) => String(value || '').trim().replace(/\\/g, '/'))
|
|
335
|
+
.filter(Boolean);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function matchesOwnedPath(filePath, ownedPath) {
|
|
339
|
+
if (ownedPath.includes('*')) {
|
|
340
|
+
const prefix = ownedPath.split('*')[0];
|
|
341
|
+
return filePath.startsWith(prefix);
|
|
342
|
+
}
|
|
343
|
+
return filePath === ownedPath
|
|
344
|
+
|| filePath.startsWith(`${ownedPath}/`)
|
|
345
|
+
|| ownedPath.startsWith(`${filePath}/`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function checkpointExecutionIsActive({
|
|
349
|
+
checkpoint = {},
|
|
350
|
+
checkpointRouting = classifyCheckpointRouting(checkpoint.workflow),
|
|
351
|
+
planning = {},
|
|
352
|
+
quick = {},
|
|
353
|
+
} = {}) {
|
|
354
|
+
if (checkpointRouting.workflow === 'quick') {
|
|
355
|
+
return quick.hasIncompleteWork === true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (checkpointRouting.workflow !== 'phase') {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const checkpointPhase = normalizePhaseRef(checkpoint.phase);
|
|
363
|
+
if (!checkpointPhase) return false;
|
|
364
|
+
|
|
365
|
+
const phases = Array.isArray(planning.phases) ? planning.phases : [];
|
|
366
|
+
if (phases.length > 0) {
|
|
367
|
+
const matchingPhase = phases.find((phase) => normalizePhaseRef(phase.number) === checkpointPhase);
|
|
368
|
+
return Boolean(matchingPhase && matchingPhase.status !== 'done');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const currentPhase = normalizePhaseRef(planning.currentPhase);
|
|
372
|
+
const nextPhase = normalizePhaseRef(planning.nextPhase);
|
|
373
|
+
return checkpointPhase === currentPhase || checkpointPhase === nextPhase;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function normalizePhaseRef(value) {
|
|
377
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
378
|
+
if (!raw) return '';
|
|
379
|
+
|
|
380
|
+
const match = raw.match(/^(\d+(?:\.\d+)*)([a-z]?)$/i);
|
|
381
|
+
if (!match) return raw;
|
|
382
|
+
|
|
383
|
+
const numericSegments = match[1]
|
|
384
|
+
.split('.')
|
|
385
|
+
.map((segment) => String(parseInt(segment, 10)));
|
|
386
|
+
return `${numericSegments.join('.')}${match[2] || ''}`;
|
|
387
|
+
}
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -18,23 +18,72 @@ function getDelegateContent(delegateFile) {
|
|
|
18
18
|
return `<!-- Delegate file not found: ${delegateFile} -->\n`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function renderSkillContent(workflow) {
|
|
22
|
-
const workflowContent = getWorkflowContent(workflow.workflow);
|
|
23
|
-
return `---
|
|
24
|
-
name: ${workflow.name}
|
|
25
|
-
description: ${workflow.description}
|
|
21
|
+
function renderSkillContent(workflow) {
|
|
22
|
+
const workflowContent = getWorkflowContent(workflow.workflow);
|
|
23
|
+
return `---
|
|
24
|
+
name: ${workflow.name}
|
|
25
|
+
description: ${workflow.description}
|
|
26
26
|
context: fork
|
|
27
27
|
agent: ${workflow.agent}
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
${workflowContent}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
${workflowContent}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderPlanningCliLauncher({ packageName = 'gsdd-cli', packageVersion }) {
|
|
34
|
+
if (!packageVersion) {
|
|
35
|
+
throw new Error('renderPlanningCliLauncher requires packageVersion');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const packageSpec = `${packageName}@${packageVersion}`;
|
|
39
|
+
|
|
40
|
+
return `#!/usr/bin/env node
|
|
41
|
+
|
|
42
|
+
import { spawnSync } from 'node:child_process';
|
|
43
|
+
|
|
44
|
+
const packageSpec = ${JSON.stringify(packageSpec)};
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const env = { ...process.env };
|
|
47
|
+
|
|
48
|
+
function forwardResult(result, fallbackMessage) {
|
|
49
|
+
if (result.error) {
|
|
50
|
+
console.error(fallbackMessage ?? result.error.message);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof result.status === 'number') {
|
|
56
|
+
process.exitCode = result.status;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.signal) {
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (env.GSDD_CLI_PATH) {
|
|
66
|
+
const localResult = spawnSync(process.execPath, [env.GSDD_CLI_PATH, ...args], {
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
env,
|
|
69
|
+
});
|
|
70
|
+
forwardResult(localResult, 'Failed to run the local GSDD CLI path from GSDD_CLI_PATH.');
|
|
71
|
+
} else {
|
|
72
|
+
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
73
|
+
const npxResult = spawnSync(npxCommand, ['--yes', packageSpec, ...args], {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
env,
|
|
76
|
+
});
|
|
77
|
+
forwardResult(npxResult, \`Failed to run \${packageSpec} via npx.\`);
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildPortableSkillEntries(workflows) {
|
|
83
|
+
return workflows.map((workflow) => ({
|
|
84
|
+
relativePath: `.agents/skills/${workflow.name}/SKILL.md`,
|
|
85
|
+
content: renderSkillContent(workflow),
|
|
86
|
+
}));
|
|
38
87
|
}
|
|
39
88
|
|
|
40
89
|
function renderOpenCodeCommandContent(workflow) {
|
|
@@ -95,9 +144,10 @@ export {
|
|
|
95
144
|
buildPortableSkillEntries,
|
|
96
145
|
getDelegateContent,
|
|
97
146
|
getWorkflowContent,
|
|
98
|
-
renderAgentsBoundedBlock,
|
|
99
|
-
renderAgentsFileContent,
|
|
100
|
-
renderOpenCodeCommandContent,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
147
|
+
renderAgentsBoundedBlock,
|
|
148
|
+
renderAgentsFileContent,
|
|
149
|
+
renderOpenCodeCommandContent,
|
|
150
|
+
renderPlanningCliLauncher,
|
|
151
|
+
renderSkillContent,
|
|
152
|
+
upsertBoundedBlock,
|
|
153
|
+
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import {
|
|
5
|
+
buildPortableSkillEntries,
|
|
6
|
+
getDelegateContent,
|
|
7
|
+
renderPlanningCliLauncher,
|
|
8
|
+
renderOpenCodeCommandContent,
|
|
9
|
+
renderSkillContent,
|
|
10
|
+
} from './rendering.mjs';
|
|
9
11
|
import {
|
|
10
12
|
CLAUDE_MODEL_PROFILES,
|
|
11
13
|
renderClaudeApproachExplorer,
|
|
@@ -22,11 +24,15 @@ import {
|
|
|
22
24
|
renderCodexApproachExplorer,
|
|
23
25
|
renderCodexPlanChecker,
|
|
24
26
|
} from '../adapters/codex.mjs';
|
|
25
|
-
import {
|
|
26
|
-
getRuntimeModelOverride,
|
|
27
|
-
loadProjectModelConfig,
|
|
28
|
-
resolveRuntimeAgentModel,
|
|
29
|
-
} from './models.mjs';
|
|
27
|
+
import {
|
|
28
|
+
getRuntimeModelOverride,
|
|
29
|
+
loadProjectModelConfig,
|
|
30
|
+
resolveRuntimeAgentModel,
|
|
31
|
+
} from './models.mjs';
|
|
32
|
+
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = dirname(__filename);
|
|
35
|
+
const PACKAGE_JSON = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
30
36
|
|
|
31
37
|
function normalizeContent(content) {
|
|
32
38
|
return String(content).replace(/\r\n/g, '\n');
|
|
@@ -127,7 +133,7 @@ function buildOpenCodeEntries({ cwd, workflows }) {
|
|
|
127
133
|
return entries;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
function buildCodexEntries({ cwd }) {
|
|
136
|
+
function buildCodexEntries({ cwd }) {
|
|
131
137
|
const config = loadProjectModelConfig(cwd);
|
|
132
138
|
const checkerModelId = getRuntimeModelOverride(config, 'codex', 'plan-checker');
|
|
133
139
|
const explorerModelId = getRuntimeModelOverride(config, 'codex', 'approach-explorer');
|
|
@@ -142,13 +148,32 @@ function buildCodexEntries({ cwd }) {
|
|
|
142
148
|
expectedContent: renderCodexApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId),
|
|
143
149
|
},
|
|
144
150
|
];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return [
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildWorkspaceHelperEntries() {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
relativePath: '.planning/bin/gsdd.mjs',
|
|
157
|
+
expectedContent: renderPlanningCliLauncher({
|
|
158
|
+
packageName: PACKAGE_JSON.name,
|
|
159
|
+
packageVersion: PACKAGE_JSON.version,
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workflows }) {
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
runtime: 'workspace-helper',
|
|
169
|
+
label: 'workspace workflow helper',
|
|
170
|
+
root: '.planning/bin',
|
|
171
|
+
repairCommand: 'gsdd update',
|
|
172
|
+
entries: buildWorkspaceHelperEntries(),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
runtime: 'portable',
|
|
176
|
+
label: 'portable skills',
|
|
152
177
|
root: '.agents/skills',
|
|
153
178
|
repairCommand: 'gsdd update',
|
|
154
179
|
entries: buildPortableSkillEntries(workflows).map((entry) => ({
|