create-agdf 0.3.4 → 0.3.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 (25) hide show
  1. package/README.md +2 -0
  2. package/bin/create-agdf.js +429 -23
  3. package/generated/.agdf/control/templates/AGDF_RUN.md +29 -0
  4. package/generated/.agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
  5. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +7 -10
  6. package/generated/.agdf/control/templates/artefacts/OR.md +20 -4
  7. package/generated/.github/skills/agdf-gate-check/SKILL.md +7 -0
  8. package/generated/.github/skills/agdf-release-or/SKILL.md +2 -0
  9. package/generated/.github/skills/agdf-runtime-contract.md +132 -0
  10. package/generated/.opencode/AGDF.md +4 -0
  11. package/generated/.opencode/agdf-runtime-contract.md +132 -0
  12. package/generated/.opencode/agents/agdf-gate-check.md +7 -0
  13. package/generated/.opencode/agents/agdf-release-or.md +2 -0
  14. package/generated/AGENTS.md +4 -0
  15. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  16. package/generated/plugins/agdf/control/templates/AGDF_RUN.md +29 -0
  17. package/generated/plugins/agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
  18. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +7 -10
  19. package/generated/plugins/agdf/control/templates/artefacts/OR.md +20 -4
  20. package/generated/plugins/agdf/meta/agdf-agent-router.md +4 -0
  21. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  22. package/generated/plugins/agdf/meta/agdf-runtime-contract.md +132 -0
  23. package/generated/plugins/agdf/skills/gate-check/SKILL.md +7 -0
  24. package/generated/plugins/agdf/skills/release-or/SKILL.md +2 -0
  25. package/package.json +1 -1
package/README.md CHANGED
@@ -117,6 +117,8 @@ npx --yes create-agdf@latest gate-check --json
117
117
 
118
118
  The gate check reports `open | blocked`, the current gate, blocking reason, missing exact approval, allowed outputs, forbidden outputs, next allowed action, evidence references and the embedded doctor report.
119
119
 
120
+ `gate-check --json` and `delivery-map --json` also expose a `status_card` object. It is a compact projection of the current control state: current gate, allowed and forbidden actions, blocker, next skill, next permissible step and `quality_outlook`. `next_step` is process permission; `quality_outlook` is the next meaningful quality-improvement focus and does not unlock gates.
121
+
120
122
  Together, `init`, `doctor` and `gate-check --json` turn AGDF from an instruction layer into a repository control system when durable control state is needed. For normal fresh requests, the agent-native path can stay lighter: draft the minimal UR in the response, request `Approval: UR`, and use CLI validators only when machine-readable proof is useful.
121
123
 
122
124
  ## Single source of truth
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { dirname, join, resolve } from "node:path";
4
+ import { dirname, join, posix, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import process from "node:process";
7
7
 
@@ -24,6 +24,36 @@ const deliveryRelationships = [
24
24
  { from: "TP", relationship: "derived_from", to: "SD", requiredBy: "TP" },
25
25
  { from: "QA_REPORT", relationship: "tests", to: "TP", requiredBy: "QA" },
26
26
  ];
27
+ const nextSkillByGate = {
28
+ UR: "gate-check",
29
+ "Brownfield Review": "brownfield-analysis",
30
+ "Mode/Slice Decision": "gate-check",
31
+ "Quick Task Execution": "none",
32
+ PRD: "gate-check",
33
+ SD: "gate-check",
34
+ TP: "gate-check",
35
+ "Brownfield Analysis": "brownfield-analysis",
36
+ "CD+Tests": "none",
37
+ CR: "code-review",
38
+ QA: "qa-gate",
39
+ UAT: "delivery-closeout",
40
+ OR: "release-or",
41
+ };
42
+ const gateProgressOrder = [
43
+ "UR",
44
+ "Brownfield Review",
45
+ "Mode/Slice Decision",
46
+ "Quick Task Execution",
47
+ "PRD",
48
+ "SD",
49
+ "TP",
50
+ "Brownfield Analysis",
51
+ "CD+Tests",
52
+ "CR",
53
+ "QA",
54
+ "UAT",
55
+ "OR",
56
+ ];
27
57
  const codexSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.codex.skillPrefix}${skill.slug}`);
28
58
  const copilotSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.copilot.skillPrefix}${skill.slug}`);
