gsdd-cli 0.19.0 → 0.19.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/README.md +4 -3
- package/agents/approach-explorer.md +9 -0
- package/agents/executor.md +118 -39
- package/agents/planner.md +32 -3
- package/bin/adapters/claude.mjs +9 -7
- package/bin/adapters/opencode.mjs +9 -7
- package/bin/gsdd.mjs +4 -4
- package/bin/lib/evidence-contract.mjs +216 -3
- package/bin/lib/health-truth.mjs +26 -11
- package/bin/lib/health.mjs +88 -23
- package/bin/lib/init-runtime.mjs +2 -0
- package/bin/lib/lifecycle-preflight.mjs +418 -6
- package/bin/lib/lifecycle-state.mjs +9 -4
- package/bin/lib/phase.mjs +7 -2
- package/bin/lib/plan-constants.mjs +5 -0
- package/bin/lib/rendering.mjs +4 -0
- package/bin/lib/session-fingerprint.mjs +91 -14
- package/distilled/DESIGN.md +148 -18
- package/distilled/EVIDENCE-INDEX.md +29 -5
- package/distilled/templates/approach.md +40 -0
- package/distilled/templates/delegates/approach-explorer.md +3 -0
- package/distilled/templates/delegates/plan-checker.md +14 -4
- package/distilled/workflows/audit-milestone.md +29 -1
- package/distilled/workflows/complete-milestone.md +36 -23
- package/distilled/workflows/execute.md +35 -33
- package/distilled/workflows/new-project.md +28 -31
- package/distilled/workflows/plan.md +18 -16
- package/distilled/workflows/quick.md +10 -6
- package/distilled/workflows/verify.md +8 -8
- package/package.json +1 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const EVIDENCE_KINDS = Object.freeze(['code', 'test', 'runtime', 'delivery', 'human']);
|
|
2
2
|
const DELIVERY_POSTURES = Object.freeze(['repo_only', 'delivery_sensitive']);
|
|
3
3
|
const CLOSURE_SURFACES = Object.freeze(['verify', 'audit-milestone', 'complete-milestone']);
|
|
4
|
+
const RELEASE_CLAIM_POSTURES = Object.freeze([
|
|
5
|
+
'repo_closeout',
|
|
6
|
+
'runtime_validated_closeout',
|
|
7
|
+
'delivery_supported_closeout',
|
|
8
|
+
]);
|
|
4
9
|
|
|
5
10
|
const LEGACY_EVIDENCE_ALIASES = Object.freeze({
|
|
6
11
|
code: 'code',
|
|
@@ -22,8 +27,8 @@ const EVIDENCE_MATRIX = Object.freeze({
|
|
|
22
27
|
blockedSoloKinds: Object.freeze(['human', 'delivery']),
|
|
23
28
|
}),
|
|
24
29
|
delivery_sensitive: Object.freeze({
|
|
25
|
-
requiredKinds: Object.freeze(['code', 'runtime']),
|
|
26
|
-
recommendedKinds: Object.freeze(['test', '
|
|
30
|
+
requiredKinds: Object.freeze(['code', 'runtime', 'delivery']),
|
|
31
|
+
recommendedKinds: Object.freeze(['test', 'human']),
|
|
27
32
|
blockedSoloKinds: Object.freeze(['code', 'human']),
|
|
28
33
|
}),
|
|
29
34
|
}),
|
|
@@ -53,7 +58,45 @@ const EVIDENCE_MATRIX = Object.freeze({
|
|
|
53
58
|
}),
|
|
54
59
|
});
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
const CONTRADICTION_CATEGORIES = Object.freeze([
|
|
62
|
+
'evidence',
|
|
63
|
+
'public_surface',
|
|
64
|
+
'runtime',
|
|
65
|
+
'delivery',
|
|
66
|
+
'planning_drift',
|
|
67
|
+
'generated_surface',
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const CONTRADICTION_STATUSES = Object.freeze(['passed', 'failed', 'not_applicable']);
|
|
71
|
+
|
|
72
|
+
const RELEASE_CLAIM_MATRIX = Object.freeze({
|
|
73
|
+
repo_closeout: Object.freeze({
|
|
74
|
+
deliveryPosture: 'repo_only',
|
|
75
|
+
requiredClaimKinds: Object.freeze([]),
|
|
76
|
+
allowedClaim: 'Repo-local milestone or phase closeout is supported by planning and repository artifacts only.',
|
|
77
|
+
invalidClaim: 'Do not imply runtime validation, delivery, publication, or public support from repo-local closeout alone.',
|
|
78
|
+
}),
|
|
79
|
+
runtime_validated_closeout: Object.freeze({
|
|
80
|
+
deliveryPosture: 'repo_only',
|
|
81
|
+
requiredClaimKinds: Object.freeze(['runtime']),
|
|
82
|
+
allowedClaim: 'Runtime behavior or a runtime surface was directly executed and observed for the named runtime or surface.',
|
|
83
|
+
invalidClaim: 'Do not generalize validation from one runtime or generated surface to another.',
|
|
84
|
+
}),
|
|
85
|
+
delivery_supported_closeout: Object.freeze({
|
|
86
|
+
deliveryPosture: 'delivery_sensitive',
|
|
87
|
+
requiredClaimKinds: Object.freeze([]),
|
|
88
|
+
allowedClaim: 'Externally consumed release, support, install, or delivery claims are supported by the delivery-sensitive evidence bar.',
|
|
89
|
+
invalidClaim: 'Do not imply merge, package, tag, GitHub Release, publication, generated-surface freshness, or public support without matching delivery evidence.',
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const CONTRADICTION_BLOCKERS_BY_POSTURE = Object.freeze({
|
|
94
|
+
repo_closeout: Object.freeze(['evidence', 'public_surface', 'planning_drift']),
|
|
95
|
+
runtime_validated_closeout: Object.freeze(['evidence', 'runtime', 'generated_surface', 'planning_drift']),
|
|
96
|
+
delivery_supported_closeout: CONTRADICTION_CATEGORIES,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export { CLOSURE_SURFACES, DELIVERY_POSTURES, EVIDENCE_KINDS, RELEASE_CLAIM_POSTURES };
|
|
57
100
|
|
|
58
101
|
export function normalizeEvidenceKind(kind) {
|
|
59
102
|
if (!kind) {
|
|
@@ -78,6 +121,11 @@ export function isClosureSurface(surface) {
|
|
|
78
121
|
return CLOSURE_SURFACES.includes(surface);
|
|
79
122
|
}
|
|
80
123
|
|
|
124
|
+
export function normalizeReleaseClaimPosture(posture) {
|
|
125
|
+
if (!posture) return 'repo_closeout';
|
|
126
|
+
return RELEASE_CLAIM_POSTURES.includes(posture) ? posture : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
81
129
|
export function getEvidenceContract(surface, deliveryPosture) {
|
|
82
130
|
const matrix = EVIDENCE_MATRIX[surface];
|
|
83
131
|
if (!matrix) {
|
|
@@ -108,5 +156,170 @@ export function describeEvidenceSurface(surface) {
|
|
|
108
156
|
surface,
|
|
109
157
|
supportedKinds: [...EVIDENCE_KINDS],
|
|
110
158
|
deliveryPostures: DELIVERY_POSTURES.map((deliveryPosture) => getEvidenceContract(surface, deliveryPosture)),
|
|
159
|
+
releaseClaimPostures: RELEASE_CLAIM_POSTURES.map((releaseClaimPosture) => getReleaseClaimContract(surface, releaseClaimPosture)),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function uniqueKinds(kinds) {
|
|
164
|
+
return [...new Set(kinds)];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getDowngradePosture(observedKinds) {
|
|
168
|
+
if (observedKinds.includes('runtime')) {
|
|
169
|
+
return 'runtime_validated_closeout';
|
|
170
|
+
}
|
|
171
|
+
return 'repo_closeout';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getReleaseClaimContract(surface, releaseClaimPosture = 'repo_closeout') {
|
|
175
|
+
const posture = normalizeReleaseClaimPosture(releaseClaimPosture);
|
|
176
|
+
if (!posture) {
|
|
177
|
+
throw new Error(`Unsupported release claim posture: ${releaseClaimPosture}`);
|
|
178
|
+
}
|
|
179
|
+
const claim = RELEASE_CLAIM_MATRIX[posture];
|
|
180
|
+
const evidence = getEvidenceContract(surface, claim.deliveryPosture);
|
|
181
|
+
const requiredKinds = uniqueKinds([...evidence.requiredKinds, ...claim.requiredClaimKinds]);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
surface,
|
|
185
|
+
releaseClaimPosture: posture,
|
|
186
|
+
deliveryPosture: claim.deliveryPosture,
|
|
187
|
+
supportedKinds: [...EVIDENCE_KINDS],
|
|
188
|
+
requiredKinds,
|
|
189
|
+
requiredClaimKinds: [...claim.requiredClaimKinds],
|
|
190
|
+
allowedClaim: claim.allowedClaim,
|
|
191
|
+
invalidClaim: claim.invalidClaim,
|
|
192
|
+
waiverRule: 'Waivers may only narrow the release claim posture or defer an unsupported claim; they never satisfy missing required evidence for the stronger claim.',
|
|
193
|
+
deferralRule: 'Deferrals must name the unsupported claim, missing evidence kinds, and later workflow or milestone candidate when known.',
|
|
194
|
+
contradictionCategories: [...CONTRADICTION_CATEGORIES],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function evaluateReleaseClaimPosture({
|
|
199
|
+
surface,
|
|
200
|
+
releaseClaimPosture = 'repo_closeout',
|
|
201
|
+
observedKinds = [],
|
|
202
|
+
waivedKinds = [],
|
|
203
|
+
} = {}) {
|
|
204
|
+
const contract = getReleaseClaimContract(surface, releaseClaimPosture);
|
|
205
|
+
const observed = normalizeEvidenceKinds(observedKinds);
|
|
206
|
+
const waived = normalizeEvidenceKinds(waivedKinds);
|
|
207
|
+
const missingKinds = contract.requiredKinds.filter((kind) => !observed.includes(kind));
|
|
208
|
+
const invalidWaivers = waived.filter((kind) => missingKinds.includes(kind));
|
|
209
|
+
const hasUnsupportedStrongClaim = contract.releaseClaimPosture !== 'repo_closeout' && missingKinds.length > 0;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
surface: contract.surface,
|
|
213
|
+
releaseClaimPosture: contract.releaseClaimPosture,
|
|
214
|
+
deliveryPosture: contract.deliveryPosture,
|
|
215
|
+
requiredKinds: [...contract.requiredKinds],
|
|
216
|
+
observedKinds: observed,
|
|
217
|
+
missingKinds,
|
|
218
|
+
invalidWaivers,
|
|
219
|
+
status: missingKinds.length === 0 && invalidWaivers.length === 0 ? 'supported' : 'unsupported',
|
|
220
|
+
disposition: hasUnsupportedStrongClaim ? 'downgrade_or_defer' : missingKinds.length > 0 ? 'block_or_defer' : 'proceed',
|
|
221
|
+
downgradeTo: hasUnsupportedStrongClaim ? getDowngradePosture(observed) : null,
|
|
222
|
+
deferredClaims: hasUnsupportedStrongClaim
|
|
223
|
+
? [{ claim: contract.releaseClaimPosture, missingKinds }]
|
|
224
|
+
: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function evaluateReleaseClaimCloseoutContract({
|
|
229
|
+
surface,
|
|
230
|
+
deliveryPosture = null,
|
|
231
|
+
releaseClaimPosture = 'repo_closeout',
|
|
232
|
+
observedKinds = [],
|
|
233
|
+
waivedKinds = [],
|
|
234
|
+
unsupportedClaims = [],
|
|
235
|
+
deferrals = [],
|
|
236
|
+
contradictionChecks = {},
|
|
237
|
+
} = {}) {
|
|
238
|
+
const posture = evaluateReleaseClaimPosture({
|
|
239
|
+
surface,
|
|
240
|
+
releaseClaimPosture,
|
|
241
|
+
observedKinds,
|
|
242
|
+
waivedKinds,
|
|
243
|
+
});
|
|
244
|
+
const missingContradictionChecks = CONTRADICTION_CATEGORIES.filter((name) => !(name in contradictionChecks));
|
|
245
|
+
const failedContradictionChecks = Object.entries(contradictionChecks)
|
|
246
|
+
.filter(([, status]) => status === 'failed')
|
|
247
|
+
.map(([name]) => name);
|
|
248
|
+
const unknownContradictionChecks = Object.keys(contradictionChecks)
|
|
249
|
+
.filter((name) => !CONTRADICTION_CATEGORIES.includes(name));
|
|
250
|
+
const invalidContradictionChecks = Object.entries(contradictionChecks)
|
|
251
|
+
.filter(([, status]) => !CONTRADICTION_STATUSES.includes(status))
|
|
252
|
+
.map(([name]) => name);
|
|
253
|
+
const blockingContradictionChecks = failedContradictionChecks.filter((name) =>
|
|
254
|
+
CONTRADICTION_BLOCKERS_BY_POSTURE[posture.releaseClaimPosture].includes(name)
|
|
255
|
+
);
|
|
256
|
+
const unresolvedUnsupportedClaims = unsupportedClaims.filter((claim) =>
|
|
257
|
+
!deferrals.some((deferral) => namesUnsupportedClaim(deferral, claim))
|
|
258
|
+
);
|
|
259
|
+
const blockers = [];
|
|
260
|
+
|
|
261
|
+
if (deliveryPosture && deliveryPosture !== posture.deliveryPosture) {
|
|
262
|
+
blockers.push({
|
|
263
|
+
code: 'incompatible_release_claim_posture',
|
|
264
|
+
details: [`${deliveryPosture} cannot support ${posture.releaseClaimPosture}; expected ${posture.deliveryPosture}`],
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (posture.missingKinds.length > 0) {
|
|
269
|
+
blockers.push({ code: 'missing_required_release_evidence', details: posture.missingKinds });
|
|
270
|
+
}
|
|
271
|
+
if (posture.invalidWaivers.length > 0) {
|
|
272
|
+
blockers.push({ code: 'invalid_release_waivers', details: posture.invalidWaivers });
|
|
273
|
+
}
|
|
274
|
+
if (unresolvedUnsupportedClaims.length > 0) {
|
|
275
|
+
blockers.push({ code: 'unsupported_release_claims', details: unresolvedUnsupportedClaims });
|
|
276
|
+
}
|
|
277
|
+
if (missingContradictionChecks.length > 0) {
|
|
278
|
+
blockers.push({ code: 'missing_release_contradiction_checks', details: missingContradictionChecks });
|
|
279
|
+
}
|
|
280
|
+
if (unknownContradictionChecks.length > 0) {
|
|
281
|
+
blockers.push({ code: 'unknown_release_contradiction_checks', details: unknownContradictionChecks });
|
|
282
|
+
}
|
|
283
|
+
if (invalidContradictionChecks.length > 0) {
|
|
284
|
+
blockers.push({ code: 'invalid_release_contradiction_checks', details: invalidContradictionChecks });
|
|
285
|
+
}
|
|
286
|
+
if (blockingContradictionChecks.length > 0) {
|
|
287
|
+
blockers.push({ code: 'failed_release_contradiction_checks', details: blockingContradictionChecks });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
...posture,
|
|
292
|
+
unsupportedClaims: [...unsupportedClaims],
|
|
293
|
+
deferrals: [...deferrals],
|
|
294
|
+
failedContradictionChecks: blockingContradictionChecks,
|
|
295
|
+
allFailedContradictionChecks: failedContradictionChecks,
|
|
296
|
+
missingContradictionChecks,
|
|
297
|
+
unknownContradictionChecks,
|
|
298
|
+
invalidContradictionChecks,
|
|
299
|
+
unresolvedUnsupportedClaims,
|
|
300
|
+
blockers,
|
|
301
|
+
status: blockers.length === 0 ? 'supported' : 'unsupported',
|
|
111
302
|
};
|
|
112
303
|
}
|
|
304
|
+
|
|
305
|
+
function namesUnsupportedClaim(deferral, claim) {
|
|
306
|
+
const normalizedDeferral = normalizeClaimText(deferral);
|
|
307
|
+
const normalizedClaim = normalizeClaimText(claim);
|
|
308
|
+
if (!normalizedDeferral || !normalizedClaim) return false;
|
|
309
|
+
return normalizedDeferral.includes(normalizedClaim) && isStructuredDeferral(normalizedDeferral);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isStructuredDeferral(normalizedDeferral) {
|
|
313
|
+
const namesEvidenceKind = EVIDENCE_KINDS.some((kind) => normalizedDeferral.includes(kind));
|
|
314
|
+
const namesLaterTarget = /\b(later|next|future|workflow|milestone|phase|gsdd)\b/.test(normalizedDeferral);
|
|
315
|
+
return namesEvidenceKind && namesLaterTarget;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeClaimText(value) {
|
|
319
|
+
return String(value || '')
|
|
320
|
+
.trim()
|
|
321
|
+
.toLowerCase()
|
|
322
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
323
|
+
.replace(/\s+/g, ' ')
|
|
324
|
+
.trim();
|
|
325
|
+
}
|
package/bin/lib/health-truth.mjs
CHANGED
|
@@ -56,8 +56,8 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, option
|
|
|
56
56
|
warnings.push({
|
|
57
57
|
id: 'W8',
|
|
58
58
|
severity: 'WARN',
|
|
59
|
-
message: `distilled/README.md workflow inventory is out of sync (${issues.join('; ')})`,
|
|
60
|
-
fix: 'Update distilled/README.md workflow
|
|
59
|
+
message: `distilled/README.md workflow surface/status inventory is out of sync (${issues.join('; ')})`,
|
|
60
|
+
fix: 'Update distilled/README.md workflow inventory table and framework file tree to match distilled/workflows/',
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -94,7 +94,7 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, option
|
|
|
94
94
|
warnings.push({
|
|
95
95
|
id: 'W11',
|
|
96
96
|
severity: 'WARN',
|
|
97
|
-
message: `
|
|
97
|
+
message: `Renderer-backed generated runtime and workflow-helper surfaces drift from current render output (${summarizeRuntimeFreshnessIssues(options.runtimeFreshnessReport)})`,
|
|
98
98
|
fix: getRuntimeFreshnessRepairGuidance(options.runtimeFreshnessReport),
|
|
99
99
|
});
|
|
100
100
|
}
|
|
@@ -105,8 +105,8 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, option
|
|
|
105
105
|
id: 'W12',
|
|
106
106
|
severity: 'WARN',
|
|
107
107
|
message: `Planning state drifted since last recorded session (${drift.details.join('; ')})`,
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
fix: 'Review the changed planning files. If the drift is intentional, rebaseline with `node .planning/bin/gsdd.mjs session-fingerprint write`, then rerun the blocked lifecycle preflight.',
|
|
109
|
+
});
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
return warnings;
|
|
@@ -119,19 +119,34 @@ function extractHealthTableIds(content) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function extractReadmeStatusEntries(content) {
|
|
122
|
-
const
|
|
122
|
+
const workflowSurface = extractSection(content, '## Workflow Surface', 'Architecture notes:');
|
|
123
|
+
const currentStatus = extractSection(content, '## Current Status', 'Architecture notes:');
|
|
124
|
+
const section = workflowSurface || currentStatus;
|
|
123
125
|
if (!section) return [];
|
|
124
126
|
return [...normalizeContent(section).matchAll(/\|\s*`([^`]+\.md)`\s*\|/g)].map((result) => result[1]);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
function extractReadmeWorkflowTreeEntries(content) {
|
|
128
130
|
const section = extractSection(content, '## Files In This Framework', '## ');
|
|
129
|
-
const match = normalizeContent(section || '').match(/```[
|
|
131
|
+
const match = normalizeContent(section || '').match(/```[^\n]*\n([\s\S]*?)\n```/);
|
|
130
132
|
if (!match) return [];
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
const entries = [];
|
|
134
|
+
let inWorkflows = false;
|
|
135
|
+
for (const rawLine of match[1].split('\n')) {
|
|
136
|
+
const line = rawLine.trim();
|
|
137
|
+
if (!line) continue;
|
|
138
|
+
if (!inWorkflows) {
|
|
139
|
+
if (/\bworkflows\/$/.test(line)) inWorkflows = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (/\.md\b/.test(line)) {
|
|
143
|
+
const file = line.match(/([^\s/`|├└─]+\.md)\b/);
|
|
144
|
+
if (file) entries.push(file[1]);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (/\b[^\s/]+\/$/.test(line)) break;
|
|
148
|
+
}
|
|
149
|
+
return entries;
|
|
135
150
|
}
|
|
136
151
|
|
|
137
152
|
function extractRepoLocalPaths(content) {
|
package/bin/lib/health.mjs
CHANGED
|
@@ -164,10 +164,10 @@ export function createCmdHealth(ctx) {
|
|
|
164
164
|
if (!cat.hashes) continue;
|
|
165
165
|
const result = detectModifications(cat.dir, cat.hashes);
|
|
166
166
|
if (result.modified.length > 0) {
|
|
167
|
-
warnings.push({ id: 'W2', severity: 'WARN', message: `${cat.name}: ${result.modified.length} file(s) modified locally (${result.modified.join(', ')})`, fix: `Intentional? Run \`${cat.fixCommand}\` to reset` });
|
|
167
|
+
warnings.push({ id: 'W2', severity: 'WARN', message: `${cat.name}: ${result.modified.length} manifest-tracked installed file(s) modified locally (${result.modified.join(', ')})`, fix: `Intentional? Run \`${cat.fixCommand}\` to reset` });
|
|
168
168
|
}
|
|
169
169
|
if (result.missing.length > 0) {
|
|
170
|
-
warnings.push({ id: 'W3', severity: 'WARN', message: `${cat.name}: ${result.missing.length} file(s) missing from disk (${result.missing.join(', ')})`, fix: `Run \`${cat.fixCommand}\` to restore` });
|
|
170
|
+
warnings.push({ id: 'W3', severity: 'WARN', message: `${cat.name}: ${result.missing.length} manifest-tracked installed file(s) missing from disk (${result.missing.join(', ')})`, fix: `Run \`${cat.fixCommand}\` to restore` });
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
@@ -179,7 +179,7 @@ export function createCmdHealth(ctx) {
|
|
|
179
179
|
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
180
180
|
|
|
181
181
|
if (roadmap && existsSync(phasesDir)) {
|
|
182
|
-
for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.
|
|
182
|
+
for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.hasLifecycleArtifacts)) {
|
|
183
183
|
warnings.push({
|
|
184
184
|
id: 'W4',
|
|
185
185
|
severity: 'WARN',
|
|
@@ -201,16 +201,9 @@ export function createCmdHealth(ctx) {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
// W6: No adapter surfaces detected
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
join(cwd, '.claude'),
|
|
208
|
-
join(cwd, '.opencode'),
|
|
209
|
-
join(cwd, '.codex'),
|
|
210
|
-
];
|
|
211
|
-
const hasAnyAdapter = adapterPaths.some((p) => existsSync(p));
|
|
212
|
-
if (!hasAnyAdapter) {
|
|
213
|
-
warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `npx -y gsdd-cli init --tools <platform>`' });
|
|
204
|
+
// W6: No generated workflow adapter surfaces detected
|
|
205
|
+
if (!hasAnyGeneratedWorkflowSurface(cwd)) {
|
|
206
|
+
warnings.push({ id: 'W6', severity: 'WARN', message: 'No generated workflow adapter surfaces detected', fix: 'Run `npx -y gsdd-cli init --tools <platform>`' });
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
const runtimeFreshnessReport = configOk && Array.isArray(ctx.workflows)
|
|
@@ -245,15 +238,15 @@ export function createCmdHealth(ctx) {
|
|
|
245
238
|
});
|
|
246
239
|
}
|
|
247
240
|
|
|
248
|
-
// I3: Which
|
|
249
|
-
const
|
|
250
|
-
if (
|
|
251
|
-
if (
|
|
252
|
-
if (
|
|
253
|
-
if (
|
|
254
|
-
if (existsSync(join(cwd, 'AGENTS.md')))
|
|
255
|
-
if (
|
|
256
|
-
info.push({ id: 'I3', severity: 'INFO', message: `
|
|
241
|
+
// I3: Which runtime/governance surfaces are installed
|
|
242
|
+
const installedSurfaces = [];
|
|
243
|
+
if (hasGeneratedSkillSurface(cwd)) installedSurfaces.push('open-standard-skills');
|
|
244
|
+
if (hasGeneratedClaudeSurface(cwd)) installedSurfaces.push('claude');
|
|
245
|
+
if (hasGeneratedOpenCodeSurface(cwd)) installedSurfaces.push('opencode');
|
|
246
|
+
if (hasGeneratedCodexSurface(cwd)) installedSurfaces.push('codex');
|
|
247
|
+
if (existsSync(join(cwd, 'AGENTS.md'))) installedSurfaces.push('root AGENTS.md governance-only');
|
|
248
|
+
if (installedSurfaces.length > 0) {
|
|
249
|
+
info.push({ id: 'I3', severity: 'INFO', message: `Installed runtime/governance surfaces: ${installedSurfaces.join(', ')}` });
|
|
257
250
|
}
|
|
258
251
|
|
|
259
252
|
// --- Verdict ---
|
|
@@ -281,6 +274,78 @@ export function createCmdHealth(ctx) {
|
|
|
281
274
|
};
|
|
282
275
|
}
|
|
283
276
|
|
|
277
|
+
function hasAnyGeneratedWorkflowSurface(cwd) {
|
|
278
|
+
return hasGeneratedSkillSurface(cwd)
|
|
279
|
+
|| hasGeneratedClaudeEntrySurface(cwd)
|
|
280
|
+
|| hasGeneratedOpenCodeEntrySurface(cwd);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function hasGeneratedSkillSurface(cwd) {
|
|
284
|
+
return hasGeneratedSkillDirectory(join(cwd, '.agents', 'skills'));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hasGeneratedClaudeSurface(cwd) {
|
|
288
|
+
return hasGeneratedClaudeEntrySurface(cwd)
|
|
289
|
+
|| hasGeneratedMarkdownFile(join(cwd, '.claude', 'agents'));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function hasGeneratedClaudeEntrySurface(cwd) {
|
|
293
|
+
return hasGeneratedSkillDirectory(join(cwd, '.claude', 'skills'))
|
|
294
|
+
|| hasGeneratedMarkdownFile(join(cwd, '.claude', 'commands'));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function hasGeneratedOpenCodeSurface(cwd) {
|
|
298
|
+
return hasGeneratedOpenCodeEntrySurface(cwd)
|
|
299
|
+
|| hasGeneratedMarkdownFile(join(cwd, '.opencode', 'agents'));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function hasGeneratedOpenCodeEntrySurface(cwd) {
|
|
303
|
+
return hasGeneratedMarkdownFile(join(cwd, '.opencode', 'commands'))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function hasGeneratedCodexSurface(cwd) {
|
|
307
|
+
return hasGeneratedTomlFile(join(cwd, '.codex', 'agents'));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function hasGeneratedSkillDirectory(dir) {
|
|
311
|
+
try {
|
|
312
|
+
return readdirSync(dir, { withFileTypes: true }).some((entry) => {
|
|
313
|
+
return entry.isDirectory()
|
|
314
|
+
&& entry.name.startsWith('gsdd-')
|
|
315
|
+
&& existsSync(join(dir, entry.name, 'SKILL.md'));
|
|
316
|
+
});
|
|
317
|
+
} catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function hasGeneratedMarkdownFile(dir) {
|
|
323
|
+
try {
|
|
324
|
+
return readdirSync(dir, { withFileTypes: true }).some((entry) => {
|
|
325
|
+
return entry.isFile() && entry.name.startsWith('gsdd-') && entry.name.endsWith('.md');
|
|
326
|
+
});
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function hasGeneratedTomlFile(dir) {
|
|
333
|
+
try {
|
|
334
|
+
return readdirSync(dir, { withFileTypes: true }).some((entry) => {
|
|
335
|
+
return entry.isFile() && entry.name.startsWith('gsdd-') && entry.name.endsWith('.toml');
|
|
336
|
+
});
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
284
342
|
function isFrameworkSourceRepo(cwd) {
|
|
285
|
-
|
|
343
|
+
if (!existsSync(join(cwd, 'distilled', 'templates')) || !existsSync(join(cwd, 'distilled', 'workflows'))) return false;
|
|
344
|
+
if (!existsSync(join(cwd, 'bin', 'gsdd.mjs')) || !existsSync(join(cwd, 'package.json'))) return false;
|
|
345
|
+
try {
|
|
346
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
|
|
347
|
+
return pkg.name === 'gsdd-cli';
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
286
351
|
}
|
package/bin/lib/init-runtime.mjs
CHANGED
|
@@ -186,6 +186,7 @@ Commands:
|
|
|
186
186
|
phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])
|
|
187
187
|
lifecycle-preflight <surface> [phase]
|
|
188
188
|
Inspect deterministic lifecycle gate results for a workflow surface
|
|
189
|
+
session-fingerprint write Rebaseline planning-state drift after reviewing changed planning files
|
|
189
190
|
help Show this summary
|
|
190
191
|
|
|
191
192
|
Platforms (for --tools):
|
|
@@ -254,6 +255,7 @@ Starting lanes after init:
|
|
|
254
255
|
|
|
255
256
|
Advanced/internal helpers (kept available, but not the primary first-run user story):
|
|
256
257
|
lifecycle-preflight Inspect deterministic lifecycle gate results for a workflow surface
|
|
258
|
+
session-fingerprint Rebaseline the local planning-state fingerprint after review
|
|
257
259
|
phase-status Update ROADMAP.md phase status through the local helper surface
|
|
258
260
|
file-op Deterministic workspace-confined file copy/delete/text mutation
|
|
259
261
|
`;
|