create-agdf 0.2.2 → 0.2.4

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 (36) hide show
  1. package/README.md +6 -0
  2. package/bin/create-agdf.js +522 -36
  3. package/generated/.agdf/control/README.md +17 -1
  4. package/generated/.agdf/control/templates/AGDF_RUN.md +23 -1
  5. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +16 -11
  6. package/generated/.agdf/control/templates/artefacts/OR.md +86 -0
  7. package/generated/.agdf/control/templates/artefacts/PRD.md +42 -0
  8. package/generated/.agdf/control/templates/artefacts/QA_REPORT.md +38 -0
  9. package/generated/.agdf/control/templates/artefacts/SD.md +42 -0
  10. package/generated/.agdf/control/templates/artefacts/TP.md +36 -0
  11. package/generated/.agdf/control/templates/artefacts/UR.md +41 -0
  12. package/generated/.github/copilot-instructions.md +3 -0
  13. package/generated/.github/instructions/agdf-governance.instructions.md +1 -0
  14. package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +24 -17
  15. package/generated/.github/skills/agdf-gate-check/SKILL.md +53 -5
  16. package/generated/.github/skills/agdf-release-or/SKILL.md +13 -9
  17. package/generated/.github/skills/agdf-runtime-contract.md +60 -2
  18. package/generated/AGENTS.md +15 -2
  19. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  20. package/generated/plugins/agdf/control/README.md +17 -1
  21. package/generated/plugins/agdf/control/templates/AGDF_RUN.md +23 -1
  22. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +16 -11
  23. package/generated/plugins/agdf/control/templates/artefacts/OR.md +86 -0
  24. package/generated/plugins/agdf/control/templates/artefacts/PRD.md +42 -0
  25. package/generated/plugins/agdf/control/templates/artefacts/QA_REPORT.md +38 -0
  26. package/generated/plugins/agdf/control/templates/artefacts/SD.md +42 -0
  27. package/generated/plugins/agdf/control/templates/artefacts/TP.md +36 -0
  28. package/generated/plugins/agdf/control/templates/artefacts/UR.md +41 -0
  29. package/generated/plugins/agdf/meta/agdf-agent-router.md +15 -2
  30. package/generated/plugins/agdf/meta/agdf-constitution.md +22 -5
  31. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +4 -4
  32. package/generated/plugins/agdf/meta/agdf-runtime-contract.md +60 -2
  33. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +24 -17
  34. package/generated/plugins/agdf/skills/gate-check/SKILL.md +53 -5
  35. package/generated/plugins/agdf/skills/release-or/SKILL.md +13 -9
  36. package/package.json +1 -1
@@ -11,9 +11,18 @@ const generatedRoot = join(packageRoot, "generated");
11
11
  const pluginDefinitionPath = join(generatedRoot, "plugins", "agdf", "meta", "agdf-plugin.definition.json");
12
12
  const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
13
13
  const pluginInstallCommand = "claude plugin add arndtgold/ai-native-governance-delivery-framework";
14
- const allowedTargets = new Set(["codex", "copilot", "both", "init", "doctor", "gate-check"]);
14
+ const allowedTargets = new Set(["codex", "copilot", "both", "init", "doctor", "gate-check", "delivery-map"]);
15
15
  const agdfFragmentPath = "AGENTS.agdf.md";
16
16
  const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
17
+ const durableGateArtefacts = new Set(["UR", "PRD", "SD", "TP", "QA"]);
18
+ const internalStepArtefacts = new Set(["Brownfield Review"]);
19
+ const deliveryRelationships = [
20
+ { from: "UR", relationship: "approved_by", to: "Approval: UR", requiredBy: "UR" },
21
+ { from: "PRD", relationship: "derived_from", to: "UR", requiredBy: "PRD" },
22
+ { from: "SD", relationship: "derived_from", to: "PRD", requiredBy: "SD" },
23
+ { from: "TP", relationship: "derived_from", to: "SD", requiredBy: "TP" },
24
+ { from: "QA_REPORT", relationship: "tests", to: "TP", requiredBy: "QA" },
25
+ ];
17
26
  const codexSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.codex.skillPrefix}${skill.slug}`);
18
27
  const copilotSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.copilot.skillPrefix}${skill.slug}`);
