create-agdf 0.9.1 → 0.9.5

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.
Files changed (37) hide show
  1. package/README.md +33 -4
  2. package/bin/create-agdf.js +2 -3333
  3. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +3 -3
  4. package/generated/.github/skills/agdf-gate-check/SKILL.md +5 -0
  5. package/generated/.github/skills/agdf-interaction-locales.json +36 -0
  6. package/generated/.github/skills/contracts/interaction.md +9 -1
  7. package/generated/.opencode/agdf-interaction-locales.json +36 -0
  8. package/generated/.opencode/contracts/interaction.md +9 -1
  9. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +5 -0
  10. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  11. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +3 -3
  12. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +36 -0
  13. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  14. package/generated/plugins/agdf/meta/contracts/interaction.md +9 -1
  15. package/generated/plugins/agdf/skills/gate-check/SKILL.md +5 -0
  16. package/lib/cli/application.js +342 -0
  17. package/lib/cli/command-registry.js +119 -0
  18. package/lib/cli/delivery-path-search-command.js +75 -0
  19. package/lib/cli/parse-args.js +150 -0
  20. package/lib/cli/runtime-context.js +83 -0
  21. package/lib/control-evaluation/delivery-map.js +291 -0
  22. package/lib/control-evaluation/doctor.js +265 -0
  23. package/lib/control-evaluation/gate-check.js +404 -0
  24. package/lib/control-evaluation/gate-policy.js +283 -0
  25. package/lib/control-evaluation/run-state.js +217 -0
  26. package/lib/control-evaluation/shared.js +344 -0
  27. package/lib/control-evaluation/verified-change.js +156 -0
  28. package/lib/installers/opencode.js +443 -0
  29. package/lib/installers/plugin-installers.js +99 -0
  30. package/lib/lifecycle/operations.js +177 -0
  31. package/lib/lifecycle/presentation.js +52 -0
  32. package/lib/lifecycle/result.js +69 -0
  33. package/lib/lifecycle/status.js +108 -0
  34. package/lib/scaffold/plan.js +278 -0
  35. package/lib/scaffold/presentation.js +98 -0
  36. package/lib/scaffold/write.js +45 -0
  37. package/package.json +4 -2
