gsdd-cli 0.27.0 → 0.29.0
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 +53 -22
- package/agents/DISTILLATION.md +2 -2
- package/agents/README.md +4 -4
- package/agents/approach-explorer.md +4 -4
- package/agents/executor.md +20 -20
- package/agents/integration-checker.md +2 -2
- package/agents/planner.md +10 -26
- package/agents/researcher.md +2 -2
- package/agents/roadmapper.md +6 -6
- package/agents/synthesizer.md +18 -18
- package/agents/verifier.md +3 -3
- package/bin/adapters/agents.mjs +3 -3
- package/bin/adapters/claude.mjs +16 -14
- package/bin/adapters/opencode.mjs +15 -12
- package/bin/gsdd.mjs +16 -13
- package/bin/lib/{models.mjs → config.mjs} +23 -16
- package/bin/lib/control-map.mjs +17 -488
- package/bin/lib/health-truth.mjs +8 -13
- package/bin/lib/health.mjs +25 -39
- package/bin/lib/init-flow.mjs +44 -38
- package/bin/lib/init-prompts.mjs +3 -3
- package/bin/lib/init-runtime.mjs +11 -30
- package/bin/lib/lifecycle-preflight.mjs +97 -410
- package/bin/lib/lifecycle-state.mjs +2 -1
- package/bin/lib/next.mjs +243 -20
- package/bin/lib/phase.mjs +706 -280
- package/bin/lib/plan-constants.mjs +0 -5
- package/bin/lib/rendering.mjs +64 -44
- package/bin/lib/runtime-freshness.mjs +18 -15
- package/bin/lib/state-dir.mjs +45 -0
- package/bin/lib/templates.mjs +59 -22
- package/bin/lib/work-context.mjs +12 -1
- package/bin/lib/workflows.mjs +0 -1
- package/bin/lib/workspace-root.mjs +11 -6
- package/distilled/DESIGN.md +89 -31
- package/distilled/EVIDENCE-INDEX.md +18 -5
- package/distilled/README.md +23 -33
- package/distilled/SKILL.md +9 -10
- package/distilled/templates/agents.block.md +5 -5
- package/distilled/templates/approach.md +3 -3
- package/distilled/templates/auth-matrix.md +2 -2
- package/distilled/templates/brownfield-change/CHANGE.md +1 -1
- package/distilled/templates/delegates/approach-explorer.md +5 -5
- package/distilled/templates/delegates/mapper-arch.md +3 -3
- package/distilled/templates/delegates/mapper-concerns.md +4 -4
- package/distilled/templates/delegates/mapper-quality.md +3 -3
- package/distilled/templates/delegates/mapper-tech.md +3 -3
- package/distilled/templates/delegates/plan-checker.md +18 -19
- package/distilled/templates/delegates/researcher-architecture.md +3 -3
- package/distilled/templates/delegates/researcher-features.md +3 -3
- package/distilled/templates/delegates/researcher-pitfalls.md +3 -3
- package/distilled/templates/delegates/researcher-stack.md +3 -3
- package/distilled/templates/delegates/researcher-synthesizer.md +7 -7
- package/distilled/templates/research/architecture.md +1 -1
- package/distilled/templates/research/pitfalls.md +1 -1
- package/distilled/templates/research/stack.md +1 -1
- package/distilled/templates/roadmap.md +4 -4
- package/distilled/templates/spec.md +2 -2
- package/distilled/templates/ui-proof.md +81 -181
- package/distilled/workflows/audit-milestone.md +23 -23
- package/distilled/workflows/complete-milestone.md +35 -35
- package/distilled/workflows/execute.md +34 -35
- package/distilled/workflows/map-codebase.md +30 -30
- package/distilled/workflows/new-milestone.md +18 -18
- package/distilled/workflows/new-project.md +45 -45
- package/distilled/workflows/pause.md +15 -15
- package/distilled/workflows/plan.md +106 -114
- package/distilled/workflows/progress.md +40 -39
- package/distilled/workflows/quick.md +49 -50
- package/distilled/workflows/resume.md +23 -22
- package/distilled/workflows/verify-work.md +7 -7
- package/distilled/workflows/verify.md +26 -26
- package/docs/BROWNFIELD-PROOF.md +1 -1
- package/docs/RUNTIME-SUPPORT.md +13 -13
- package/docs/USER-GUIDE.md +26 -21
- package/docs/claude/context-monitor.md +1 -1
- package/docs/proof/consumer-node-cli/README.md +1 -1
- package/package.json +3 -3
- package/bin/lib/closeout-report.mjs +0 -318
- package/bin/lib/evidence-contract.mjs +0 -325
- package/bin/lib/provenance.mjs +0 -390
- package/bin/lib/session-fingerprint.mjs +0 -223
- package/bin/lib/ui-proof.mjs +0 -1007
- package/distilled/workflows/plan-milestone-gaps.md +0 -204
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
// closeout-report.mjs - read-only post-merge closure replay report
|
|
2
|
-
|
|
3
|
-
import { output, parseFlagValue } from './cli-utils.mjs';
|
|
4
|
-
import { buildControlMap } from './control-map.mjs';
|
|
5
|
-
import { evaluateLifecyclePreflight } from './lifecycle-preflight.mjs';
|
|
6
|
-
import { evaluateLifecycleState, normalizePhaseToken } from './lifecycle-state.mjs';
|
|
7
|
-
import { buildPhaseVerificationReport } from './phase.mjs';
|
|
8
|
-
import { resolveWorkspaceContext } from './workspace-root.mjs';
|
|
9
|
-
|
|
10
|
-
const USAGE = 'Usage: gsdd closeout-report [--json] [--phase <N>]';
|
|
11
|
-
|
|
12
|
-
function severityFromRisk(risk) {
|
|
13
|
-
if (risk.severity === 'block') return 'blocker';
|
|
14
|
-
if (risk.severity === 'warn') return 'warn';
|
|
15
|
-
return risk.severity || 'info';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function notice(source, severity, entry) {
|
|
19
|
-
return {
|
|
20
|
-
source,
|
|
21
|
-
severity,
|
|
22
|
-
code: entry.code || entry.id || 'unknown',
|
|
23
|
-
message: entry.message,
|
|
24
|
-
fix: entry.fix_hint || entry.fix || null,
|
|
25
|
-
path: entry.path || null,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function latestCompletedPhase(lifecycle) {
|
|
30
|
-
const completed = lifecycle.phases.filter((phase) => phase.status === 'done');
|
|
31
|
-
return completed.length > 0 ? completed[completed.length - 1] : null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function buildHealthReportSafe(ctx, args) {
|
|
35
|
-
try {
|
|
36
|
-
const health = await import('./health.mjs');
|
|
37
|
-
return health.buildHealthReport(ctx, args);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return {
|
|
40
|
-
status: 'degraded',
|
|
41
|
-
errors: [],
|
|
42
|
-
warnings: [{
|
|
43
|
-
id: 'W_CLOSEOUT_HEALTH_UNAVAILABLE',
|
|
44
|
-
severity: 'WARN',
|
|
45
|
-
message: `Health report builder unavailable in this helper runtime: ${error.message}`,
|
|
46
|
-
fix: 'Run the source CLI `gsdd health --json` for full health diagnostics, or refresh generated helper support.',
|
|
47
|
-
}],
|
|
48
|
-
info: [],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function summarizeControlMap(map) {
|
|
54
|
-
return {
|
|
55
|
-
status: map.risks.some((risk) => risk.severity === 'block')
|
|
56
|
-
? 'blocked'
|
|
57
|
-
: map.risks.some((risk) => risk.severity === 'warn')
|
|
58
|
-
? 'warnings'
|
|
59
|
-
: 'clear',
|
|
60
|
-
authority: map.authority,
|
|
61
|
-
canonical_worktree: {
|
|
62
|
-
branch: map.canonical_worktree.branch,
|
|
63
|
-
head: map.canonical_worktree.head,
|
|
64
|
-
dirty: map.canonical_worktree.dirty,
|
|
65
|
-
ahead_behind: map.canonical_worktree.ahead_behind,
|
|
66
|
-
},
|
|
67
|
-
worktree_count: map.worktrees.length,
|
|
68
|
-
risks: map.risks,
|
|
69
|
-
interventions: map.interventions,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function summarizePreflight(preflight) {
|
|
74
|
-
return {
|
|
75
|
-
status: preflight.status,
|
|
76
|
-
allowed: preflight.allowed,
|
|
77
|
-
reason: preflight.reason,
|
|
78
|
-
blockers: preflight.blockers,
|
|
79
|
-
warnings: preflight.warnings,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function summarizePhaseVerification(report) {
|
|
84
|
-
if (!report.ok) {
|
|
85
|
-
return {
|
|
86
|
-
status: 'blocked',
|
|
87
|
-
verified: false,
|
|
88
|
-
error: report.error,
|
|
89
|
-
blocked_on: ['verification'],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
status: report.result.verified ? 'passed' : 'blocked',
|
|
94
|
-
verified: report.result.verified,
|
|
95
|
-
phase_artifacts_present: report.result.phase_artifacts_present,
|
|
96
|
-
prerequisite_status: report.result.prerequisite_status,
|
|
97
|
-
artifact_status: report.result.artifact_status,
|
|
98
|
-
blocked_on: report.result.blocked_on,
|
|
99
|
-
blocks_verification: report.result.blocks_verification,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function collectBlockers({ health, preflight, phaseReport }) {
|
|
104
|
-
const blockers = [];
|
|
105
|
-
const uiProofErrors = phaseReport.result?.uiProof?.errors || [];
|
|
106
|
-
const uiProofGate = phaseReport.result?.ui_proof || {};
|
|
107
|
-
|
|
108
|
-
blockers.push(...health.errors.map((entry) => notice('health', 'blocker', entry)));
|
|
109
|
-
blockers.push(...preflight.blockers.map((entry) => notice('preflight', 'blocker', entry)));
|
|
110
|
-
|
|
111
|
-
if (!phaseReport.ok) {
|
|
112
|
-
blockers.push(notice('phase_verification', 'blocker', {
|
|
113
|
-
code: 'phase_verification_error',
|
|
114
|
-
message: phaseReport.error,
|
|
115
|
-
fix_hint: 'Run the direct verify command for the selected phase and repair the reported prerequisite.',
|
|
116
|
-
}));
|
|
117
|
-
} else {
|
|
118
|
-
for (const blockerEntry of phaseReport.result.prerequisite_status.blockers || []) {
|
|
119
|
-
blockers.push(notice('phase_verification', 'blocker', blockerEntry));
|
|
120
|
-
}
|
|
121
|
-
for (const artifact of phaseReport.result.artifact_status.unsatisfied || []) {
|
|
122
|
-
blockers.push(notice('phase_verification', 'blocker', {
|
|
123
|
-
code: 'unsatisfied_plan_artifact',
|
|
124
|
-
message: `${artifact.file} is not ${artifact.expected}.`,
|
|
125
|
-
fix_hint: artifact.fix_hint,
|
|
126
|
-
path: artifact.file,
|
|
127
|
-
}));
|
|
128
|
-
}
|
|
129
|
-
for (const entry of uiProofErrors) {
|
|
130
|
-
blockers.push(notice('ui_proof', entry.severity === 'warn' ? 'warn' : 'blocker', entry));
|
|
131
|
-
}
|
|
132
|
-
if (uiProofErrors.length === 0 && uiProofGate.blocks_verification) {
|
|
133
|
-
blockers.push(notice('ui_proof', 'blocker', {
|
|
134
|
-
code: uiProofGate.required_block || 'ui_proof_verification_failed',
|
|
135
|
-
message: `UI proof verification is required for phase ${phaseReport.result.phase} but reported status ${uiProofGate.status}.`,
|
|
136
|
-
fix_hint: 'Run the phase-specific UI proof checks and supply a passing observed proof bundle for each declared slot.',
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return blockers.filter((entry) => entry.severity === 'blocker');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function collectWarnings({ controlMap, health, preflight, phaseReport }) {
|
|
145
|
-
const warnings = [];
|
|
146
|
-
warnings.push(...controlMap.risks
|
|
147
|
-
.filter((risk) => risk.severity !== 'block')
|
|
148
|
-
.map((risk) => notice('control_map', severityFromRisk(risk), risk)));
|
|
149
|
-
warnings.push(...health.warnings.map((entry) => notice('health', 'warn', entry)));
|
|
150
|
-
warnings.push(...preflight.warnings
|
|
151
|
-
.filter((entry) => entry.source !== 'control-map')
|
|
152
|
-
.map((entry) => notice('preflight', entry.severity === 'info' ? 'info' : 'warn', entry)));
|
|
153
|
-
if (phaseReport.ok) {
|
|
154
|
-
warnings.push(...(phaseReport.result.uiProof?.warnings || []).map((entry) => notice('ui_proof', 'warn', entry)));
|
|
155
|
-
}
|
|
156
|
-
return warnings;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function nextSafeAction({ blockers, warnings, phaseNumber }) {
|
|
160
|
-
if (blockers.length > 0) {
|
|
161
|
-
return {
|
|
162
|
-
command: `gsdd verify ${phaseNumber}`,
|
|
163
|
-
reason: 'Fix blockers first, then re-run closeout replay.',
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const hasWarn = warnings.some((entry) => entry.severity === 'warn');
|
|
167
|
-
if (warnings.length > 0 && hasWarn) {
|
|
168
|
-
const sources = new Set(warnings.filter((entry) => entry.severity === 'warn').map((entry) => entry.source));
|
|
169
|
-
if (sources.has('health')) {
|
|
170
|
-
return {
|
|
171
|
-
command: 'gsdd health --json',
|
|
172
|
-
reason: 'Resolve workspace health warnings before claiming the environment is clean.',
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (sources.has('ui_proof') || sources.has('phase_verification')) {
|
|
176
|
-
return {
|
|
177
|
-
command: `gsdd verify ${phaseNumber}`,
|
|
178
|
-
reason: 'Resolve phase verification warnings before claiming closeout is replay-clean.',
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
return {
|
|
182
|
-
command: 'gsdd control-map --json',
|
|
183
|
-
reason: 'Resolve local state warnings before claiming the environment is clean.',
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
if (warnings.length > 0) {
|
|
187
|
-
return {
|
|
188
|
-
command: 'gsdd control-map --json',
|
|
189
|
-
reason: 'Review the informational notices before claiming the local environment is clean.',
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
command: `gsdd verify ${phaseNumber}`,
|
|
194
|
-
reason: 'Closeout replay is clean; run formal verification for closure if it has not already been recorded.',
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function buildCloseoutReport(ctx = {}, args = []) {
|
|
199
|
-
const context = resolveWorkspaceContext(args, { cwd: ctx.cwd || process.cwd() });
|
|
200
|
-
if (context.invalid) {
|
|
201
|
-
return {
|
|
202
|
-
ok: false,
|
|
203
|
-
error: context.error,
|
|
204
|
-
exitCode: 1,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const phaseArg = parseFlagValue(context.args, '--phase');
|
|
209
|
-
if (phaseArg.invalid) {
|
|
210
|
-
return { ok: false, error: USAGE, exitCode: 1 };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const filteredArgs = context.args.filter((arg, index) => {
|
|
214
|
-
if (arg === '--json') return false;
|
|
215
|
-
if (arg === '--phase') return false;
|
|
216
|
-
if (index > 0 && context.args[index - 1] === '--phase') return false;
|
|
217
|
-
return true;
|
|
218
|
-
});
|
|
219
|
-
if (filteredArgs.length > 0) {
|
|
220
|
-
return { ok: false, error: USAGE, exitCode: 1 };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const lifecycle = evaluateLifecycleState({ planningDir: context.planningDir });
|
|
224
|
-
const selectedPhase = phaseArg.present
|
|
225
|
-
? normalizePhaseToken(phaseArg.value)
|
|
226
|
-
: latestCompletedPhase(lifecycle)?.number || null;
|
|
227
|
-
if (!selectedPhase) {
|
|
228
|
-
return {
|
|
229
|
-
ok: false,
|
|
230
|
-
error: 'No completed phase found. Pass --phase <N> to replay a specific phase.',
|
|
231
|
-
exitCode: 1,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const controlMap = buildControlMap({
|
|
236
|
-
workspaceRoot: context.workspaceRoot,
|
|
237
|
-
planningDir: context.planningDir,
|
|
238
|
-
});
|
|
239
|
-
const health = await buildHealthReportSafe(ctx, ['--workspace-root', context.workspaceRoot]);
|
|
240
|
-
const preflight = evaluateLifecyclePreflight({
|
|
241
|
-
planningDir: context.planningDir,
|
|
242
|
-
surface: 'verify',
|
|
243
|
-
phaseNumber: selectedPhase,
|
|
244
|
-
expectsMutation: 'phase-status',
|
|
245
|
-
controlMapReport: controlMap,
|
|
246
|
-
});
|
|
247
|
-
const phaseReport = buildPhaseVerificationReport('--workspace-root', context.workspaceRoot, selectedPhase);
|
|
248
|
-
const blockers = collectBlockers({ health, preflight, phaseReport });
|
|
249
|
-
const warnings = collectWarnings({ controlMap, health, preflight, phaseReport });
|
|
250
|
-
const status = blockers.length > 0 ? 'blocked' : warnings.length > 0 ? 'warnings' : 'clear';
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
ok: true,
|
|
254
|
-
exitCode: blockers.length > 0 ? 1 : 0,
|
|
255
|
-
result: {
|
|
256
|
-
schema_version: 1,
|
|
257
|
-
operation: 'closeout-report',
|
|
258
|
-
generated_at: new Date().toISOString(),
|
|
259
|
-
workspace_root: context.workspaceRoot.replace(/\\/g, '/'),
|
|
260
|
-
phase: selectedPhase,
|
|
261
|
-
scope: {
|
|
262
|
-
defaulted_to_latest_completed: !phaseArg.present,
|
|
263
|
-
latest_completed_phase: latestCompletedPhase(lifecycle)?.number || null,
|
|
264
|
-
},
|
|
265
|
-
status,
|
|
266
|
-
blockers,
|
|
267
|
-
warnings,
|
|
268
|
-
next_safe_action: nextSafeAction({ blockers, warnings, phaseNumber: selectedPhase }),
|
|
269
|
-
control_map: summarizeControlMap(controlMap),
|
|
270
|
-
health: {
|
|
271
|
-
status: health.status,
|
|
272
|
-
errors: health.errors,
|
|
273
|
-
warnings: health.warnings,
|
|
274
|
-
info: health.info,
|
|
275
|
-
},
|
|
276
|
-
preflight: summarizePreflight(preflight),
|
|
277
|
-
phase_verification: summarizePhaseVerification(phaseReport),
|
|
278
|
-
ui_proof: phaseReport.ok ? phaseReport.result.ui_proof : null,
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function printHuman(report) {
|
|
284
|
-
console.log('gsdd closeout-report - read-only closure replay\n');
|
|
285
|
-
console.log(`Phase: ${report.phase}`);
|
|
286
|
-
console.log(`Status: ${report.status}`);
|
|
287
|
-
if (report.blockers.length > 0) {
|
|
288
|
-
console.log('\nBlockers:');
|
|
289
|
-
for (const blocker of report.blockers) {
|
|
290
|
-
console.log(` - [${blocker.source}] ${blocker.code}: ${blocker.message}`);
|
|
291
|
-
if (blocker.fix) console.log(` Fix: ${blocker.fix}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (report.warnings.length > 0) {
|
|
295
|
-
console.log('\nWarnings:');
|
|
296
|
-
for (const warning of report.warnings) {
|
|
297
|
-
console.log(` - [${warning.source}] ${warning.code}: ${warning.message}`);
|
|
298
|
-
if (warning.fix) console.log(` Fix: ${warning.fix}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
console.log(`\nNext safe action: ${report.next_safe_action.command}`);
|
|
302
|
-
console.log(`Reason: ${report.next_safe_action.reason}`);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function createCmdCloseoutReport(ctx = {}) {
|
|
306
|
-
return async function cmdCloseoutReport(...args) {
|
|
307
|
-
const jsonMode = args.includes('--json');
|
|
308
|
-
const report = await buildCloseoutReport(ctx, args);
|
|
309
|
-
if (!report.ok) {
|
|
310
|
-
console.error(report.error);
|
|
311
|
-
process.exitCode = report.exitCode;
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (jsonMode) output(report.result);
|
|
315
|
-
else printHuman(report.result);
|
|
316
|
-
if (report.exitCode !== 0) process.exitCode = report.exitCode;
|
|
317
|
-
};
|
|
318
|
-
}
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
const EVIDENCE_KINDS = Object.freeze(['code', 'test', 'runtime', 'delivery', 'human']);
|
|
2
|
-
const DELIVERY_POSTURES = Object.freeze(['repo_only', 'delivery_sensitive']);
|
|
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
|
-
]);
|
|
9
|
-
|
|
10
|
-
const LEGACY_EVIDENCE_ALIASES = Object.freeze({
|
|
11
|
-
code: 'code',
|
|
12
|
-
test: 'test',
|
|
13
|
-
runtime: 'runtime',
|
|
14
|
-
delivery: 'delivery',
|
|
15
|
-
human: 'human',
|
|
16
|
-
'code-evidence': 'code',
|
|
17
|
-
'repo-test': 'test',
|
|
18
|
-
'runtime-check': 'runtime',
|
|
19
|
-
'user-confirmation': 'human',
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const EVIDENCE_MATRIX = Object.freeze({
|
|
23
|
-
verify: Object.freeze({
|
|
24
|
-
repo_only: Object.freeze({
|
|
25
|
-
requiredKinds: Object.freeze(['code']),
|
|
26
|
-
recommendedKinds: Object.freeze(['test']),
|
|
27
|
-
blockedSoloKinds: Object.freeze(['human', 'delivery']),
|
|
28
|
-
}),
|
|
29
|
-
delivery_sensitive: Object.freeze({
|
|
30
|
-
requiredKinds: Object.freeze(['code', 'runtime', 'delivery']),
|
|
31
|
-
recommendedKinds: Object.freeze(['test', 'human']),
|
|
32
|
-
blockedSoloKinds: Object.freeze(['code', 'human']),
|
|
33
|
-
}),
|
|
34
|
-
}),
|
|
35
|
-
'audit-milestone': Object.freeze({
|
|
36
|
-
repo_only: Object.freeze({
|
|
37
|
-
requiredKinds: Object.freeze(['code', 'test']),
|
|
38
|
-
recommendedKinds: Object.freeze(['runtime', 'human']),
|
|
39
|
-
blockedSoloKinds: Object.freeze(['human', 'delivery']),
|
|
40
|
-
}),
|
|
41
|
-
delivery_sensitive: Object.freeze({
|
|
42
|
-
requiredKinds: Object.freeze(['code', 'test', 'runtime', 'delivery']),
|
|
43
|
-
recommendedKinds: Object.freeze(['human']),
|
|
44
|
-
blockedSoloKinds: Object.freeze(['code', 'human']),
|
|
45
|
-
}),
|
|
46
|
-
}),
|
|
47
|
-
'complete-milestone': Object.freeze({
|
|
48
|
-
repo_only: Object.freeze({
|
|
49
|
-
requiredKinds: Object.freeze(['code', 'test']),
|
|
50
|
-
recommendedKinds: Object.freeze(['runtime']),
|
|
51
|
-
blockedSoloKinds: Object.freeze(['human', 'delivery']),
|
|
52
|
-
}),
|
|
53
|
-
delivery_sensitive: Object.freeze({
|
|
54
|
-
requiredKinds: Object.freeze(['code', 'test', 'runtime', 'delivery']),
|
|
55
|
-
recommendedKinds: Object.freeze(['human']),
|
|
56
|
-
blockedSoloKinds: Object.freeze(['code', 'human']),
|
|
57
|
-
}),
|
|
58
|
-
}),
|
|
59
|
-
});
|
|
60
|
-
|
|
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 };
|
|
100
|
-
|
|
101
|
-
export function normalizeEvidenceKind(kind) {
|
|
102
|
-
if (!kind) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return LEGACY_EVIDENCE_ALIASES[kind] ?? null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function normalizeEvidenceKinds(kinds = []) {
|
|
110
|
-
const normalized = [];
|
|
111
|
-
for (const kind of kinds) {
|
|
112
|
-
const resolved = normalizeEvidenceKind(kind);
|
|
113
|
-
if (resolved && !normalized.includes(resolved)) {
|
|
114
|
-
normalized.push(resolved);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return normalized;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function isClosureSurface(surface) {
|
|
121
|
-
return CLOSURE_SURFACES.includes(surface);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function normalizeReleaseClaimPosture(posture) {
|
|
125
|
-
if (!posture) return 'repo_closeout';
|
|
126
|
-
return RELEASE_CLAIM_POSTURES.includes(posture) ? posture : null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function getEvidenceContract(surface, deliveryPosture) {
|
|
130
|
-
const matrix = EVIDENCE_MATRIX[surface];
|
|
131
|
-
if (!matrix) {
|
|
132
|
-
throw new Error(`Unsupported closure evidence surface: ${surface}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const posture = matrix[deliveryPosture];
|
|
136
|
-
if (!posture) {
|
|
137
|
-
throw new Error(`Unsupported delivery posture for ${surface}: ${deliveryPosture}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
surface,
|
|
142
|
-
deliveryPosture,
|
|
143
|
-
supportedKinds: [...EVIDENCE_KINDS],
|
|
144
|
-
requiredKinds: [...posture.requiredKinds],
|
|
145
|
-
recommendedKinds: [...posture.recommendedKinds],
|
|
146
|
-
blockedSoloKinds: [...posture.blockedSoloKinds],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function describeEvidenceSurface(surface) {
|
|
151
|
-
if (!isClosureSurface(surface)) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
surface,
|
|
157
|
-
supportedKinds: [...EVIDENCE_KINDS],
|
|
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',
|
|
302
|
-
};
|
|
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
|
-
}
|