19
28
  const codexPluginFiles = [
@@ -25,6 +34,12 @@ const codexPluginFiles = [
25
34
  join("plugins", "agdf", "control", "templates", "SOT_REGISTRY.md"),
26
35
  join("plugins", "agdf", "control", "templates", "CONTEXT_GRAPH.md"),
27
36
  join("plugins", "agdf", "control", "templates", "AGENT_QUALITY_CONTRACTS.json"),
37
+ join("plugins", "agdf", "control", "templates", "artefacts", "UR.md"),
38
+ join("plugins", "agdf", "control", "templates", "artefacts", "PRD.md"),
39
+ join("plugins", "agdf", "control", "templates", "artefacts", "SD.md"),
40
+ join("plugins", "agdf", "control", "templates", "artefacts", "TP.md"),
41
+ join("plugins", "agdf", "control", "templates", "artefacts", "QA_REPORT.md"),
42
+ join("plugins", "agdf", "control", "templates", "artefacts", "OR.md"),
28
43
  join("plugins", "agdf", "hooks", "hooks.json"),
29
44
  join("plugins", "agdf", "hooks", "session-start.sh"),
30
45
  join("plugins", "agdf", "meta", "agdf-agent-router.md"),
@@ -41,6 +56,12 @@ const controlFiles = [
41
56
  join(".agdf", "control", "templates", "SOT_REGISTRY.md"),
42
57
  join(".agdf", "control", "templates", "CONTEXT_GRAPH.md"),
43
58
  join(".agdf", "control", "templates", "AGENT_QUALITY_CONTRACTS.json"),
59
+ join(".agdf", "control", "templates", "artefacts", "UR.md"),
60
+ join(".agdf", "control", "templates", "artefacts", "PRD.md"),
61
+ join(".agdf", "control", "templates", "artefacts", "SD.md"),
62
+ join(".agdf", "control", "templates", "artefacts", "TP.md"),
63
+ join(".agdf", "control", "templates", "artefacts", "QA_REPORT.md"),
64
+ join(".agdf", "control", "templates", "artefacts", "OR.md"),
44
65
  ];
45
66
  const liveControlFiles = [
46
67
  {
@@ -64,6 +85,14 @@ const liveControlFiles = [
64
85
  source: join(".agdf", "control", "templates", "AGENT_QUALITY_CONTRACTS.json"),
65
86
  },
66
87
  ];
88
+ const artefactTemplateFiles = [
89
+ join(".agdf", "control", "templates", "artefacts", "UR.md"),
90
+ join(".agdf", "control", "templates", "artefacts", "PRD.md"),
91
+ join(".agdf", "control", "templates", "artefacts", "SD.md"),
92
+ join(".agdf", "control", "templates", "artefacts", "TP.md"),
93
+ join(".agdf", "control", "templates", "artefacts", "QA_REPORT.md"),
94
+ join(".agdf", "control", "templates", "artefacts", "OR.md"),
95
+ ];
67
96
  const doctorRequiredFiles = [
68
97
  join(".agdf", "control", "AGDF_RUN.md"),
69
98
  join(".agdf", "control", "MASTER_BACKLOG.md"),
@@ -91,11 +120,12 @@ Usage:
91
120
  npm create agdf@latest init
92
121
  npm create agdf@latest doctor
93
122
  npm create agdf@latest gate-check
123
+ npm create agdf@latest delivery-map
94
124
 
95
125
  Options:
96
126
  --dir <path> Write files into a specific directory
97
127
  --force Overwrite existing generated files
98
- --json Print doctor or gate-check output as JSON
128
+ --json Print doctor, gate-check or delivery-map output as JSON
99
129
  --help Show this help
100
130
  `);
101
131
  }
@@ -158,7 +188,7 @@ function parseArgs(argv) {
158
188
  }
159
189
 
160
190
  if (!target || !allowedTargets.has(target)) {
161
- console.error("Please choose one target: codex, copilot, both, init, doctor or gate-check.");
191
+ console.error("Please choose one target: codex, copilot, both, init, doctor, gate-check or delivery-map.");
162
192
  printUsage();
163
193
  process.exit(1);
164
194
  }
@@ -202,6 +232,13 @@ function generatedFilesForTarget(target, targetDir, force) {
202
232
  });
203
233
  }
204
234
 
235
+ for (const templatePath of artefactTemplateFiles) {
236
+ files.push({
237
+ path: templatePath,
238
+ content: loadAsset(templatePath),
239
+ });
240
+ }
241
+
205
242
  return files;
206
243
  }
207
244
 
@@ -336,6 +373,21 @@ function hasFilledEvidenceRow(content) {
336
373
  });
337
374
  }
338
375
 
376
+ function markdownSection(content, heading) {
377
+ const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
378
+ return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?:\\n## |\\n# |$)`))?.[1] ?? "";
379
+ }
380
+
381
+ function filled(value) {
382
+ return Boolean(value && !isPlaceholderValue(value) && value.trim() !== "");
383
+ }
384
+
385
+ function nonTemplateRows(section, headerCell) {
386
+ return tableRows(section)
387
+ .filter((cells) => cells[0] !== headerCell)
388
+ .filter((cells) => cells.some((cell) => filled(cell)));
389
+ }
390
+
339
391
  function parseQualityContracts(content) {
340
392
  const parsed = JSON.parse(content);
341
393
  if (!Array.isArray(parsed.contracts) || parsed.contracts.length === 0) {
@@ -479,6 +531,11 @@ function evaluateDoctor(targetDir) {
479
531
  "Restore the generated contracts or fix the JSON contract schema.",
480
532
  );
481
533
  }
534
+
535
+ const runState = readRunState(targetDir);
536
+ for (const finding of analyzeDeliveryMap(runState).findings) {
537
+ addFinding(findings, finding.severity, finding.code, finding.message, finding.path, finding.next_step);
538
+ }
482
539
  }
483
540
 
484
541
  const severityRank = { block: 3, revise: 2, warn: 1 };
@@ -524,10 +581,26 @@ function extractField(content, field) {
524
581
  return content.match(pattern)?.[1]?.trim() ?? "";
525
582
  }
526
583
 
584
+ function extractSectionField(section, field) {
585
+ const pattern = new RegExp(`^- ${field}:[^\\S\\r\\n]*(.*)$`, "m");
586
+ return section.match(pattern)?.[1]?.trim() ?? "";
587
+ }
588
+
527
589
  function cleanStatusCell(value) {
528
590
  return value.replace(/^`|`$/g, "").trim();
529
591
  }
530
592
 
593
+ function addApprovalRows(approvals, section) {
594
+ for (const cells of tableRows(section)) {
595
+ const [gate, status, evidence] = cells;
596
+ if (!userGateOrder.includes(gate)) continue;
597
+ approvals.set(gate, {
598
+ status: cleanStatusCell(status ?? ""),
599
+ evidence: evidence ?? "",
600
+ });
601
+ }
602
+ }
603
+
531
604
  function readRunState(targetDir) {
532
605
  const runPath = join(".agdf", "control", "AGDF_RUN.md");
533
606
  if (!existsSync(join(targetDir, runPath))) {
@@ -537,26 +610,44 @@ function readRunState(targetDir) {
537
610
  current_gate: "",
538
611
  next_allowed_action: "",
539
612
  approvals: new Map(),
613
+ artefacts: new Map(),
540
614
  evidence_refs: [],
615
+ artefact_chain: [],
616
+ mode_slice_decision: {},
617
+ missing_evidence: [],
618
+ risks: [],
619
+ context_graph: {},
541
620
  };
542
621
  }
543
622
 
544
623
  const content = readTargetFile(targetDir, runPath);
545
- const approvalsSection = content.match(/## Approvals([\s\S]*?)(?:\n## |\n# |$)/)?.[1] ?? "";
546
624
  const approvals = new Map();
547
- for (const cells of tableRows(approvalsSection)) {
548
- const [gate, status, evidence] = cells;
549
- if (!userGateOrder.includes(gate)) continue;
550
- approvals.set(gate, {
625
+ addApprovalRows(approvals, content.match(/## Approvals([\s\S]*?)(?:\n## |\n# |$)/)?.[1] ?? "");
626
+ addApprovalRows(approvals, content.match(/## Gate Checklist([\s\S]*?)(?:\n## |\n# |$)/)?.[1] ?? "");
627
+
628
+ const artefacts = new Map();
629
+ const artefactsSection = markdownSection(content, "Artefacts");
630
+ for (const cells of tableRows(artefactsSection)) {
631
+ const [type, path, status, notes] = cells;
632
+ if (!userGateOrder.includes(type) && !internalStepArtefacts.has(type)) continue;
633
+ artefacts.set(type, {
634
+ path: path ?? "",
551
635
  status: cleanStatusCell(status ?? ""),
552
- evidence: evidence ?? "",
636
+ notes: notes ?? "",
553
637
  });
554
638
  }
555
639
 
556
- const evidenceSection = content.match(/## Evidence([\s\S]*?)(?:\n## |\n# |$)/)?.[1] ?? "";
557
- const evidence_refs = tableRows(evidenceSection)
640
+ const artefactChain = nonTemplateRows(markdownSection(content, "Artefact Chain"), "From")
641
+ .map((cells) => ({
642
+ from: cells[0] ?? "",
643
+ relationship: cleanStatusCell(cells[1] ?? ""),
644
+ to: cleanStatusCell(cells[2] ?? ""),
645
+ evidence: cells[3] ?? "",
646
+ }));
647
+
648
+ const evidence_refs = tableRows(markdownSection(content, "Evidence"))
558
649
  .filter((cells) => cells[0] !== "Evidence")
559
- .filter((cells) => Boolean(cells[0] || cells[1] || cells[2]))
650
+ .filter((cells) => filled(cells[0]) || filled(cells[1]) || filled(cells[2]))
560
651
  .map((cells) => ({
561
652
  evidence: cells[0] ?? "",
562
653
  source: cells[1] ?? "",
@@ -564,13 +655,53 @@ function readRunState(targetDir) {
564
655
  strength: cleanStatusCell(cells[3] ?? ""),
565
656
  }));
566
657
 
658
+ const missingEvidence = tableRows(markdownSection(content, "Missing Evidence"))
659
+ .filter((cells) => cells[0] !== "Missing evidence")
660
+ .filter((cells) => filled(cells[0]))
661
+ .map((cells) => ({
662
+ missing_evidence: cells[0] ?? "",
663
+ impact: cleanStatusCell(cells[1] ?? ""),
664
+ required_next_step: cells[2] ?? "",
665
+ }));
666
+
667
+ const risks = tableRows(markdownSection(content, "Risks"))
668
+ .filter((cells) => cells[0] !== "Risk")
669
+ .filter((cells) => filled(cells[0]))
670
+ .map((cells) => ({
671
+ risk: cells[0] ?? "",
672
+ impact: cleanStatusCell(cells[1] ?? ""),
673
+ mitigation_or_owner: cells[2] ?? "",
674
+ }));
675
+
676
+ const contextGraph = {
677
+ impact: cleanStatusCell(extractField(content, "context_graph_impact")),
678
+ refs: extractField(content, "context_graph_refs"),
679
+ required_action: cleanStatusCell(extractField(content, "context_graph_required_action")),
680
+ gate_effect: cleanStatusCell(extractField(content, "context_graph_gate_effect")),
681
+ evidence: extractField(content, "context_graph_evidence"),
682
+ };
683
+
684
+ const modeSliceSection = markdownSection(content, "Mode / Slice Decision");
685
+ const modeSliceDecision = {
686
+ decision: cleanStatusCell(extractSectionField(modeSliceSection, "decision")),
687
+ required_next_gate: cleanStatusCell(extractSectionField(modeSliceSection, "required_next_gate")),
688
+ scope_reason: extractSectionField(modeSliceSection, "scope_reason"),
689
+ evidence: extractSectionField(modeSliceSection, "evidence"),
690
+ };
691
+
567
692
  return {
568
693
  path: runPath,
569
694
  content,
570
695
  current_gate: extractField(content, "current_gate"),
571
696
  next_allowed_action: extractField(content, "next_allowed_action"),
572
697
  approvals,
698
+ artefacts,
573
699
  evidence_refs,
700
+ artefact_chain: artefactChain,
701
+ mode_slice_decision: modeSliceDecision,
702
+ missing_evidence: missingEvidence,
703
+ risks,
704
+ context_graph: contextGraph,
574
705
  };
575
706
  }
576
707
 
@@ -593,29 +724,301 @@ function gateApprovalStatus(runState, gate) {
593
724
  return runState.approvals.get(gate)?.status ?? "";
594
725
  }
595
726
 
727
+ function gateArtefactStatus(runState, gate) {
728
+ if (!userGateOrder.includes(gate)) return { status: "not_applicable", path: "" };
729
+ return runState.artefacts.get(gate) ?? { status: "", path: "" };
730
+ }
731
+
732
+ function isDurableGateArtefactSatisfied(runState, gate) {
733
+ if (!durableGateArtefacts.has(gate)) return true;
734
+ const artefact = gateArtefactStatus(runState, gate);
735
+ if (!artefact.path || isPlaceholderValue(artefact.path)) return false;
736
+ if (gate === "QA") return artefact.status === "pass";
737
+ return artefact.status === "approved";
738
+ }
739
+
740
+ function isInternalStepSatisfied(runState, step) {
741
+ const artefact = runState.artefacts.get(step);
742
+ if (!artefact) return false;
743
+ return artefact.status === "done" || artefact.status === "not_applicable";
744
+ }
745
+
746
+ function modeSliceDecision(runState) {
747
+ const decision = runState.mode_slice_decision?.decision ?? "";
748
+ if (!decision || isPlaceholderValue(decision)) return "undecided";
749
+ return decision;
750
+ }
751
+
752
+ function relationshipRequired(runState, requiredBy) {
753
+ if (gateApprovalStatus(runState, requiredBy) === "not_applicable") return false;
754
+ if (requiredBy === "QA") {
755
+ return isGateSatisfied(runState, "QA");
756
+ }
757
+
758
+ return isGateSatisfied(runState, requiredBy);
759
+ }
760
+
761
+ function findRelationship(runState, expected) {
762
+ return runState.artefact_chain.find((row) =>
763
+ row.from === expected.from
764
+ && row.relationship === expected.relationship
765
+ && row.to === expected.to
766
+ );
767
+ }
768
+
769
+ function severityFromImpact(value) {
770
+ if (value === "block") return "block";
771
+ if (value === "revise") return "revise";
772
+ if (value === "warning" || value === "warn") return "warn";
773
+ return null;
774
+ }
775
+
776
+ function analyzeDeliveryMap(runState) {
777
+ const findings = [];
778
+ const relationships = deliveryRelationships.map((expected) => {
779
+ const row = findRelationship(runState, expected);
780
+ const required = relationshipRequired(runState, expected.requiredBy);
781
+ const evidence = row?.evidence ?? "";
782
+ const status = !row ? "missing" : filled(evidence) ? "pass" : required ? "missing_evidence" : "template";
783
+
784
+ if (required && status !== "pass") {
785
+ findings.push({
786
+ severity: "revise",
787
+ code: status === "missing" ? "AGDF_DELIVERY_RELATIONSHIP_MISSING" : "AGDF_DELIVERY_RELATIONSHIP_EVIDENCE_MISSING",
788
+ message: `${expected.from} must be traceable via ${expected.relationship} ${expected.to}.`,
789
+ path: runState.path,
790
+ next_step: "Fill the Artefact Chain row with concrete evidence before treating the delivery map as complete.",
791
+ });
792
+ }
793
+
794
+ return {
795
+ from: expected.from,
796
+ relationship: expected.relationship,
797
+ to: expected.to,
798
+ required,
799
+ status,
800
+ evidence,
801
+ };
802
+ });
803
+
804
+ for (const item of runState.missing_evidence) {
805
+ const severity = severityFromImpact(item.impact);
806
+ if (!severity) continue;
807
+ findings.push({
808
+ severity,
809
+ code: "AGDF_MISSING_EVIDENCE_DECLARED",
810
+ message: item.missing_evidence,
811
+ path: runState.path,
812
+ next_step: item.required_next_step || "Resolve or explicitly accept the missing evidence before advancing the gate.",
813
+ });
814
+ }
815
+
816
+ for (const item of runState.risks) {
817
+ const severity = severityFromImpact(item.impact);
818
+ if (!severity) continue;
819
+ findings.push({
820
+ severity,
821
+ code: "AGDF_RISK_DECLARED",
822
+ message: item.risk,
823
+ path: runState.path,
824
+ next_step: item.mitigation_or_owner || "Assign mitigation or ownership before advancing the gate.",
825
+ });
826
+ }
827
+
828
+ const contextSeverity = severityFromImpact(runState.context_graph?.gate_effect);
829
+ if (contextSeverity) {
830
+ findings.push({
831
+ severity: contextSeverity,
832
+ code: "AGDF_CONTEXT_GRAPH_GATE_EFFECT",
833
+ message: `Context Graph impact is ${runState.context_graph.impact || "unspecified"} with gate effect ${runState.context_graph.gate_effect}.`,
834
+ path: runState.path,
835
+ next_step: runState.context_graph.required_action && runState.context_graph.required_action !== "none"
836
+ ? `Resolve Context Graph action: ${runState.context_graph.required_action}.`
837
+ : "Clarify the Context Graph impact before advancing the gate.",
838
+ });
839
+ }
840
+
841
+ return {
842
+ relationships,
843
+ missing_evidence: runState.missing_evidence,
844
+ risks: runState.risks,
845
+ context_graph: runState.context_graph,
846
+ findings,
847
+ };
848
+ }
849
+
850
+ function durableArtefactBlock(gate, nextGate) {
851
+ const label = gate === "QA" ? "QA report" : `${gate} artefact`;
852
+ const stablePath = gate === "QA" ? ".agdf/control/artefacts/<key>/QA_REPORT.md" : `.agdf/control/artefacts/<key>/${gate}.md`;
853
+ return {
854
+ status: "blocked",
855
+ current_gate: gate,
856
+ blocking_reason: `missing_durable_${gate.toLowerCase()}_artefact`,
857
+ missing_approval: "none",
858
+ allowed: [
859
+ `persist the approved ${label} in a stable artefact path such as ${stablePath}`,
860
+ "link the artefact from AGDF_RUN.md and MASTER_BACKLOG.md",
861
+ "run gate-check again",
862
+ ],
863
+ forbidden: nextGate
864
+ ? [`create ${nextGate}`, "create later-gate artefacts", "implement gated work", "claim QA or release readiness"]
865
+ : ["create later-gate artefacts", "implement gated work", "claim release readiness"],
866
+ next_allowed_action: `Persist the approved ${label} and link it from the AGDF control state before continuing.`,
867
+ };
868
+ }
869
+
870
+ function isGateSatisfied(runState, gate) {
871
+ const status = gateApprovalStatus(runState, gate);
872
+ if (status === "not_applicable") return true;
873
+ if (status !== "approved") return false;
874
+ return isDurableGateArtefactSatisfied(runState, gate);
875
+ }
876
+
877
+ function transitionDecisionForRunState(runState) {
878
+ if (gateApprovalStatus(runState, "UR") === "approved" && !isGateSatisfied(runState, "UR")) return durableArtefactBlock("UR", "PRD");
879
+
880
+ if (!isGateSatisfied(runState, "UR")) {
881
+ return {
882
+ status: "blocked",
883
+ current_gate: "UR",
884
+ blocking_reason: "missing_exact_approval",
885
+ missing_approval: "Approval: UR",
886
+ allowed: ["clarify user need", "formulate and persist UR", "record evidence", "request exact UR approval"],
887
+ forbidden: ["create PRD", "create SD", "create TP", "run Brownfield Analysis", "implement code", "claim QA or release readiness"],
888
+ next_allowed_action: "Clarify the user requirement, persist the UR, and request exact approval: Approval: UR",
889
+ };
890
+ }
891
+
892
+ if (gateApprovalStatus(runState, "PRD") === "approved" && !isGateSatisfied(runState, "PRD")) return durableArtefactBlock("PRD", "SD");
893
+
894
+ if (!isGateSatisfied(runState, "PRD")) {
895
+ if (!isInternalStepSatisfied(runState, "Brownfield Review")) {
896
+ return {
897
+ status: "open",
898
+ current_gate: "Brownfield Review",
899
+ blocking_reason: "none",
900
+ missing_approval: "none",
901
+ allowed: [
902
+ "run Brownfield Review after G-00",
903
+ "identify existing workstream, owners, SoT, reuse risks and open PRD/SD questions",
904
+ "mark Brownfield Review as done or not_applicable in AGDF_RUN.md",
905
+ ],
906
+ forbidden: ["create PRD before Brownfield Review is resolved", "create SD", "create TP", "implement code", "claim QA or release readiness"],
907
+ next_allowed_action: "Run Brownfield Review after G-00 before drafting PRD, or mark Brownfield Review not_applicable with evidence.",
908
+ };
909
+ }
910
+
911
+ const modeDecision = modeSliceDecision(runState);
912
+ if (modeDecision === "undecided") {
913
+ return {
914
+ status: "open",
915
+ current_gate: "Mode/Slice Decision",
916
+ blocking_reason: "none",
917
+ missing_approval: "none",
918
+ allowed: [
919
+ "decide whether the approved UR is quick_task, structured_slice, structured_delivery or block",
920
+ "record scope reason and evidence in AGDF_RUN.md",
921
+ "choose the next required gate depth before drafting PRD or implementing",
922
+ ],
923
+ forbidden: ["create PRD before process size is decided", "create SD", "create TP", "implement code", "claim QA or release readiness"],
924
+ next_allowed_action: "Record the Mode/Slice Decision from Brownfield Review before choosing PRD depth or Quick Task execution.",
925
+ };
926
+ }
927
+
928
+ if (modeDecision === "block") {
929
+ return {
930
+ status: "blocked",
931
+ current_gate: "Mode/Slice Decision",
932
+ blocking_reason: "mode_slice_decision_blocked",
933
+ missing_approval: "none",
934
+ allowed: ["resolve ownership, SoT, evidence, impact or product-direction uncertainty", "run gate-check again"],
935
+ forbidden: ["create PRD", "create SD", "create TP", "implement code", "claim QA or release readiness"],
936
+ next_allowed_action: "Resolve the Brownfield Review blocker before choosing a delivery path.",
937
+ };
938
+ }
939
+
940
+ if (modeDecision === "quick_task") {
941
+ return {
942
+ status: "open",
943
+ current_gate: "Quick Task Execution",
944
+ blocking_reason: "none",
945
+ missing_approval: "none",
946
+ allowed: ["implement the narrow approved UR scope", "run relevant checks", "record evidence and close with OR-lite"],
947
+ forbidden: ["expand scope beyond the Brownfield Review decision", "create broad PRD/SD/TP artefacts by ritual", "claim QA or release readiness without evidence"],
948
+ next_allowed_action: "Proceed as a Quick Task within the Brownfield Review scope and record verification evidence.",
949
+ };
950
+ }
951
+
952
+ return {
953
+ status: "open",
954
+ current_gate: "PRD",
955
+ blocking_reason: "none",
956
+ missing_approval: "Approval: PRD",
957
+ allowed: ["draft or refine PRD", "define scope", "define acceptance criteria", "define non-goals", "request exact PRD approval"],
958
+ forbidden: ["create SD", "create TP", "run Brownfield Analysis as implementation preparation", "implement code", "claim QA or release readiness"],
959
+ next_allowed_action: modeDecision === "structured_slice"
960
+ ? "Draft or refine the smallest PRD slice justified by Brownfield Review; do not implement before required artefacts are approved."
961
+ : "Draft or refine the PRD; do not implement before PRD, SD and TP are approved.",
962
+ };
963
+ }
964
+
965
+ if (gateApprovalStatus(runState, "SD") === "approved" && !isGateSatisfied(runState, "SD")) return durableArtefactBlock("SD", "TP");
966
+
967
+ if (!isGateSatisfied(runState, "SD")) {
968
+ return {
969
+ status: "open",
970
+ current_gate: "SD",
971
+ blocking_reason: "none",
972
+ missing_approval: "Approval: SD",
973
+ allowed: ["draft or refine Solution Design", "define architecture", "define ownership", "request exact SD approval"],
974
+ forbidden: ["create TP", "implement code", "claim QA or release readiness"],
975
+ next_allowed_action: "Draft or refine the Solution Design; do not implement before SD and TP are approved.",
976
+ };
977
+ }
978
+
979
+ if (gateApprovalStatus(runState, "TP") === "approved" && !isGateSatisfied(runState, "TP")) return durableArtefactBlock("TP", "Brownfield Analysis");
980
+
981
+ if (!isGateSatisfied(runState, "TP")) {
982
+ return {
983
+ status: "open",
984
+ current_gate: "TP",
985
+ blocking_reason: "none",
986
+ missing_approval: "Approval: TP",
987
+ allowed: ["draft or refine Task/Test Plan", "define task IDs", "define test evidence", "request exact TP approval"],
988
+ forbidden: ["implement code", "claim QA or release readiness"],
989
+ next_allowed_action: "Draft or refine the Task/Test Plan; do not implement before TP is approved.",
990
+ };
991
+ }
992
+
993
+ if (gateApprovalStatus(runState, "QA") === "approved" && !isGateSatisfied(runState, "QA")) return durableArtefactBlock("QA", "UAT");
994
+
995
+ return {
996
+ status: "open",
997
+ current_gate: "Brownfield Analysis",
998
+ blocking_reason: "none",
999
+ missing_approval: "none",
1000
+ allowed: ["run Brownfield Analysis for the approved TP scope", "verify existing owners, reuse paths and regression risks"],
1001
+ forbidden: ["implement before Brownfield evidence supports the approved TP path", "claim QA or release readiness"],
1002
+ next_allowed_action: "Run Brownfield Analysis for the approved TP scope before CD+Tests.",
1003
+ };
1004
+ }
1005
+
596
1006
  function evaluateGateCheck(targetDir) {
597
1007
  const doctorReport = evaluateDoctor(targetDir);
598
1008
  const runState = readRunState(targetDir);
599
- const fallbackGate = firstUnapprovedGate(runState.approvals);
600
- const currentGate = normalizeCurrentGate(runState.current_gate, fallbackGate);
601
- const currentApprovalStatus = gateApprovalStatus(runState, currentGate);
1009
+ const transitionDecision = transitionDecisionForRunState(runState);
1010
+ const deliveryMap = analyzeDeliveryMap(runState);
602
1011
  const doctorBlocker = doctorReport.findings.find((finding) => finding.severity === "block");
603
1012
  const doctorRevise = doctorReport.findings.find((finding) => finding.severity === "revise");
604
1013
 
605
- let status = "open";
606
- let blockingReason = "none";
607
- let missingApproval = "none";
608
- let allowed = [
609
- "continue with the documented next allowed action",
610
- "update evidence when new facts are observed",
611
- "run doctor or gate-check again after control changes",
612
- ];
613
- let forbidden = [
614
- "bypass the documented gate state",
615
- "treat warnings as resolved without evidence",
616
- ];
1014
+ let status = transitionDecision.status;
1015
+ let currentGate = transitionDecision.current_gate;
1016
+ let blockingReason = transitionDecision.blocking_reason;
1017
+ let missingApproval = transitionDecision.missing_approval;
1018
+ let allowed = transitionDecision.allowed;
1019
+ let forbidden = transitionDecision.forbidden;
617
1020
  let nextAllowedAction = isPlaceholderValue(runState.next_allowed_action)
618
- ? "Continue with the current documented gate only after setting next_allowed_action."
1021
+ ? transitionDecision.next_allowed_action
619
1022
  : runState.next_allowed_action;
620
1023
 
621
1024
  if (doctorBlocker) {
@@ -627,16 +1030,9 @@ function evaluateGateCheck(targetDir) {
627
1030
  } else if (doctorRevise) {
628
1031
  status = "blocked";
629
1032
  blockingReason = doctorRevise.code;
630
- allowed = ["repair the incomplete control state", "run doctor again"];
1033
+ allowed = [...new Set(["repair the incomplete control state", ...transitionDecision.allowed, "run doctor again"])];
631
1034
  forbidden = ["continue governed delivery", "create later-gate artefacts", "implement gated work"];
632
1035
  nextAllowedAction = doctorRevise.next_step;
633
- } else if (userGateOrder.includes(currentGate) && currentApprovalStatus !== "approved" && currentApprovalStatus !== "not_applicable") {
634
- status = "blocked";
635
- blockingReason = "missing_exact_approval";
636
- missingApproval = `Approval: ${currentGate}`;
637
- allowed = ["clarify the current gate", "record evidence", "write OR-lite status", "request exact approval"];
638
- forbidden = ["create later-gate artefacts", "implement gated work", "mark QA or UAT as passed"];
639
- nextAllowedAction = `Request exact approval: Approval: ${currentGate}`;
640
1036
  }
641
1037
 
642
1038
  return {
@@ -651,6 +1047,12 @@ function evaluateGateCheck(targetDir) {
651
1047
  doctor_status: doctorReport.status,
652
1048
  doctor_summary: doctorReport.summary,
653
1049
  evidence_refs: runState.evidence_refs,
1050
+ delivery_map: {
1051
+ relationships: deliveryMap.relationships,
1052
+ mode_slice_decision: runState.mode_slice_decision,
1053
+ context_graph: deliveryMap.context_graph,
1054
+ findings: deliveryMap.findings,
1055
+ },
654
1056
  doctor_report: doctorReport,
655
1057
  };
656
1058
  }
@@ -676,6 +1078,84 @@ function printGateCheckReport(report, json) {
676
1078
  console.log(`Next allowed action: ${report.next_allowed_action}`);
677
1079
  }
678
1080
 
1081
+ function readBacklogPointers(targetDir) {
1082
+ const backlogPath = join(".agdf", "control", "MASTER_BACKLOG.md");
1083
+ if (!existsSync(join(targetDir, backlogPath))) return [];
1084
+
1085
+ const backlog = readTargetFile(targetDir, backlogPath);
1086
+ const activeSection = markdownSection(backlog, "Active Backlog");
1087
+ return nonTemplateRows(activeSection, "Prio")
1088
+ .map((cells) => ({
1089
+ prio: cells[0] ?? "",
1090
+ key: cells[1] ?? "",
1091
+ title: cells[2] ?? "",
1092
+ status: cleanStatusCell(cells[3] ?? ""),
1093
+ ur: cells[4] ?? "",
1094
+ prd: cells[5] ?? "",
1095
+ sd: cells[6] ?? "",
1096
+ tp: cells[7] ?? "",
1097
+ qa: cells[8] ?? "",
1098
+ or: cells[9] ?? "",
1099
+ current_spec: cells[10] ?? "",
1100
+ notes: cells[11] ?? "",
1101
+ }));
1102
+ }
1103
+
1104
+ function evaluateDeliveryMap(targetDir) {
1105
+ const doctorReport = evaluateDoctor(targetDir);
1106
+ const runState = readRunState(targetDir);
1107
+ const map = analyzeDeliveryMap(runState);
1108
+ const gateDecision = transitionDecisionForRunState(runState);
1109
+
1110
+ const severityRank = { block: 3, revise: 2, warn: 1 };
1111
+ const deliverySeverity = map.findings.reduce((max, finding) => Math.max(max, severityRank[finding.severity] ?? 0), 0);
1112
+ const doctorSeverity = severityRank[doctorReport.status] ?? 0;
1113
+ const maxSeverity = Math.max(deliverySeverity, doctorSeverity);
1114
+ const status = maxSeverity >= 3 ? "block" : maxSeverity === 2 ? "revise" : maxSeverity === 1 ? "warn" : "pass";
1115
+
1116
+ return {
1117
+ schema_version: "1",
1118
+ status,
1119
+ checked_at: new Date().toISOString(),
1120
+ target_dir: targetDir,
1121
+ current_gate: gateDecision.current_gate,
1122
+ next_allowed_action: isPlaceholderValue(runState.next_allowed_action) ? gateDecision.next_allowed_action : runState.next_allowed_action,
1123
+ backlog_pointers: readBacklogPointers(targetDir),
1124
+ artefacts: Object.fromEntries([...runState.artefacts.entries()]),
1125
+ approvals: Object.fromEntries([...runState.approvals.entries()]),
1126
+ mode_slice_decision: runState.mode_slice_decision,
1127
+ relationships: map.relationships,
1128
+ evidence_refs: runState.evidence_refs,
1129
+ missing_evidence: map.missing_evidence,
1130
+ risks: map.risks,
1131
+ context_graph: map.context_graph,
1132
+ findings: map.findings,
1133
+ doctor_status: doctorReport.status,
1134
+ doctor_summary: doctorReport.summary,
1135
+ doctor_report: doctorReport,
1136
+ };
1137
+ }
1138
+
1139
+ function printDeliveryMapReport(report, json) {
1140
+ if (json) {
1141
+ console.log(JSON.stringify(report, null, 2));
1142
+ return;
1143
+ }
1144
+
1145
+ console.log(`AGDF delivery-map: ${report.status}`);
1146
+ console.log(`Current gate: ${report.current_gate}`);
1147
+ console.log(`Next allowed action: ${report.next_allowed_action}`);
1148
+ console.log(`Relationships: ${report.relationships.filter((item) => item.status === "pass").length}/${report.relationships.length} evidenced`);
1149
+ console.log(`Findings: ${report.findings.length}`);
1150
+
1151
+ for (const finding of report.findings) {
1152
+ console.log("");
1153
+ console.log(`[${finding.severity}] ${finding.code}`);
1154
+ console.log(finding.message);
1155
+ console.log(`Next step: ${finding.next_step}`);
1156
+ }
1157
+ }
1158
+
679
1159
  function main() {
680
1160
  const options = parseArgs(process.argv.slice(2));
681
1161
 
@@ -691,6 +1171,12 @@ function main() {
691
1171
  process.exit(report.status === "blocked" ? 2 : 0);
692
1172
  }
693
1173
 
1174
+ if (options.target === "delivery-map") {
1175
+ const report = evaluateDeliveryMap(options.dir);
1176
+ printDeliveryMapReport(report, options.json);
1177
+ process.exit(report.status === "block" ? 2 : 0);
1178
+ }
1179
+
694
1180
  const files = generatedFilesForTarget(options.target, options.dir, options.force);
695
1181
  const wroteAgentsFragment = files.some(file => file.path === agdfFragmentPath);
696
1182