@@ -0,0 +1,404 @@
1
+ import { attachApprovalOrientationSnapshot, buildArtefactRefs, buildQualityReadiness, formatArtefactRefs, gateTitle, localePack, resolveHumanRunTitle } from '../interaction-presentation.js';
2
+ import { interactionLocales, resolveConfiguredChatLanguage } from '../cli/runtime-context.js';
3
+ import { evaluateDoctor } from './doctor.js';
4
+ import { analyzeDeliveryMap, deriveQualityOutlook } from './delivery-map.js';
5
+ import { transitionDecisionForRunState } from './gate-policy.js';
6
+ import { evaluateVerifiedChange, extractField, verifiedChangeEscalationTargets } from './verified-change.js';
7
+ import { gateApprovalStatus, modeSliceDecision, readArtefactHeading, readRunState, resolvedArtefactFile } from './run-state.js';
8
+ import { isPlaceholderValue } from './shared.js';
9
+
10
+ const nextSkillByGate = {
11
+ UR: "gate-check",
12
+ "Brownfield Review": "brownfield-analysis",
13
+ "Mode/Slice Decision": "gate-check",
14
+ "Quick Task Execution": "none",
15
+ "Verified Change Execution": "gate-check",
16
+ PRD: "gate-check",
17
+ SD: "gate-check",
18
+ TP: "gate-check",
19
+ "Brownfield Analysis": "brownfield-analysis",
20
+ "CD+Tests": "none",
21
+ CR: "code-review",
22
+ QA: "qa-gate",
23
+ UAT: "delivery-closeout",
24
+ OR: "release-or",
25
+ };
26
+
27
+ export function postApprovalTransition(missingApproval) {
28
+ const transitions = new Map([
29
+ ["Approval: UR", {
30
+ next_gate_after_approval: "Brownfield Review",
31
+ allowed_after_approval: "Run Brownfield Review and record Mode/Slice Decision before PRD or implementation.",
32
+ internal_next_step: "Brownfield Review and Mode/Slice Decision",
33
+ next_user_gate: "PRD",
34
+ user_action_required: "yes",
35
+ }],
36
+ ["Approval: PRD", {
37
+ next_gate_after_approval: "SD",
38
+ allowed_after_approval: "Draft Solution Design; implementation remains forbidden.",
39
+ internal_next_step: "draft Solution Design",
40
+ next_user_gate: "SD",
41
+ user_action_required: "yes",
42
+ }],
43
+ ["Approval: SD", {
44
+ next_gate_after_approval: "TP",
45
+ allowed_after_approval: "Draft Task/Test Plan; implementation remains forbidden.",
46
+ internal_next_step: "draft Task/Test Plan",
47
+ next_user_gate: "TP",
48
+ user_action_required: "yes",
49
+ }],
50
+ ["Approval: TP", {
51
+ next_gate_after_approval: "none",
52
+ allowed_after_approval: "Run implementation-prep Brownfield Analysis before CD+Tests; no further user approval is required at this internal step.",
53
+ internal_next_step: "pre-implementation Brownfield Analysis",
54
+ next_user_gate: "none",
55
+ user_action_required: "no",
56
+ }],
57
+ ["Approval: QA", {
58
+ next_gate_after_approval: "UAT",
59
+ allowed_after_approval: "Request UAT when QA has passed; release remains gated.",
60
+ internal_next_step: "prepare UAT evidence",
61
+ next_user_gate: "UAT",
62
+ user_action_required: "yes",
63
+ }],
64
+ ["Approval: UAT", {
65
+ next_gate_after_approval: "OR",
66
+ allowed_after_approval: "Produce OR or delivery closeout; VCS and release actions still require explicit instruction.",
67
+ internal_next_step: "OR or delivery closeout",
68
+ next_user_gate: "none",
69
+ user_action_required: "no",
70
+ }],
71
+ ]);
72
+
73
+ return transitions.get(missingApproval) ?? {
74
+ next_gate_after_approval: "none",
75
+ allowed_after_approval: "none",
76
+ };
77
+ }
78
+
79
+ const BREADCRUMB_PATH_TEMPLATES = {
80
+ structured_delivery: ["UR", "PRD", "SD", "TP", "QA", "UAT"],
81
+ structured_slice: ["UR", "PRD", "SD", "TP", "QA", "UAT"],
82
+ verified_change: ["UR", "Verified Change", "OR"],
83
+ quick_task: ["UR", "Quick Task"],
84
+ block: ["UR", "Block"],
85
+ undecided: ["UR"],
86
+ };
87
+
88
+ const BREADCRUMB_STANDARD_GATES = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
89
+
90
+ export function isReadyUserGateApproval({ status, currentGate, missingApproval }) {
91
+ return status === "open"
92
+ && BREADCRUMB_STANDARD_GATES.includes(currentGate)
93
+ && missingApproval === `Approval: ${currentGate}`;
94
+ }
95
+
96
+ function buildBreadcrumbPath(modeSliceDecision, currentGate, runState) {
97
+ const gates = BREADCRUMB_PATH_TEMPLATES[modeSliceDecision] ?? ["UR"];
98
+ return gates.map((gate) => {
99
+ if (BREADCRUMB_STANDARD_GATES.includes(gate)) {
100
+ if (gateApprovalStatus(runState, gate) === "approved") return { gate, status: "fulfilled" };
101
+ if (gate === currentGate) return { gate, status: "current" };
102
+ return { gate, status: "open" };
103
+ }
104
+ if (gate === currentGate) return { gate, status: "current" };
105
+ if ((gate === "Verified Change" || gate === "Quick Task") && currentGate === "OR")
106
+ return { gate, status: "fulfilled" };
107
+ if (gate === "Verified Change" && modeSliceDecision === "verified_change")
108
+ return { gate, status: "current" };
109
+ if (gate === "Quick Task" && modeSliceDecision === "quick_task")
110
+ return { gate, status: "current" };
111
+ if (gate === "Block" && modeSliceDecision === "block")
112
+ return { gate, status: "current" };
113
+ if (gate === "OR" && currentGate === "OR")
114
+ return { gate, status: "current" };
115
+ return { gate, status: "open" };
116
+ });
117
+ }
118
+
119
+ export function buildStatusCard({
120
+ status,
121
+ currentGate,
122
+ allowed = [],
123
+ forbidden = [],
124
+ blockingReason = "none",
125
+ missingApproval = "none",
126
+ nextAllowedAction,
127
+ runState,
128
+ chatLanguage = "en",
129
+ findings = [],
130
+ }) {
131
+ const qualityOutlook = deriveQualityOutlook(runState, findings);
132
+ const postApproval = postApprovalTransition(missingApproval);
133
+ const isUserGateApproval = isReadyUserGateApproval({ status, currentGate, missingApproval });
134
+ const lifecycle = extractField(runState.content ?? "", "lifecycle") || "unknown";
135
+ const nativeAttemptRequired = false;
136
+ const interactionKind = status === "open" && isUserGateApproval ? "gate_approval" : status === "blocked" ? "blocked" : "status";
137
+ return {
138
+ run_id: extractField(runState.content ?? "", "run_id") || "unknown",
139
+ presentation_language: chatLanguage,
140
+ mode: extractField(runState.content ?? "", "mode") || "unknown",
141
+ lifecycle,
142
+ delivery_state: lifecycle === "completed" && currentGate === "OR" ? "completed_closeout_pending" : lifecycle,
143
+ status,
144
+ current_gate: currentGate,
145
+ mode_slice_decision: runState.mode_slice_decision?.decision || "undecided",
146
+ breadcrumb: buildBreadcrumbPath(
147
+ runState.mode_slice_decision?.decision || "undecided",
148
+ currentGate,
149
+ runState,
150
+ ),
151
+ allowed_now: allowed,
152
+ forbidden_now: forbidden,
153
+ blocking_condition: blockingReason || "none",
154
+ missing_approval: missingApproval || "none",
155
+ next_gate_after_approval: postApproval.next_gate_after_approval,
156
+ allowed_after_approval: postApproval.allowed_after_approval,
157
+ user_visible_outcome_after_approval: postApproval.allowed_after_approval,
158
+ internal_next_step: postApproval.internal_next_step || (isUserGateApproval ? "none" : nextAllowedAction),
159
+ next_user_gate: postApproval.next_user_gate || "none",
160
+ user_action_required: postApproval.user_action_required || (isUserGateApproval ? "yes" : "no"),
161
+ evidence: runState.evidence_refs,
162
+ next_skill: nextSkillByGate[currentGate] ?? "gate-check",
163
+ next_step: nextAllowedAction,
164
+ quality_outlook: qualityOutlook,
165
+ interaction_kind: interactionKind,
166
+ native_attempt_required: nativeAttemptRequired,
167
+ native_preflight_outcome: isUserGateApproval ? "unavailable_before_invocation" : "not_applicable",
168
+ native_preflight_reason: isUserGateApproval ? "capability_missing" : "none",
169
+ };
170
+ }
171
+
172
+ function statusFromReviewArtefact(artefact) {
173
+ if (!artefact) return "unknown";
174
+ const text = `${artefact.status ?? ""} ${artefact.notes ?? ""}`.toLowerCase();
175
+ for (const status of ["block", "revise", "warn", "pass"]) {
176
+ if (new RegExp(`\\b${status}(?:ed)?\\b`).test(text)) return status;
177
+ }
178
+ return "unknown";
179
+ }
180
+
181
+ export function qualityReadinessForRunState(runState, nextAction) {
182
+ const artefacts = runState.artefacts;
183
+ const plan = artefacts.get("TP Review");
184
+ const clean = artefacts.get("Clean Implementation Review") ?? artefacts.get("Clean Review");
185
+ const code = artefacts.get("CR") ?? artefacts.get("Code Review");
186
+ const qa = artefacts.get("QA");
187
+ if (![plan, clean, code, qa].some(Boolean)) return null;
188
+ const reviewRows = [plan, clean, code, qa].filter(Boolean);
189
+ const decisive = reviewRows.find((artefact) => /\b(?:block|revise)\b/i.test(`${artefact.status ?? ""} ${artefact.notes ?? ""}`));
190
+ const readiness = buildQualityReadiness({
191
+ planCoverage: statusFromReviewArtefact(plan),
192
+ solutionIntegrity: statusFromReviewArtefact(clean),
193
+ codeQuality: statusFromReviewArtefact(code),
194
+ qaDecision: statusFromReviewArtefact(qa),
195
+ decisiveReason: decisive?.notes ?? "",
196
+ nextAction,
197
+ });
198
+ if (!readiness) return null;
199
+ return Object.freeze({ ...readiness, decisive_reference: decisive?.path ?? "" });
200
+ }
201
+
202
+ function buildHumanPresentation(targetDir, runState, currentGate, presentationLocale) {
203
+ const currentArtefactHeading = readArtefactHeading(targetDir, runState.artefacts.get(currentGate));
204
+ const urHeading = readArtefactHeading(targetDir, runState.artefacts.get("UR"));
205
+ return {
206
+ runTitle: resolveHumanRunTitle({
207
+ currentArtefactHeading,
208
+ urHeading,
209
+ runContent: runState.content,
210
+ runId: extractField(runState.content ?? "", "run_id") || "unknown",
211
+ }),
212
+ gateTitle: gateTitle(interactionLocales, presentationLocale, currentGate),
213
+ artefactRefs: buildArtefactRefs(runState.artefacts, interactionLocales, presentationLocale, {
214
+ pathExists(path) {
215
+ return Boolean(resolvedArtefactFile(targetDir, path));
216
+ },
217
+ }),
218
+ };
219
+ }
220
+
221
+ export function evaluateGateCheck(targetDir, selection = {}) {
222
+ const doctorReport = evaluateDoctor(targetDir, selection);
223
+ const runState = readRunState(targetDir, selection);
224
+ const verifiedChange = modeSliceDecision(runState) === "verified_change"
225
+ ? evaluateVerifiedChange(targetDir, runState)
226
+ : null;
227
+ const transitionDecision = transitionDecisionForRunState(runState, verifiedChange);
228
+ const deliveryMap = analyzeDeliveryMap(runState);
229
+ const doctorBlocker = doctorReport.findings.find((finding) => finding.severity === "block");
230
+ const doctorRevise = doctorReport.findings.find((finding) => finding.severity === "revise");
231
+ const routesInvalidVerifiedChange = modeSliceDecision(runState) === "verified_change"
232
+ && verifiedChange?.status === "invalid"
233
+ && verifiedChangeEscalationTargets.has(verifiedChange.escalation_target);
234
+
235
+ let status = transitionDecision.status;
236
+ let currentGate = transitionDecision.current_gate;
237
+ let blockingReason = transitionDecision.blocking_reason;
238
+ let missingApproval = transitionDecision.missing_approval;
239
+ let allowed = transitionDecision.allowed;
240
+ let forbidden = transitionDecision.forbidden;
241
+ let nextAllowedAction = modeSliceDecision(runState) === "verified_change"
242
+ ? transitionDecision.next_allowed_action
243
+ : isPlaceholderValue(runState.next_allowed_action)
244
+ ? transitionDecision.next_allowed_action
245
+ : runState.next_allowed_action;
246
+
247
+ if (doctorBlocker?.code === "AGDF_CONTROL_FILE_MISSING") {
248
+ status = "blocked";
249
+ blockingReason = doctorBlocker.code;
250
+ currentGate = "UR";
251
+ missingApproval = "Approval: UR";
252
+ allowed = [
253
+ "draft the minimal UR for the requested change in the response",
254
+ "request exact approval: Approval: UR",
255
+ "run npx --yes @agdf/cli@latest init only when durable control state or deterministic setup is explicitly needed",
256
+ ];
257
+ forbidden = ["create PRD", "create SD", "create TP", "run Brownfield Analysis", "implement code", "claim QA or release readiness"];
258
+ nextAllowedAction = "Draft the minimal UR for the request in the response, then ask for exact approval: Approval: UR. Do not write a full .agdf/control scaffold unless durable control state or deterministic setup is explicitly needed.";
259
+ } else if (doctorBlocker && !routesInvalidVerifiedChange) {
260
+ status = "blocked";
261
+ blockingReason = doctorBlocker.code;
262
+ allowed = ["repair the AGDF control scaffold", ...transitionDecision.allowed, "run doctor again"];
263
+ forbidden = ["create later-gate artefacts beyond the current allowed gate", "implement gated work", "claim QA or release readiness"];
264
+ nextAllowedAction = doctorBlocker.next_step;
265
+ } else if (doctorRevise && !routesInvalidVerifiedChange) {
266
+ status = "blocked";
267
+ blockingReason = doctorRevise.code;
268
+ allowed = [...new Set(["complete the current control-state fields", ...transitionDecision.allowed, "run doctor again"])];
269
+ forbidden = ["create later-gate artefacts beyond the current allowed gate", "implement gated work before the gate allows it", "claim QA or release readiness"];
270
+ nextAllowedAction = transitionDecision.current_gate === "UR"
271
+ ? "Fill the current UR control state, persist the UR draft, and request exact approval: Approval: UR."
272
+ : doctorRevise.next_step;
273
+ }
274
+
275
+ const postApproval = postApprovalTransition(missingApproval);
276
+ const presentationLocale = resolveConfiguredChatLanguage(targetDir);
277
+ const statusCard = buildStatusCard({
278
+ status,
279
+ currentGate,
280
+ allowed,
281
+ forbidden,
282
+ blockingReason,
283
+ missingApproval,
284
+ nextAllowedAction,
285
+ runState,
286
+ chatLanguage: presentationLocale,
287
+ findings: deliveryMap.findings,
288
+ });
289
+ const humanPresentation = buildHumanPresentation(targetDir, runState, currentGate, presentationLocale);
290
+ Object.defineProperty(statusCard, "humanPresentation", {
291
+ value: humanPresentation,
292
+ enumerable: false,
293
+ });
294
+ attachApprovalOrientationSnapshot(statusCard, {
295
+ ready: isReadyUserGateApproval({ status, currentGate, missingApproval }),
296
+ humanPresentation,
297
+ revisionId: extractField(runState.content ?? "", "revision_id"),
298
+ registry: interactionLocales,
299
+ requestedLocale: presentationLocale,
300
+ });
301
+ Object.defineProperty(statusCard, "runState", {
302
+ value: runState,
303
+ enumerable: false,
304
+ });
305
+
306
+ return {
307
+ schema_version: "1",
308
+ status,
309
+ current_gate: currentGate,
310
+ blocking_reason: blockingReason,
311
+ missing_approval: missingApproval,
312
+ next_gate_after_approval: postApproval.next_gate_after_approval,
313
+ allowed_after_approval: postApproval.allowed_after_approval,
314
+ allowed,
315
+ forbidden,
316
+ next_allowed_action: nextAllowedAction,
317
+ doctor_status: doctorReport.status,
318
+ doctor_summary: doctorReport.summary,
319
+ evidence_refs: runState.evidence_refs,
320
+ quality_outlook: deriveQualityOutlook(runState, deliveryMap.findings),
321
+ interaction_kind: statusCard.interaction_kind,
322
+ native_attempt_required: statusCard.native_attempt_required,
323
+ candidate_runs: runState.candidate_runs ?? [],
324
+ status_card: statusCard,
325
+ delivery_map: {
326
+ relationships: deliveryMap.relationships,
327
+ mode_slice_decision: runState.mode_slice_decision,
328
+ context_graph: deliveryMap.context_graph,
329
+ source_scope: deliveryMap.source_scope,
330
+ memory: deliveryMap.memory,
331
+ findings: deliveryMap.findings,
332
+ },
333
+ verified_change: verifiedChange,
334
+ doctor_report: doctorReport,
335
+ };
336
+ }
337
+
338
+ function printGateCheckStatusCard(report, io) {
339
+ const card = report.status_card;
340
+ const labels = localePack(interactionLocales, card.presentation_language).statusCard;
341
+ const primary = localePack(interactionLocales, card.presentation_language).primary;
342
+ const presentation = card.humanPresentation ?? {
343
+ runTitle: card.run_id,
344
+ gateTitle: gateTitle(interactionLocales, card.presentation_language, card.current_gate),
345
+ artefactRefs: buildArtefactRefs({}, interactionLocales, card.presentation_language),
346
+ };
347
+ io.log(`${labels.title}: ${primary.status[card.status] ?? card.status}`);
348
+ io.log(`${labels.run}: ${card.run_id}`);
349
+ io.log(`${labels.humanTitle}: ${presentation.runTitle}`);
350
+ io.log(`${labels.gate}: ${card.current_gate} — ${presentation.gateTitle}`);
351
+ io.log(`${labels.lifecycle}: ${primary.lifecycle[card.delivery_state] ?? primary.lifecycle.unknown}`);
352
+ io.log(`${labels.artefacts}: ${formatArtefactRefs(presentation.artefactRefs)}`);
353
+ io.log(`${labels.blocked}: ${card.blocking_condition === "none" ? primary.none : primary.blocked}`);
354
+ io.log(`${labels.missing}: ${card.missing_approval === "none" ? primary.none : card.missing_approval}`);
355
+ if (card.next_gate_after_approval !== "none") {
356
+ io.log(`${labels.nextGate}: ${card.next_gate_after_approval} — ${gateTitle(interactionLocales, card.presentation_language, card.next_gate_after_approval)}`);
357
+ io.log(`${labels.allowedAfter}: ${primary.afterApproval[card.current_gate] ?? primary.actions[card.current_gate]}`);
358
+ }
359
+ io.log(`${labels.allowed}: ${primary.actions[card.current_gate] ?? primary.none}`);
360
+ io.log(`${labels.step}: ${primary.actions[card.current_gate] ?? primary.none}`);
361
+ io.log(`${labels.quality}: ${primary.quality}`);
362
+ const readiness = qualityReadinessForRunState(card.runState, report.next_allowed_action);
363
+ if (readiness) {
364
+ const quality = localePack(interactionLocales, card.presentation_language).qualityReadiness;
365
+ io.log("");
366
+ io.log(`${quality.title}: ${primary.status[readiness.status] ?? readiness.status}`);
367
+ for (const row of readiness.rows) {
368
+ io.log(`${quality.rows[row.id]}: ${primary.status[row.status] ?? quality.unknown}`);
369
+ }
370
+ const reason = readiness.decisive_reason
371
+ || (readiness.status === "pass" ? primary.none : quality.fallbackReasons[readiness.decisive_dimension] || primary.none);
372
+ io.log(`${quality.reason}: ${reason}`);
373
+ if (readiness.status !== "pass" && readiness.decisive_reference) io.log(`${quality.reference}: ${readiness.decisive_reference}`);
374
+ io.log(`${quality.nextAction}: ${readiness.next_action || primary.none}`);
375
+ io.log(`${quality.decisionOwner}: ${quality.decisionOwnerValue}`);
376
+ }
377
+ }
378
+
379
+ export function printGateCheckReport(report, json, statusCard = false, io = console) {
380
+ if (json) {
381
+ io.log(JSON.stringify(report, null, 2));
382
+ return;
383
+ }
384
+
385
+ if (statusCard) {
386
+ printGateCheckStatusCard(report, io);
387
+ return;
388
+ }
389
+
390
+ io.log(`AGDF gate-check: ${report.status}`);
391
+ io.log(`Current gate: ${report.current_gate}`);
392
+ io.log(`Blocking reason: ${report.blocking_reason}`);
393
+ io.log(`Missing approval: ${report.missing_approval}`);
394
+ io.log(`Quality outlook: ${report.quality_outlook}`);
395
+ io.log(`Doctor: ${report.doctor_status} (${report.doctor_summary.findings} findings)`);
396
+ io.log("");
397
+ io.log("Allowed:");
398
+ for (const item of report.allowed) io.log(`- ${item}`);
399
+ io.log("");
400
+ io.log("Forbidden:");
401
+ for (const item of report.forbidden) io.log(`- ${item}`);
402
+ io.log("");
403
+ io.log(`Next allowed action: ${report.next_allowed_action}`);
404
+ }