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.
- package/README.md +6 -0
- package/bin/create-agdf.js +522 -36
- package/generated/.agdf/control/README.md +17 -1
- package/generated/.agdf/control/templates/AGDF_RUN.md +23 -1
- package/generated/.agdf/control/templates/MASTER_BACKLOG.md +16 -11
- package/generated/.agdf/control/templates/artefacts/OR.md +86 -0
- package/generated/.agdf/control/templates/artefacts/PRD.md +42 -0
- package/generated/.agdf/control/templates/artefacts/QA_REPORT.md +38 -0
- package/generated/.agdf/control/templates/artefacts/SD.md +42 -0
- package/generated/.agdf/control/templates/artefacts/TP.md +36 -0
- package/generated/.agdf/control/templates/artefacts/UR.md +41 -0
- package/generated/.github/copilot-instructions.md +3 -0
- package/generated/.github/instructions/agdf-governance.instructions.md +1 -0
- package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +24 -17
- package/generated/.github/skills/agdf-gate-check/SKILL.md +53 -5
- package/generated/.github/skills/agdf-release-or/SKILL.md +13 -9
- package/generated/.github/skills/agdf-runtime-contract.md +60 -2
- package/generated/AGENTS.md +15 -2
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/README.md +17 -1
- package/generated/plugins/agdf/control/templates/AGDF_RUN.md +23 -1
- package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +16 -11
- package/generated/plugins/agdf/control/templates/artefacts/OR.md +86 -0
- package/generated/plugins/agdf/control/templates/artefacts/PRD.md +42 -0
- package/generated/plugins/agdf/control/templates/artefacts/QA_REPORT.md +38 -0
- package/generated/plugins/agdf/control/templates/artefacts/SD.md +42 -0
- package/generated/plugins/agdf/control/templates/artefacts/TP.md +36 -0
- package/generated/plugins/agdf/control/templates/artefacts/UR.md +41 -0
- package/generated/plugins/agdf/meta/agdf-agent-router.md +15 -2
- package/generated/plugins/agdf/meta/agdf-constitution.md +22 -5
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +4 -4
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +60 -2
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +24 -17
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +53 -5
- package/generated/plugins/agdf/skills/release-or/SKILL.md +13 -9
- package/package.json +1 -1
package/bin/create-agdf.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
636
|
+
notes: notes ?? "",
|
|
553
637
|
});
|
|
554
638
|
}
|
|
555
639
|
|
|
556
|
-
const
|
|
557
|
-
|
|
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) =>
|
|
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
|
|
600
|
-
const
|
|
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 =
|
|
606
|
-
let
|
|
607
|
-
let
|
|
608
|
-
let
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
?
|
|
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
|
|