29
59
  const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.opencode.skillPrefix}${skill.slug}`);
@@ -510,6 +540,222 @@ function tableRows(content) {
510
540
  });
511
541
  }
512
542
 
543
+ const compactBacklogHeaders = ["priority", "key", "work item", "status", "artefacts", "current spec", "next step"];
544
+ const legacyBacklogHeaders = ["prio", "key", "title", "status", "ur", "brownfield review", "prd", "sd", "tp", "qa", "or", "current spec", "notes"];
545
+ const backlogStatusLabels = new Map([
546
+ ["needs ur", "needs_ur"],
547
+ ["awaiting brownfield review", "awaiting_brownfield_review"],
548
+ ["awaiting prd", "awaiting_prd"],
549
+ ["awaiting prd approval", "awaiting_prd_approval"],
550
+ ["awaiting sd", "awaiting_sd"],
551
+ ["awaiting sd approval", "awaiting_sd_approval"],
552
+ ["awaiting tp", "awaiting_tp"],
553
+ ["awaiting tp approval", "awaiting_tp_approval"],
554
+ ["in progress", "in_progress"],
555
+ ["blocked", "blocked"],
556
+ ["awaiting qa", "awaiting_qa"],
557
+ ["awaiting uat", "awaiting_uat"],
558
+ ["completed", "completed"],
559
+ ["superseded", "superseded"],
560
+ ["abandoned", "abandoned"],
561
+ ]);
562
+ const backlogArtefactLabels = new Map([
563
+ ["ur", "ur"],
564
+ ["brownfield", "brownfield_review"],
565
+ ["prd", "prd"],
566
+ ["sd", "sd"],
567
+ ["tp", "tp"],
568
+ ["qa", "qa"],
569
+ ["or", "or"],
570
+ ]);
571
+
572
+ function normalizeBacklogHeader(value) {
573
+ return value.replace(/`/g, "").trim().toLowerCase();
574
+ }
575
+
576
+ function markdownLink(value) {
577
+ const match = value.trim().match(/^\[([^\]]+)\]\(([^)]+)\)$/);
578
+ return match ? { label: match[1].trim(), target: match[2].trim() } : null;
579
+ }
580
+
581
+ function resolvedBacklogLinkTarget(target) {
582
+ if (!target
583
+ || target.startsWith("/")
584
+ || target.includes("\\")
585
+ || /^[a-z][a-z0-9+.-]*:/i.test(target)) {
586
+ return null;
587
+ }
588
+ const resolved = posix.normalize(posix.join(".agdf/control", target));
589
+ if (resolved === ".." || resolved.startsWith("../")) return null;
590
+ return resolved;
591
+ }
592
+
593
+ function normalizedBacklogLinkTarget(value, findings, backlogPath, label) {
594
+ const link = markdownLink(value);
595
+ if (!link) return value.trim();
596
+ const resolved = resolvedBacklogLinkTarget(link.target);
597
+ if (!resolved) {
598
+ if (findings) {
599
+ addFinding(
600
+ findings,
601
+ "revise",
602
+ "AGDF_BACKLOG_LINK_TARGET_INVALID",
603
+ `Backlog ${label} link must be relative to MASTER_BACKLOG.md: ${link.target || "<empty>"}.`,
604
+ backlogPath,
605
+ "Use a document-relative artefact target such as artefacts/<key>/UR.md.",
606
+ );
607
+ }
608
+ return link.target;
609
+ }
610
+ return resolved;
611
+ }
612
+
613
+ function normalizeBacklogStatus(value, findings, backlogPath) {
614
+ const cleaned = cleanStatusCell(value ?? "");
615
+ const lookup = cleaned.replaceAll("_", " ").toLowerCase();
616
+ const normalized = backlogStatusLabels.get(lookup);
617
+ if (normalized) return normalized;
618
+ if (/^[a-z][a-z0-9_]*$/.test(cleaned)) return cleaned;
619
+ if (findings && filled(cleaned)) {
620
+ addFinding(
621
+ findings,
622
+ "revise",
623
+ "AGDF_BACKLOG_STATUS_UNKNOWN",
624
+ `MASTER_BACKLOG.md uses an unknown human status label: ${cleaned}.`,
625
+ backlogPath,
626
+ "Use a documented human status label or a supported legacy snake_case value.",
627
+ );
628
+ }
629
+ return cleaned;
630
+ }
631
+
632
+ function emptyBacklogPointer() {
633
+ return {
634
+ prio: "",
635
+ key: "",
636
+ title: "",
637
+ status: "",
638
+ ur: "",
639
+ brownfield_review: "",
640
+ prd: "",
641
+ sd: "",
642
+ tp: "",
643
+ qa: "",
644
+ or: "",
645
+ current_spec: "",
646
+ notes: "",
647
+ };
648
+ }
649
+
650
+ function parseCompactArtefacts(value, pointer, findings, backlogPath) {
651
+ if (!filled(value)) return;
652
+ const seen = new Set();
653
+ for (const entry of value.split(/\s+·\s+/)) {
654
+ const link = markdownLink(entry);
655
+ if (!link) {
656
+ if (findings) {
657
+ addFinding(
658
+ findings,
659
+ "revise",
660
+ "AGDF_BACKLOG_ARTEFACT_LINK_INVALID",
661
+ `Compact Artefacts entry is not a Markdown link: ${entry}.`,
662
+ backlogPath,
663
+ "Use [UR](artefacts/<key>/UR.md) style links separated by a middle dot.",
664
+ );
665
+ }
666
+ continue;
667
+ }
668
+ const field = backlogArtefactLabels.get(link.label.toLowerCase());
669
+ if (!field) {
670
+ if (findings) {
671
+ addFinding(
672
+ findings,
673
+ "revise",
674
+ "AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN",
675
+ `Compact Artefacts uses an unknown label: ${link.label}.`,
676
+ backlogPath,
677
+ "Use UR, Brownfield, PRD, SD, TP, QA or OR.",
678
+ );
679
+ }
680
+ continue;
681
+ }
682
+ if (seen.has(field)) {
683
+ if (findings) {
684
+ addFinding(
685
+ findings,
686
+ "revise",
687
+ "AGDF_BACKLOG_ARTEFACT_LABEL_DUPLICATE",
688
+ `Compact Artefacts repeats the ${link.label} link.`,
689
+ backlogPath,
690
+ "Keep exactly one link per artefact type.",
691
+ );
692
+ }
693
+ continue;
694
+ }
695
+ seen.add(field);
696
+ pointer[field] = normalizedBacklogLinkTarget(entry, findings, backlogPath, link.label);
697
+ }
698
+ }
699
+
700
+ function parseBacklogSection(section, findings = null, backlogPath = ".agdf/control/MASTER_BACKLOG.md") {
701
+ const rows = tableRows(section);
702
+ if (rows.length === 0) return [];
703
+ const headers = rows[0].map(normalizeBacklogHeader);
704
+ const layout = headers.join("|") === compactBacklogHeaders.join("|")
705
+ ? "compact"
706
+ : headers.join("|") === legacyBacklogHeaders.join("|")
707
+ ? "legacy"
708
+ : "unknown";
709
+
710
+ if (layout === "unknown") {
711
+ if (findings) {
712
+ addFinding(
713
+ findings,
714
+ "revise",
715
+ "AGDF_BACKLOG_LAYOUT_UNKNOWN",
716
+ `MASTER_BACKLOG.md uses an unsupported table layout: ${headers.join(", ")}.`,
717
+ backlogPath,
718
+ "Use the canonical compact layout or the supported legacy 13-column layout.",
719
+ );
720
+ }
721
+ return [];
722
+ }
723
+
724
+ return rows.slice(1)
725
+ .filter((cells) => cells.some((cell) => filled(cell)))
726
+ .map((cells) => {
727
+ const pointer = emptyBacklogPointer();
728
+ if (layout === "legacy") {
729
+ [
730
+ pointer.prio,
731
+ pointer.key,
732
+ pointer.title,
733
+ pointer.status,
734
+ pointer.ur,
735
+ pointer.brownfield_review,
736
+ pointer.prd,
737
+ pointer.sd,
738
+ pointer.tp,
739
+ pointer.qa,
740
+ pointer.or,
741
+ pointer.current_spec,
742
+ pointer.notes,
743
+ ] = cells.map((cell) => cell ?? "");
744
+ pointer.status = normalizeBacklogStatus(pointer.status, findings, backlogPath);
745
+ return pointer;
746
+ }
747
+
748
+ pointer.prio = cells[0] ?? "";
749
+ pointer.key = cleanStatusCell(cells[1] ?? "");
750
+ pointer.title = cells[2] ?? "";
751
+ pointer.status = normalizeBacklogStatus(cells[3], findings, backlogPath);
752
+ parseCompactArtefacts(cells[4] ?? "", pointer, findings, backlogPath);
753
+ pointer.current_spec = normalizedBacklogLinkTarget(cells[5] ?? "", findings, backlogPath, "Current spec");
754
+ pointer.notes = cells[6] ?? "";
755
+ return pointer;
756
+ });
757
+ }
758
+
513
759
  function hasFilledTableRow(content, firstCellPattern) {
514
760
  return tableRows(content)
515
761
  .filter((cells) => firstCellPattern.test(cells[0] ?? ""))
@@ -619,7 +865,12 @@ function evaluateDoctor(targetDir) {
619
865
 
620
866
  const backlogPath = join(".agdf", "control", "MASTER_BACKLOG.md");
621
867
  const backlog = readTargetFile(targetDir, backlogPath);
622
- if (!hasFilledTableRow(backlog, /^P[0-9]/)) {
868
+ parseBacklogSection(markdownSection(backlog, "Active Backlog"), findings, backlogPath);
869
+ parseBacklogSection(markdownSection(backlog, "Planned / Parking Lot"), findings, backlogPath);
870
+ const completedRows = tableRows(markdownSection(backlog, "Completed / Superseded Pointers"))
871
+ .slice(1)
872
+ .filter((cells) => cells.some((cell) => filled(cell)));
873
+ if (!hasFilledTableRow(backlog, /^P[0-9]/) && completedRows.length === 0) {
623
874
  addFinding(
624
875
  findings,
625
876
  "warn",
@@ -747,12 +998,18 @@ function cleanStatusCell(value) {
747
998
  return value.replace(/^`|`$/g, "").trim();
748
999
  }
749
1000
 
1001
+ function normalizeGateStatus(gate, status) {
1002
+ const normalized = cleanStatusCell(status ?? "");
1003
+ if (gate === "QA" && normalized === "passed") return "approved";
1004
+ return normalized;
1005
+ }
1006
+
750
1007
  function addApprovalRows(approvals, section) {
751
1008
  for (const cells of tableRows(section)) {
752
1009
  const [gate, status, evidence] = cells;
753
1010
  if (!userGateOrder.includes(gate)) continue;
754
1011
  approvals.set(gate, {
755
- status: cleanStatusCell(status ?? ""),
1012
+ status: normalizeGateStatus(gate, status),
756
1013
  evidence: evidence ?? "",
757
1014
  });
758
1015
  }
@@ -774,6 +1031,9 @@ function readRunState(targetDir) {
774
1031
  missing_evidence: [],
775
1032
  risks: [],
776
1033
  context_graph: {},
1034
+ quality_outlook: "",
1035
+ source_scope: {},
1036
+ memory: {},
777
1037
  };
778
1038
  }
779
1039
 
@@ -846,6 +1106,23 @@ function readRunState(targetDir) {
846
1106
  evidence: extractSectionField(modeSliceSection, "evidence"),
847
1107
  };
848
1108
 
1109
+ const sourceScopeSection = markdownSection(content, "Source And Scope State");
1110
+ const sourceScope = {
1111
+ normative_instruction_source: extractSectionField(sourceScopeSection, "normative_instruction_source"),
1112
+ multi_scope_state: cleanStatusCell(extractSectionField(sourceScopeSection, "multi_scope_state")),
1113
+ active_scope_evidence: extractSectionField(sourceScopeSection, "active_scope_evidence"),
1114
+ competing_scope_lines: extractSectionField(sourceScopeSection, "competing_scope_lines"),
1115
+ branch_workspace_evidence: extractSectionField(sourceScopeSection, "branch_workspace_evidence"),
1116
+ branch_workspace_scope_effect: cleanStatusCell(extractSectionField(sourceScopeSection, "branch_workspace_scope_effect")),
1117
+ };
1118
+
1119
+ const memorySection = markdownSection(content, "Knowledge Persistence Decision");
1120
+ const memory = {
1121
+ target: cleanStatusCell(extractSectionField(memorySection, "memory_target")),
1122
+ reason: extractSectionField(memorySection, "memory_reason"),
1123
+ refs: extractSectionField(memorySection, "memory_refs"),
1124
+ };
1125
+
849
1126
  return {
850
1127
  path: runPath,
851
1128
  content,
@@ -859,6 +1136,9 @@ function readRunState(targetDir) {
859
1136
  missing_evidence: missingEvidence,
860
1137
  risks,
861
1138
  context_graph: contextGraph,
1139
+ quality_outlook: extractField(content, "quality_outlook"),
1140
+ source_scope: sourceScope,
1141
+ memory,
862
1142
  };
863
1143
  }
864
1144
 
@@ -890,7 +1170,7 @@ function isDurableGateArtefactSatisfied(runState, gate) {
890
1170
  if (!durableGateArtefacts.has(gate)) return true;
891
1171
  const artefact = gateArtefactStatus(runState, gate);
892
1172
  if (!artefact.path || isPlaceholderValue(artefact.path)) return false;
893
- if (gate === "QA") return artefact.status === "pass";
1173
+ if (gate === "QA") return artefact.status === "pass" || artefact.status === "passed";
894
1174
  return artefact.status === "approved";
895
1175
  }
896
1176
 
@@ -997,15 +1277,97 @@ function analyzeDeliveryMap(runState) {
997
1277
  });
998
1278
  }
999
1279
 
1280
+ const multiScopeState = runState.source_scope?.multi_scope_state;
1281
+ if (multiScopeState === "ambiguous" || multiScopeState === "blocked") {
1282
+ findings.push({
1283
+ severity: multiScopeState === "blocked" ? "block" : "revise",
1284
+ code: "AGDF_SCOPE_AMBIGUOUS",
1285
+ message: "Multiple active scope lines are plausible; the agent must not choose one silently.",
1286
+ path: runState.path,
1287
+ next_step: "List competing scope lines with gate and artefact evidence, then clarify the active scope.",
1288
+ });
1289
+ }
1290
+
1291
+ const branchEffect = runState.source_scope?.branch_workspace_scope_effect;
1292
+ if (branchEffect === "conflicts" || branchEffect === "insufficient") {
1293
+ findings.push({
1294
+ severity: branchEffect === "conflicts" ? "revise" : "warn",
1295
+ code: "AGDF_BRANCH_NOT_SCOPE_PROOF",
1296
+ message: "Branch or workspace evidence is not sufficient scope proof.",
1297
+ path: runState.path,
1298
+ next_step: "Confirm the active scope from durable artefacts or record why branch/workspace evidence is only supporting evidence.",
1299
+ });
1300
+ }
1301
+
1302
+ if (runState.memory?.target && runState.memory.target !== "none" && !filled(runState.memory.reason)) {
1303
+ findings.push({
1304
+ severity: "warn",
1305
+ code: "AGDF_MEMORY_TARGET_REASON_MISSING",
1306
+ message: "Knowledge persistence target is set without a reason.",
1307
+ path: runState.path,
1308
+ next_step: "Fill memory_reason or set memory_target to none.",
1309
+ });
1310
+ }
1311
+
1000
1312
  return {
1001
1313
  relationships,
1002
1314
  missing_evidence: runState.missing_evidence,
1003
1315
  risks: runState.risks,
1004
1316
  context_graph: runState.context_graph,
1317
+ source_scope: runState.source_scope,
1318
+ memory: runState.memory,
1005
1319
  findings,
1006
1320
  };
1007
1321
  }
1008
1322
 
1323
+ function deriveQualityOutlook(runState, findings = []) {
1324
+ if (filled(runState.quality_outlook)) return runState.quality_outlook;
1325
+ if (findings.some((finding) => finding.severity === "block" || finding.severity === "revise")) {
1326
+ return "Resolve blocking or revise-level delivery-map findings before making stronger quality claims.";
1327
+ }
1328
+ if (findings.some((finding) => finding.severity === "warn")) {
1329
+ return "Review warning-level findings when investing further in delivery confidence.";
1330
+ }
1331
+ return "No additional quality follow-up identified from the current control state.";
1332
+ }
1333
+
1334
+ function buildStatusCard({
1335
+ status,
1336
+ currentGate,
1337
+ allowed = [],
1338
+ forbidden = [],
1339
+ blockingReason = "none",
1340
+ missingApproval = "none",
1341
+ nextAllowedAction,
1342
+ runState,
1343
+ findings = [],
1344
+ }) {
1345
+ const qualityOutlook = deriveQualityOutlook(runState, findings);
1346
+ return {
1347
+ mode: extractField(runState.content ?? "", "mode") || "unknown",
1348
+ status,
1349
+ current_gate: currentGate,
1350
+ mode_slice_decision: runState.mode_slice_decision?.decision || "undecided",
1351
+ allowed_now: allowed,
1352
+ forbidden_now: forbidden,
1353
+ blocking_condition: blockingReason || "none",
1354
+ missing_approval: missingApproval || "none",
1355
+ evidence: runState.evidence_refs,
1356
+ next_skill: nextSkillByGate[currentGate] ?? "gate-check",
1357
+ next_step: nextAllowedAction,
1358
+ quality_outlook: qualityOutlook,
1359
+ };
1360
+ }
1361
+
1362
+ function effectiveCurrentGate(runState, transitionDecision) {
1363
+ if (transitionDecision.status !== "open") return transitionDecision.current_gate;
1364
+ const explicitGate = normalizeCurrentGate(runState.current_gate, "");
1365
+ const explicitIndex = gateProgressOrder.indexOf(explicitGate);
1366
+ const fallbackIndex = gateProgressOrder.indexOf(transitionDecision.current_gate);
1367
+ if (explicitIndex === -1 || fallbackIndex === -1) return transitionDecision.current_gate;
1368
+ return explicitIndex > fallbackIndex ? explicitGate : transitionDecision.current_gate;
1369
+ }
1370
+
1009
1371
  function durableArtefactBlock(gate, nextGate) {
1010
1372
  const label = gate === "QA" ? "QA report" : `${gate} artefact`;
1011
1373
  const stablePath = gate === "QA" ? ".agdf/control/artefacts/<key>/QA_REPORT.md" : `.agdf/control/artefacts/<key>/${gate}.md`;
@@ -1030,6 +1392,7 @@ function isGateSatisfied(runState, gate) {
1030
1392
  const status = gateApprovalStatus(runState, gate);
1031
1393
  if (status === "not_applicable") return true;
1032
1394
  if (status !== "approved") return false;
1395
+ if (gate === "UAT") return true;
1033
1396
  return isDurableGateArtefactSatisfied(runState, gate);
1034
1397
  }
1035
1398
 
@@ -1151,6 +1514,30 @@ function transitionDecisionForRunState(runState) {
1151
1514
 
1152
1515
  if (gateApprovalStatus(runState, "QA") === "approved" && !isGateSatisfied(runState, "QA")) return durableArtefactBlock("QA", "UAT");
1153
1516
 
1517
+ if (isGateSatisfied(runState, "QA") && !isGateSatisfied(runState, "UAT")) {
1518
+ return {
1519
+ status: "open",
1520
+ current_gate: "UAT",
1521
+ blocking_reason: "none",
1522
+ missing_approval: "Approval: UAT",
1523
+ allowed: ["request exact UAT approval", "prepare non-operative delivery summary"],
1524
+ forbidden: ["release", "push", "open PR", "commit without explicit user instruction and required approval"],
1525
+ next_allowed_action: "Request exact approval: Approval: UAT before delivery handoff.",
1526
+ };
1527
+ }
1528
+
1529
+ if (isGateSatisfied(runState, "UAT")) {
1530
+ return {
1531
+ status: "open",
1532
+ current_gate: "OR",
1533
+ blocking_reason: "none",
1534
+ missing_approval: "none",
1535
+ allowed: ["produce OR or delivery closeout", "prepare commit, push or PR handoff when requested"],
1536
+ forbidden: ["commit, push, open PR or release automatically"],
1537
+ next_allowed_action: "Produce delivery closeout or requested handoff; do not perform VCS actions automatically.",
1538
+ };
1539
+ }
1540
+
1154
1541
  return {
1155
1542
  status: "open",
1156
1543
  current_gate: "Brownfield Analysis",
@@ -1171,7 +1558,7 @@ function evaluateGateCheck(targetDir) {
1171
1558
  const doctorRevise = doctorReport.findings.find((finding) => finding.severity === "revise");
1172
1559
 
1173
1560
  let status = transitionDecision.status;
1174
- let currentGate = transitionDecision.current_gate;
1561
+ let currentGate = effectiveCurrentGate(runState, transitionDecision);
1175
1562
  let blockingReason = transitionDecision.blocking_reason;
1176
1563
  let missingApproval = transitionDecision.missing_approval;
1177
1564
  let allowed = transitionDecision.allowed;
@@ -1220,10 +1607,24 @@ function evaluateGateCheck(targetDir) {
1220
1607
  doctor_status: doctorReport.status,
1221
1608
  doctor_summary: doctorReport.summary,
1222
1609
  evidence_refs: runState.evidence_refs,
1610
+ quality_outlook: deriveQualityOutlook(runState, deliveryMap.findings),
1611
+ status_card: buildStatusCard({
1612
+ status,
1613
+ currentGate,
1614
+ allowed,
1615
+ forbidden,
1616
+ blockingReason,
1617
+ missingApproval,
1618
+ nextAllowedAction,
1619
+ runState,
1620
+ findings: deliveryMap.findings,
1621
+ }),
1223
1622
  delivery_map: {
1224
1623
  relationships: deliveryMap.relationships,
1225
1624
  mode_slice_decision: runState.mode_slice_decision,
1226
1625
  context_graph: deliveryMap.context_graph,
1626
+ source_scope: deliveryMap.source_scope,
1627
+ memory: deliveryMap.memory,
1227
1628
  findings: deliveryMap.findings,
1228
1629
  },
1229
1630
  doctor_report: doctorReport,
@@ -1240,6 +1641,7 @@ function printGateCheckReport(report, json) {
1240
1641
  console.log(`Current gate: ${report.current_gate}`);
1241
1642
  console.log(`Blocking reason: ${report.blocking_reason}`);
1242
1643
  console.log(`Missing approval: ${report.missing_approval}`);
1644
+ console.log(`Quality outlook: ${report.quality_outlook}`);
1243
1645
  console.log(`Doctor: ${report.doctor_status} (${report.doctor_summary.findings} findings)`);
1244
1646
  console.log("");
1245
1647
  console.log("Allowed:");
@@ -1257,22 +1659,7 @@ function readBacklogPointers(targetDir) {
1257
1659
 
1258
1660
  const backlog = readTargetFile(targetDir, backlogPath);
1259
1661
  const activeSection = markdownSection(backlog, "Active Backlog");
1260
- return nonTemplateRows(activeSection, "Prio")
1261
- .map((cells) => ({
1262
- prio: cells[0] ?? "",
1263
- key: cells[1] ?? "",
1264
- title: cells[2] ?? "",
1265
- status: cleanStatusCell(cells[3] ?? ""),
1266
- ur: cells[4] ?? "",
1267
- brownfield_review: cells[5] ?? "",
1268
- prd: cells[6] ?? "",
1269
- sd: cells[7] ?? "",
1270
- tp: cells[8] ?? "",
1271
- qa: cells[9] ?? "",
1272
- or: cells[10] ?? "",
1273
- current_spec: cells[11] ?? "",
1274
- notes: cells[12] ?? "",
1275
- }));
1662
+ return parseBacklogSection(activeSection);
1276
1663
  }
1277
1664
 
1278
1665
  function evaluateDeliveryMap(targetDir) {
@@ -1280,6 +1667,7 @@ function evaluateDeliveryMap(targetDir) {
1280
1667
  const runState = readRunState(targetDir);
1281
1668
  const map = analyzeDeliveryMap(runState);
1282
1669
  const gateDecision = transitionDecisionForRunState(runState);
1670
+ const currentGate = effectiveCurrentGate(runState, gateDecision);
1283
1671
 
1284
1672
  const severityRank = { block: 3, revise: 2, warn: 1 };
1285
1673
  const deliverySeverity = map.findings.reduce((max, finding) => Math.max(max, severityRank[finding.severity] ?? 0), 0);
@@ -1287,13 +1675,28 @@ function evaluateDeliveryMap(targetDir) {
1287
1675
  const maxSeverity = Math.max(deliverySeverity, doctorSeverity);
1288
1676
  const status = maxSeverity >= 3 ? "block" : maxSeverity === 2 ? "revise" : maxSeverity === 1 ? "warn" : "pass";
1289
1677
 
1678
+ const qualityOutlook = deriveQualityOutlook(runState, map.findings);
1679
+ const nextAllowedAction = isPlaceholderValue(runState.next_allowed_action) ? gateDecision.next_allowed_action : runState.next_allowed_action;
1680
+
1290
1681
  return {
1291
1682
  schema_version: "1",
1292
1683
  status,
1293
1684
  checked_at: new Date().toISOString(),
1294
1685
  target_dir: targetDir,
1295
- current_gate: gateDecision.current_gate,
1296
- next_allowed_action: isPlaceholderValue(runState.next_allowed_action) ? gateDecision.next_allowed_action : runState.next_allowed_action,
1686
+ current_gate: currentGate,
1687
+ next_allowed_action: nextAllowedAction,
1688
+ quality_outlook: qualityOutlook,
1689
+ status_card: buildStatusCard({
1690
+ status,
1691
+ currentGate,
1692
+ allowed: gateDecision.allowed,
1693
+ forbidden: gateDecision.forbidden,
1694
+ blockingReason: gateDecision.blocking_reason,
1695
+ missingApproval: gateDecision.missing_approval,
1696
+ nextAllowedAction,
1697
+ runState,
1698
+ findings: map.findings,
1699
+ }),
1297
1700
  backlog_pointers: readBacklogPointers(targetDir),
1298
1701
  artefacts: Object.fromEntries([...runState.artefacts.entries()]),
1299
1702
  approvals: Object.fromEntries([...runState.approvals.entries()]),
@@ -1303,6 +1706,8 @@ function evaluateDeliveryMap(targetDir) {
1303
1706
  missing_evidence: map.missing_evidence,
1304
1707
  risks: map.risks,
1305
1708
  context_graph: map.context_graph,
1709
+ source_scope: map.source_scope,
1710
+ memory: map.memory,
1306
1711
  findings: map.findings,
1307
1712
  doctor_status: doctorReport.status,
1308
1713
  doctor_summary: doctorReport.summary,
@@ -1319,6 +1724,7 @@ function printDeliveryMapReport(report, json) {
1319
1724
  console.log(`AGDF delivery-map: ${report.status}`);
1320
1725
  console.log(`Current gate: ${report.current_gate}`);
1321
1726
  console.log(`Next allowed action: ${report.next_allowed_action}`);
1727
+ console.log(`Quality outlook: ${report.quality_outlook}`);
1322
1728
  console.log(`Relationships: ${report.relationships.filter((item) => item.status === "pass").length}/${report.relationships.length} evidenced`);
1323
1729
  console.log(`Findings: ${report.findings.length}`);
1324
1730
 
@@ -23,6 +23,29 @@ What outcome is this run trying to make trustworthy?
23
23
  | What is the next allowed action? | |
24
24
  | What is explicitly forbidden right now? | |
25
25
 
26
+ ## Source And Scope State
27
+
28
+ - normative_instruction_source:
29
+ - multi_scope_state: `clear | ambiguous | blocked`
30
+ - active_scope_evidence:
31
+ - competing_scope_lines:
32
+ - branch_workspace_evidence:
33
+ - branch_workspace_scope_effect: `supports | conflicts | insufficient | not_checked`
34
+
35
+ ## Run Status Card
36
+
37
+ This is a compact projection of the control state. It does not replace gate-check, QA, OR or approvals.
38
+
39
+ | Run status | Value |
40
+ |---|---|
41
+ | Status | |
42
+ | Current gate | |
43
+ | Allowed now | |
44
+ | Blocked by | |
45
+ | Missing approval | |
46
+ | Next step | |
47
+ | Quality outlook | |
48
+
26
49
  ## Approvals
27
50
 
28
51
  Valid approval format for new runs: `Approval: <GateName>`.
@@ -98,6 +121,12 @@ Keep the active work item traceable. A gate may open only when the previous gate
98
121
  - context_graph_gate_effect: `none | warning | revise | block`
99
122
  - context_graph_evidence:
100
123
 
124
+ ## Knowledge Persistence Decision
125
+
126
+ - memory_target: `context_graph | sot_registry | scope_artifact | open_questions | none`
127
+ - memory_reason:
128
+ - memory_refs:
129
+
101
130
  ## Closeout
102
131
 
103
132
  - delivered: