create-agdf 0.3.2 → 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.
- package/README.md +4 -9
- package/bin/create-agdf.js +429 -23
- package/generated/.agdf/control/templates/AGDF_RUN.md +29 -0
- package/generated/.agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
- package/generated/.agdf/control/templates/MASTER_BACKLOG.md +7 -10
- package/generated/.agdf/control/templates/artefacts/OR.md +20 -4
- package/generated/.github/skills/agdf-gate-check/SKILL.md +7 -0
- package/generated/.github/skills/agdf-release-or/SKILL.md +2 -0
- package/generated/.github/skills/agdf-runtime-contract.md +132 -0
- package/generated/.opencode/AGDF.md +4 -0
- package/generated/.opencode/agdf-runtime-contract.md +132 -0
- package/generated/.opencode/agents/agdf-gate-check.md +7 -0
- package/generated/.opencode/agents/agdf-release-or.md +2 -0
- package/generated/AGENTS.md +4 -0
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/AGDF_RUN.md +29 -0
- package/generated/plugins/agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
- package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +7 -10
- package/generated/plugins/agdf/control/templates/artefacts/OR.md +20 -4
- package/generated/plugins/agdf/meta/agdf-agent-router.md +4 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +132 -0
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +7 -0
- package/generated/plugins/agdf/skills/release-or/SKILL.md +2 -0
- 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
|
|
@@ -147,15 +149,8 @@ The routing test installs `both` into a temporary target repository and checks t
|
|
|
147
149
|
|
|
148
150
|
## Publishing
|
|
149
151
|
|
|
150
|
-
The repository publishes this package
|
|
151
|
-
|
|
152
|
-
The primary user-facing CLI package is published separately from `agdf/` and delegates to this package through the shared `create-agdf/cli` export.
|
|
153
|
-
|
|
154
|
-
- From the repository root, run `npm run set-version -- <version>`
|
|
155
|
-
- The version script updates the coupled package, plugin and site versions and refuses versions already published as `create-agdf` or `@agdf/cli`
|
|
156
|
-
- Push a matching git tag in the form `create-agdf-v<version>`
|
|
157
|
-
- After `create-agdf` is published, push `agdf-cli-v<version>` for the primary CLI package
|
|
158
|
-
- Ensure the repository secret `NPM_TOKEN` exists with publish rights for `create-agdf`
|
|
152
|
+
The repository publishes this package and the primary user-facing `@agdf/cli` wrapper as one coupled AGDF release.
|
|
153
|
+
See the root `RELEASE.md` for the sequenced `agdf-v<version>` workflow and npm token requirements.
|
|
159
154
|
|
|
160
155
|
## Trademark Notice
|
|
161
156
|
|
package/bin/create-agdf.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
1296
|
-
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:
|