create-agdf 0.6.9 → 0.7.1
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 +40 -2
- package/bin/create-agdf.js +318 -65
- package/generated/.agdf/control/README.md +2 -1
- package/generated/.agdf/control/templates/AGDF_RUN.md +4 -3
- package/generated/.agdf/control/templates/RUN_STATE.md +22 -0
- package/generated/.agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +1 -1
- package/generated/.agdf/control/templates/artefacts/VERIFIED_CHANGE.md +48 -0
- package/generated/.github/skills/agdf-runtime-contract.md +29 -5
- package/generated/.opencode/AGDF.md +3 -3
- package/generated/.opencode/agdf-runtime-contract.md +29 -5
- package/generated/AGENTS.md +3 -3
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -2
- package/generated/plugins/agdf/control/README.md +2 -1
- package/generated/plugins/agdf/control/templates/AGDF_RUN.md +4 -3
- package/generated/plugins/agdf/control/templates/RUN_STATE.md +22 -0
- package/generated/plugins/agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +1 -1
- package/generated/plugins/agdf/control/templates/artefacts/VERIFIED_CHANGE.md +48 -0
- package/generated/plugins/agdf/meta/agdf-agent-router.md +3 -3
- package/generated/plugins/agdf/meta/agdf-constitution.md +3 -3
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -2
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +29 -5
- package/generated/plugins/agdf/meta/agdf-tenets.md +5 -0
- package/lib/control-state/run-state-parser.js +7 -3
- package/package.json +4 -2
package/bin/create-agdf.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
-
import { dirname, join, posix, resolve } from "node:path";
|
|
6
|
+
import { dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { searchInputFromControl } from "../lib/delivery-path-search/state-adapter.js";
|
|
@@ -32,14 +32,23 @@ const generatedRoot = join(packageRoot, "generated");
|
|
|
32
32
|
const pluginDefinitionPath = join(generatedRoot, "plugins", "agdf", "meta", "agdf-plugin.definition.json");
|
|
33
33
|
const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
|
|
34
34
|
const pluginInstallCommand = "npx --yes @agdf/cli@latest claude";
|
|
35
|
-
const
|
|
36
|
-
const
|
|
35
|
+
const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
|
|
36
|
+
const npmCommand = testNpmCliPath
|
|
37
|
+
? process.execPath
|
|
38
|
+
: process.platform === "win32"
|
|
39
|
+
? process.execPath
|
|
40
|
+
: "npm";
|
|
41
|
+
const npmPrefixArgs = testNpmCliPath
|
|
42
|
+
? [testNpmCliPath]
|
|
43
|
+
: process.platform === "win32"
|
|
44
|
+
? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")]
|
|
45
|
+
: [];
|
|
37
46
|
const allowedTargets = new Set(["codex", "codex-repo", "claude", "copilot", "opencode", "opencode-status", "opencode-repo", "both", "init", "config", "doctor", "gate-check", "delivery-map", "delivery-path-search", "run-create", "run-migrate", "run-render-legacy"]);
|
|
38
47
|
const agdfFragmentPath = "AGENTS.agdf.md";
|
|
39
48
|
const openCodeConfigFragmentPath = "opencode.agdf.json";
|
|
40
49
|
const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
41
50
|
const durableGateArtefacts = new Set(["UR", "PRD", "SD", "TP", "QA"]);
|
|
42
|
-
const internalStepArtefacts = new Set(["Brownfield Review"]);
|
|
51
|
+
const internalStepArtefacts = new Set(["Brownfield Review", "Verified Change", "Brownfield Analysis", "CD+Tests", "CR"]);
|
|
43
52
|
const deliveryRelationships = [
|
|
44
53
|
{ from: "UR", relationship: "approved_by", to: "Approval: UR", requiredBy: "UR" },
|
|
45
54
|
{ from: "PRD", relationship: "derived_from", to: "UR", requiredBy: "PRD" },
|
|
@@ -52,6 +61,7 @@ const nextSkillByGate = {
|
|
|
52
61
|
"Brownfield Review": "brownfield-analysis",
|
|
53
62
|
"Mode/Slice Decision": "gate-check",
|
|
54
63
|
"Quick Task Execution": "none",
|
|
64
|
+
"Verified Change Execution": "gate-check",
|
|
55
65
|
PRD: "gate-check",
|
|
56
66
|
SD: "gate-check",
|
|
57
67
|
TP: "gate-check",
|
|
@@ -62,21 +72,6 @@ const nextSkillByGate = {
|
|
|
62
72
|
UAT: "delivery-closeout",
|
|
63
73
|
OR: "release-or",
|
|
64
74
|
};
|
|
65
|
-
const gateProgressOrder = [
|
|
66
|
-
"UR",
|
|
67
|
-
"Brownfield Review",
|
|
68
|
-
"Mode/Slice Decision",
|
|
69
|
-
"Quick Task Execution",
|
|
70
|
-
"PRD",
|
|
71
|
-
"SD",
|
|
72
|
-
"TP",
|
|
73
|
-
"Brownfield Analysis",
|
|
74
|
-
"CD+Tests",
|
|
75
|
-
"CR",
|
|
76
|
-
"QA",
|
|
77
|
-
"UAT",
|
|
78
|
-
"OR",
|
|
79
|
-
];
|
|
80
75
|
const codexSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.codex.skillPrefix}${skill.slug}`);
|
|
81
76
|
const copilotSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.copilot.skillPrefix}${skill.slug}`);
|
|
82
77
|
const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.opencode.skillPrefix}${skill.slug}`);
|
|
@@ -96,6 +91,7 @@ const codexPluginFiles = [
|
|
|
96
91
|
join("plugins", "agdf", "control", "templates", "AGENT_QUALITY_CONTRACTS.json"),
|
|
97
92
|
join("plugins", "agdf", "control", "templates", "artefacts", "UR.md"),
|
|
98
93
|
join("plugins", "agdf", "control", "templates", "artefacts", "BROWNFIELD_REVIEW.md"),
|
|
94
|
+
join("plugins", "agdf", "control", "templates", "artefacts", "VERIFIED_CHANGE.md"),
|
|
99
95
|
join("plugins", "agdf", "control", "templates", "artefacts", "PRD.md"),
|
|
100
96
|
join("plugins", "agdf", "control", "templates", "artefacts", "SD.md"),
|
|
101
97
|
join("plugins", "agdf", "control", "templates", "artefacts", "TP.md"),
|
|
@@ -120,6 +116,7 @@ const controlFiles = [
|
|
|
120
116
|
join(".agdf", "control", "templates", "AGENT_QUALITY_CONTRACTS.json"),
|
|
121
117
|
join(".agdf", "control", "templates", "artefacts", "UR.md"),
|
|
122
118
|
join(".agdf", "control", "templates", "artefacts", "BROWNFIELD_REVIEW.md"),
|
|
119
|
+
join(".agdf", "control", "templates", "artefacts", "VERIFIED_CHANGE.md"),
|
|
123
120
|
join(".agdf", "control", "templates", "artefacts", "PRD.md"),
|
|
124
121
|
join(".agdf", "control", "templates", "artefacts", "SD.md"),
|
|
125
122
|
join(".agdf", "control", "templates", "artefacts", "TP.md"),
|
|
@@ -151,6 +148,7 @@ const liveControlFiles = [
|
|
|
151
148
|
const artefactTemplateFiles = [
|
|
152
149
|
join(".agdf", "control", "templates", "artefacts", "UR.md"),
|
|
153
150
|
join(".agdf", "control", "templates", "artefacts", "BROWNFIELD_REVIEW.md"),
|
|
151
|
+
join(".agdf", "control", "templates", "artefacts", "VERIFIED_CHANGE.md"),
|
|
154
152
|
join(".agdf", "control", "templates", "artefacts", "PRD.md"),
|
|
155
153
|
join(".agdf", "control", "templates", "artefacts", "SD.md"),
|
|
156
154
|
join(".agdf", "control", "templates", "artefacts", "TP.md"),
|
|
@@ -481,6 +479,7 @@ function defaultOpenCodeConfigDir() {
|
|
|
481
479
|
|
|
482
480
|
function installOpenCodeGlobalPlugin(configDir) {
|
|
483
481
|
assertGlobalOpenCodeSurfaceWritable(configDir);
|
|
482
|
+
const previousPackage = resolveOpenCodePackage(configDir);
|
|
484
483
|
const configPath = join(configDir, "opencode.json");
|
|
485
484
|
let config = {};
|
|
486
485
|
|
|
@@ -528,15 +527,21 @@ function installOpenCodeGlobalPlugin(configDir) {
|
|
|
528
527
|
|
|
529
528
|
mkdirSync(configDir, { recursive: true });
|
|
530
529
|
try {
|
|
531
|
-
|
|
530
|
+
const packageSpecifier = `${pluginDefinition.opencode.npmPackage}@${pluginDefinition.version}`;
|
|
531
|
+
execFileSync(npmCommand, [...npmPrefixArgs, "install", "--silent", "--save-prod", "--save-exact", packageSpecifier], {
|
|
532
|
+
cwd: configDir,
|
|
533
|
+
stdio: "pipe",
|
|
534
|
+
});
|
|
532
535
|
} catch (error) {
|
|
533
536
|
throw new Error(`Failed to install ${pluginDefinition.opencode.npmPackage} into the OpenCode config directory: ${(error.stderr || error.message).toString().trim()}`);
|
|
534
537
|
}
|
|
535
538
|
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
539
|
+
const installedPackage = resolveOpenCodePackage(configDir);
|
|
536
540
|
|
|
537
541
|
return {
|
|
538
542
|
configPath,
|
|
539
543
|
added: !alreadyInstalled,
|
|
544
|
+
transition: openCodePackageTransition(previousPackage, installedPackage),
|
|
540
545
|
};
|
|
541
546
|
}
|
|
542
547
|
|
|
@@ -661,24 +666,64 @@ function isOpenCodeRepositorySurfacePresent(targetDir) {
|
|
|
661
666
|
function resolveOpenCodePackage(configDir) {
|
|
662
667
|
const packageName = pluginDefinition.opencode.npmPackage;
|
|
663
668
|
try {
|
|
669
|
+
const resolvedPath = execFileSync(process.execPath, ["-e", `process.stdout.write(require.resolve(${JSON.stringify(packageName)}))`], {
|
|
670
|
+
cwd: configDir,
|
|
671
|
+
encoding: "utf8",
|
|
672
|
+
stdio: "pipe",
|
|
673
|
+
});
|
|
674
|
+
let installedVersion = "";
|
|
675
|
+
try {
|
|
676
|
+
const packageManifest = JSON.parse(readFileSync(join(dirname(resolvedPath), "package.json"), "utf8"));
|
|
677
|
+
installedVersion = typeof packageManifest.version === "string" && packageManifest.version.trim() ? packageManifest.version.trim() : "";
|
|
678
|
+
} catch {
|
|
679
|
+
installedVersion = "";
|
|
680
|
+
}
|
|
664
681
|
return {
|
|
665
682
|
loadable: true,
|
|
666
|
-
path:
|
|
667
|
-
|
|
668
|
-
encoding: "utf8",
|
|
669
|
-
stdio: "pipe",
|
|
670
|
-
}),
|
|
683
|
+
path: resolvedPath,
|
|
684
|
+
installed_version: installedVersion,
|
|
671
685
|
error: "",
|
|
672
686
|
};
|
|
673
687
|
} catch (error) {
|
|
674
688
|
return {
|
|
675
689
|
loadable: false,
|
|
676
690
|
path: "",
|
|
691
|
+
installed_version: "",
|
|
677
692
|
error: (error.stderr || error.message || "package not resolvable").toString().trim(),
|
|
678
693
|
};
|
|
679
694
|
}
|
|
680
695
|
}
|
|
681
696
|
|
|
697
|
+
function openCodePackageVersionStatus(packageState) {
|
|
698
|
+
if (!packageState.loadable) return "unloadable";
|
|
699
|
+
if (!packageState.installed_version) return "unknown";
|
|
700
|
+
return packageState.installed_version === pluginDefinition.version ? "current" : "outdated";
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function openCodePackageTransition(previousPackage, installedPackage) {
|
|
704
|
+
const previousVersion = previousPackage.loadable ? previousPackage.installed_version : "";
|
|
705
|
+
const installedVersion = installedPackage.loadable ? installedPackage.installed_version : "";
|
|
706
|
+
if (!installedVersion || (previousPackage.loadable && !previousVersion)) {
|
|
707
|
+
return {
|
|
708
|
+
previous_version: previousVersion,
|
|
709
|
+
installed_version: installedVersion,
|
|
710
|
+
status: "unknown",
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (!previousPackage.loadable) {
|
|
714
|
+
return {
|
|
715
|
+
previous_version: "",
|
|
716
|
+
installed_version: installedVersion,
|
|
717
|
+
status: "installed",
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
previous_version: previousVersion,
|
|
722
|
+
installed_version: installedVersion,
|
|
723
|
+
status: previousVersion === installedVersion ? "unchanged" : "updated",
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
682
727
|
function evaluateGlobalOpenCodeSurface(configDir) {
|
|
683
728
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
684
729
|
const skillCount = globalOpenCodeSkillNames.filter((skillName) => existsSync(join(paths.skills, skillName, "SKILL.md"))).length;
|
|
@@ -693,12 +738,13 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
693
738
|
};
|
|
694
739
|
}
|
|
695
740
|
|
|
696
|
-
function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir()) {
|
|
741
|
+
function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(), transition = null) {
|
|
697
742
|
const configPath = join(configDir, "opencode.json");
|
|
698
743
|
const configState = readOpenCodeConfig(configPath);
|
|
699
744
|
const plugins = Array.isArray(configState.config.plugin) ? configState.config.plugin : [];
|
|
700
745
|
const globalConfigured = plugins.includes(pluginDefinition.opencode.npmPackage);
|
|
701
746
|
const packageState = resolveOpenCodePackage(configDir);
|
|
747
|
+
const packageVersionStatus = openCodePackageVersionStatus(packageState);
|
|
702
748
|
const globalNativeSurface = evaluateGlobalOpenCodeSurface(configDir);
|
|
703
749
|
const sessionSignals = {
|
|
704
750
|
active: process.env.AGDF_PLUGIN_ACTIVE === "1",
|
|
@@ -714,6 +760,8 @@ function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(
|
|
|
714
760
|
if (configState.parseError) findings.push(`OpenCode global config is not valid JSON: ${configState.parseError}`);
|
|
715
761
|
if (!globalConfigured) findings.push(`OpenCode global config does not include ${pluginDefinition.opencode.npmPackage}.`);
|
|
716
762
|
if (!packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is not loadable from the OpenCode config directory.`);
|
|
763
|
+
if (packageVersionStatus === "outdated") findings.push(`${pluginDefinition.opencode.npmPackage} version ${packageState.installed_version} is outdated; expected ${pluginDefinition.version}.`);
|
|
764
|
+
if (packageVersionStatus === "unknown" && packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is loadable but its installed version is unknown.`);
|
|
717
765
|
if (!globalNativeSurface.complete) findings.push(`Global OpenCode native skill surface is incomplete (${globalNativeSurface.skill_count}/${globalNativeSurface.expected_skill_count} skills).`);
|
|
718
766
|
if (!sessionSignals.active) findings.push("No active AGDF OpenCode session signal is visible in this process.");
|
|
719
767
|
if (!repositorySurface) findings.push("Current repository does not contain the AGDF OpenCode surface.");
|
|
@@ -731,6 +779,10 @@ function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(
|
|
|
731
779
|
name: pluginDefinition.opencode.npmPackage,
|
|
732
780
|
loadable: packageState.loadable,
|
|
733
781
|
resolved_path: packageState.path,
|
|
782
|
+
installed_version: packageState.installed_version || null,
|
|
783
|
+
expected_version: pluginDefinition.version,
|
|
784
|
+
version_status: packageVersionStatus,
|
|
785
|
+
...(transition ? { transition } : {}),
|
|
734
786
|
error: packageState.error,
|
|
735
787
|
},
|
|
736
788
|
global_native_surface: globalNativeSurface,
|
|
@@ -744,7 +796,9 @@ function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(
|
|
|
744
796
|
},
|
|
745
797
|
visible_entrypoint: repositorySurface ? `${pluginDefinition.opencode.skillPrefix}gate-check (native skill)` : "none until opencode-repo is installed for this repository",
|
|
746
798
|
findings,
|
|
747
|
-
next_step:
|
|
799
|
+
next_step: packageVersionStatus !== "current"
|
|
800
|
+
? "Run npx --yes @agdf/cli@latest opencode to install or repair the OpenCode package version."
|
|
801
|
+
: !globalNativeSurface.complete
|
|
748
802
|
? "Run npx --yes @agdf/cli@latest opencode to install or repair the global native OpenCode skill surface."
|
|
749
803
|
: repositorySurface
|
|
750
804
|
? `Restart OpenCode if needed, then load ${pluginDefinition.opencode.skillPrefix}gate-check through the native skill tool for new build/change intent.`
|
|
@@ -764,6 +818,18 @@ function printOpenCodeStatus(report, json) {
|
|
|
764
818
|
if (report.global_config.parse_error) console.log(`Config parse error: ${report.global_config.parse_error}`);
|
|
765
819
|
console.log(`Package loadable: ${report.package.loadable ? "yes" : "no"}`);
|
|
766
820
|
if (report.package.resolved_path) console.log(`Package path: ${report.package.resolved_path}`);
|
|
821
|
+
console.log(`Package version: ${report.package.installed_version || "unknown"}`);
|
|
822
|
+
console.log(`Expected version: ${report.package.expected_version}`);
|
|
823
|
+
console.log(`Version status: ${report.package.version_status}`);
|
|
824
|
+
if (report.package.transition?.status === "updated") {
|
|
825
|
+
console.log(`Version transition: ${report.package.transition.previous_version} -> ${report.package.transition.installed_version}`);
|
|
826
|
+
} else if (report.package.transition?.status === "installed") {
|
|
827
|
+
console.log(`Version transition: new install (${report.package.transition.installed_version || "unknown"})`);
|
|
828
|
+
} else if (report.package.transition?.status === "unchanged") {
|
|
829
|
+
console.log(`Version transition: unchanged (${report.package.transition.installed_version})`);
|
|
830
|
+
} else if (report.package.transition?.status === "unknown") {
|
|
831
|
+
console.log("Version transition: unknown");
|
|
832
|
+
}
|
|
767
833
|
console.log(`Global native skills: ${report.global_native_surface.complete ? "complete" : "incomplete"} (${report.global_native_surface.skill_count}/${report.global_native_surface.expected_skill_count})`);
|
|
768
834
|
console.log(`Global skill path: ${report.global_native_surface.path}`);
|
|
769
835
|
console.log(`Session active signal: ${report.session.active ? "yes" : "no"}`);
|
|
@@ -1647,6 +1713,12 @@ function evaluateDoctor(targetDir, selection = {}) {
|
|
|
1647
1713
|
}
|
|
1648
1714
|
|
|
1649
1715
|
const runState = selectedRunState;
|
|
1716
|
+
if (modeSliceDecision(runState) === "verified_change") {
|
|
1717
|
+
const verifiedChange = evaluateVerifiedChange(targetDir, runState);
|
|
1718
|
+
for (const finding of verifiedChange.findings) {
|
|
1719
|
+
addFinding(findings, finding.severity, finding.code, finding.message, finding.path, finding.next_step);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1650
1722
|
for (const finding of analyzeDurableGateArtefactConsistency(runState)) {
|
|
1651
1723
|
addFinding(findings, finding.severity, finding.code, finding.message, finding.path, finding.next_step);
|
|
1652
1724
|
}
|
|
@@ -1698,6 +1770,115 @@ function extractField(content, field) {
|
|
|
1698
1770
|
return content.match(pattern)?.[1]?.trim() ?? "";
|
|
1699
1771
|
}
|
|
1700
1772
|
|
|
1773
|
+
const verifiedChangeStatuses = new Set(["draft", "eligible", "executed", "escalated"]);
|
|
1774
|
+
const verifiedChangeEscalationTargets = new Set(["structured_slice", "structured_delivery"]);
|
|
1775
|
+
|
|
1776
|
+
function parseVerifiedChangePathList(value) {
|
|
1777
|
+
const cleaned = cleanStatusCell(value ?? "");
|
|
1778
|
+
if (!cleaned || cleaned === "none") return [];
|
|
1779
|
+
return cleaned.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function isSafeRepoRelativePath(value) {
|
|
1783
|
+
if (!value || value === "none" || value.startsWith("/") || value.includes("\\")) return false;
|
|
1784
|
+
const normalized = posix.normalize(value);
|
|
1785
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith("../") && normalized === value;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function gitPathList(targetDir, args) {
|
|
1789
|
+
try {
|
|
1790
|
+
return execFileSync("git", args, { cwd: targetDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })
|
|
1791
|
+
.split("\n")
|
|
1792
|
+
.map((item) => item.trim())
|
|
1793
|
+
.filter(Boolean);
|
|
1794
|
+
} catch {
|
|
1795
|
+
return null;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function readVerifiedChangeRecord(targetDir, runState) {
|
|
1800
|
+
const artefact = runState.artefacts.get("Verified Change");
|
|
1801
|
+
if (!artefact?.path || isPlaceholderValue(artefact.path)) return { status: "missing", path: "", content: "" };
|
|
1802
|
+
if (!isSafeRepoRelativePath(artefact.path)) return { status: "invalid", path: artefact.path, content: "", error: "record_path_invalid" };
|
|
1803
|
+
const absolutePath = resolve(targetDir, artefact.path);
|
|
1804
|
+
const relativePath = relative(resolve(targetDir), absolutePath);
|
|
1805
|
+
if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || !existsSync(absolutePath)) {
|
|
1806
|
+
return { status: "missing", path: artefact.path, content: "", error: "record_missing" };
|
|
1807
|
+
}
|
|
1808
|
+
return { status: "present", path: artefact.path, content: readFileSync(absolutePath, "utf8") };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function evaluateVerifiedChange(targetDir, runState) {
|
|
1812
|
+
const record = readVerifiedChangeRecord(targetDir, runState);
|
|
1813
|
+
const findings = [];
|
|
1814
|
+
const add = (code, message, nextStep, severity = "revise") => findings.push({
|
|
1815
|
+
severity,
|
|
1816
|
+
code,
|
|
1817
|
+
message,
|
|
1818
|
+
path: record.path || runState.path,
|
|
1819
|
+
next_step: nextStep,
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
if (record.status === "missing") {
|
|
1823
|
+
return { status: "missing", record, findings };
|
|
1824
|
+
}
|
|
1825
|
+
if (record.status === "invalid") {
|
|
1826
|
+
add("AGDF_VERIFIED_CHANGE_RECORD_PATH_INVALID", "Verified Change record path must be a repository-relative path inside the target repository.", "Link a stable repository-relative VERIFIED_CHANGE.md artefact from the selected run state.", "block");
|
|
1827
|
+
return { status: "invalid", record, findings };
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const status = cleanStatusCell(extractField(record.content, "status"));
|
|
1831
|
+
const relatedUr = cleanStatusCell(extractField(record.content, "related_ur"));
|
|
1832
|
+
const escalationTarget = cleanStatusCell(extractField(record.content, "escalation_target"));
|
|
1833
|
+
const canonicalOwner = cleanStatusCell(extractField(record.content, "canonical_owner"));
|
|
1834
|
+
const sourcePaths = parseVerifiedChangePathList(extractField(record.content, "allowed_source_paths"));
|
|
1835
|
+
const derivedPaths = parseVerifiedChangePathList(extractField(record.content, "allowed_derived_paths"));
|
|
1836
|
+
const prohibitedImpacts = cleanStatusCell(extractField(record.content, "prohibited_impacts"));
|
|
1837
|
+
const propagationCommand = extractField(record.content, "propagation_command");
|
|
1838
|
+
const validationCommands = extractField(record.content, "validation_commands");
|
|
1839
|
+
const baselineTracked = parseVerifiedChangePathList(extractField(record.content, "baseline_tracked_paths"));
|
|
1840
|
+
const baselineUntracked = parseVerifiedChangePathList(extractField(record.content, "baseline_untracked_paths"));
|
|
1841
|
+
const validationStatus = cleanStatusCell(extractField(record.content, "validation_status"));
|
|
1842
|
+
const propagationStatus = cleanStatusCell(extractField(record.content, "propagation_status"));
|
|
1843
|
+
const urArtefact = runState.artefacts.get("UR");
|
|
1844
|
+
|
|
1845
|
+
if (!verifiedChangeStatuses.has(status)) add("AGDF_VERIFIED_CHANGE_STATUS_INVALID", "Verified Change record must use status draft, eligible, executed or escalated.", "Set a supported record status or escalate to the declared structured target.");
|
|
1846
|
+
if (!isSafeRepoRelativePath(relatedUr) || relatedUr !== urArtefact?.path) add("AGDF_VERIFIED_CHANGE_RELATED_UR_INVALID", "Verified Change record must link exactly to the selected run's repository-relative UR artefact.", "Set related_ur to the selected run's durable UR artefact path.");
|
|
1847
|
+
if (!verifiedChangeEscalationTargets.has(escalationTarget)) add("AGDF_VERIFIED_CHANGE_ESCALATION_INVALID", "Verified Change record must declare structured_slice or structured_delivery as its escalation target.", "Record the Brownfield-selected structured escalation target.");
|
|
1848
|
+
if (!canonicalOwner || canonicalOwner.includes(",") || !isSafeRepoRelativePath(canonicalOwner)) add("AGDF_VERIFIED_CHANGE_OWNER_INVALID", "Verified Change requires exactly one repository-relative canonical_owner.", "Name one canonical owner path; otherwise escalate.");
|
|
1849
|
+
if (sourcePaths.length === 0 || sourcePaths.some((path) => !isSafeRepoRelativePath(path))) add("AGDF_VERIFIED_CHANGE_SOURCE_PATHS_INVALID", "Verified Change requires non-empty normalized allowed_source_paths.", "Declare a bounded comma-separated source path list; otherwise escalate.");
|
|
1850
|
+
if (derivedPaths.some((path) => !isSafeRepoRelativePath(path))) add("AGDF_VERIFIED_CHANGE_DERIVED_PATHS_INVALID", "Verified Change derived paths must be normalized repository-relative paths or none.", "Correct the derived path list or escalate.");
|
|
1851
|
+
if (prohibitedImpacts !== "none") add("AGDF_VERIFIED_CHANGE_IMPACTS_INVALID", "Verified Change must declare prohibited_impacts: none after checking gates, permissions, security, persistence, architecture, external API, CLI and release behavior.", "Use the structured path when any prohibited impact applies.");
|
|
1852
|
+
if (!validationCommands || validationCommands === "none") add("AGDF_VERIFIED_CHANGE_VALIDATION_MISSING", "Verified Change requires at least one deterministic validation command.", "Record a deterministic acceptance or consistency check, or escalate.");
|
|
1853
|
+
if (derivedPaths.length > 0 && (!propagationCommand || propagationCommand === "none")) add("AGDF_VERIFIED_CHANGE_PROPAGATION_MISSING", "Derived paths require a deterministic propagation command.", "Record the propagation command or escalate.");
|
|
1854
|
+
if (!extractField(record.content, "baseline_tracked_paths") || !extractField(record.content, "baseline_untracked_paths")) add("AGDF_VERIFIED_CHANGE_BASELINE_MISSING", "Verified Change requires both tracked and untracked baseline path fields, using none when empty.", "Capture the worktree baseline before marking the record eligible.");
|
|
1855
|
+
|
|
1856
|
+
const allowedPaths = new Set([...sourcePaths, ...derivedPaths]);
|
|
1857
|
+
const baselinePaths = new Set([...baselineTracked, ...baselineUntracked]);
|
|
1858
|
+
const dirtyCandidate = [...allowedPaths].find((path) => baselinePaths.has(path));
|
|
1859
|
+
if (dirtyCandidate) add("AGDF_VERIFIED_CHANGE_BASELINE_CANDIDATE_DIRTY", `Declared candidate path is already dirty at baseline: ${dirtyCandidate}.`, "Escalate or start from a clean candidate path; do not adopt pre-existing edits.", "block");
|
|
1860
|
+
|
|
1861
|
+
const currentTracked = gitPathList(targetDir, ["diff", "HEAD", "--name-only"]);
|
|
1862
|
+
const currentUntracked = gitPathList(targetDir, ["ls-files", "--others", "--exclude-standard"]);
|
|
1863
|
+
if (currentTracked === null || currentUntracked === null) {
|
|
1864
|
+
add("AGDF_VERIFIED_CHANGE_GIT_BASELINE_UNAVAILABLE", "Verified Change requires a readable Git worktree for scoped baseline validation.", "Use the structured path or restore Git worktree access.", "block");
|
|
1865
|
+
} else {
|
|
1866
|
+
const currentPaths = new Set([...currentTracked, ...currentUntracked]);
|
|
1867
|
+
const permittedControlPaths = new Set([record.path, runState.path, ".agdf/control/MASTER_BACKLOG.md"]);
|
|
1868
|
+
const unexpected = [...currentPaths].filter((path) => !baselinePaths.has(path) && !allowedPaths.has(path) && !permittedControlPaths.has(path));
|
|
1869
|
+
if (unexpected.length > 0) add("AGDF_VERIFIED_CHANGE_SCOPE_ESCAPE", `Verified Change introduced unlisted path(s): ${unexpected.join(", ")}.`, "Mark the record escalated and continue at the declared structured target.", "block");
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (status === "executed") {
|
|
1873
|
+
if (validationStatus !== "pass") add("AGDF_VERIFIED_CHANGE_VALIDATION_EVIDENCE_MISSING", "Executed Verified Change requires validation_status: pass.", "Record passing deterministic validation evidence or escalate.");
|
|
1874
|
+
if (derivedPaths.length > 0 && propagationStatus !== "pass") add("AGDF_VERIFIED_CHANGE_PROPAGATION_EVIDENCE_MISSING", "Executed Verified Change with derived paths requires propagation_status: pass.", "Record successful propagation evidence or escalate.");
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
if (status === "escalated") return { status: "escalated", record, escalation_target: escalationTarget, findings };
|
|
1878
|
+
if (findings.length > 0) return { status: "invalid", record, escalation_target: escalationTarget, findings };
|
|
1879
|
+
return { status, record, escalation_target: escalationTarget, findings };
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1701
1882
|
function cleanStatusCell(value) {
|
|
1702
1883
|
return value.replace(/^`|`$/g, "").trim();
|
|
1703
1884
|
}
|
|
@@ -1753,20 +1934,6 @@ function readRunState(targetDir, selection = {}) {
|
|
|
1753
1934
|
};
|
|
1754
1935
|
}
|
|
1755
1936
|
|
|
1756
|
-
function firstUnapprovedGate(approvals) {
|
|
1757
|
-
for (const gate of userGateOrder) {
|
|
1758
|
-
const status = approvals.get(gate)?.status ?? "";
|
|
1759
|
-
if (status !== "approved" && status !== "not_applicable") return gate;
|
|
1760
|
-
}
|
|
1761
|
-
return "OR";
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
function normalizeCurrentGate(value, fallbackGate) {
|
|
1765
|
-
if (isPlaceholderValue(value)) return fallbackGate;
|
|
1766
|
-
const normalized = value.replace(/`/g, "").trim();
|
|
1767
|
-
return normalized || fallbackGate;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
1937
|
function gateApprovalStatus(runState, gate) {
|
|
1771
1938
|
if (!userGateOrder.includes(gate)) return "not_applicable";
|
|
1772
1939
|
return runState.approvals.get(gate)?.status ?? "";
|
|
@@ -1820,7 +1987,9 @@ function analyzeDurableGateArtefactConsistency(runState) {
|
|
|
1820
1987
|
function isInternalStepSatisfied(runState, step) {
|
|
1821
1988
|
const artefact = runState.artefacts.get(step);
|
|
1822
1989
|
if (!artefact) return false;
|
|
1823
|
-
|
|
1990
|
+
if (artefact.status === "done") return true;
|
|
1991
|
+
return artefact.status === "not_applicable"
|
|
1992
|
+
&& (step === "Brownfield Review" || step === "Brownfield Analysis");
|
|
1824
1993
|
}
|
|
1825
1994
|
|
|
1826
1995
|
function modeSliceDecision(runState) {
|
|
@@ -2039,15 +2208,6 @@ function buildStatusCard({
|
|
|
2039
2208
|
};
|
|
2040
2209
|
}
|
|
2041
2210
|
|
|
2042
|
-
function effectiveCurrentGate(runState, transitionDecision) {
|
|
2043
|
-
if (transitionDecision.status !== "open") return transitionDecision.current_gate;
|
|
2044
|
-
const explicitGate = normalizeCurrentGate(runState.current_gate, "");
|
|
2045
|
-
const explicitIndex = gateProgressOrder.indexOf(explicitGate);
|
|
2046
|
-
const fallbackIndex = gateProgressOrder.indexOf(transitionDecision.current_gate);
|
|
2047
|
-
if (explicitIndex === -1 || fallbackIndex === -1) return transitionDecision.current_gate;
|
|
2048
|
-
return explicitIndex > fallbackIndex ? explicitGate : transitionDecision.current_gate;
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
2211
|
function durableArtefactBlock(gate, nextGate) {
|
|
2052
2212
|
const label = gate === "QA" ? "QA report" : `${gate} artefact`;
|
|
2053
2213
|
const stablePath = gate === "QA" ? ".agdf/control/artefacts/<key>/QA_REPORT.md" : `.agdf/control/artefacts/<key>/${gate}.md`;
|
|
@@ -2076,7 +2236,7 @@ function isGateSatisfied(runState, gate) {
|
|
|
2076
2236
|
return isDurableGateArtefactSatisfied(runState, gate);
|
|
2077
2237
|
}
|
|
2078
2238
|
|
|
2079
|
-
function transitionDecisionForRunState(runState) {
|
|
2239
|
+
function transitionDecisionForRunState(runState, verifiedChange = null) {
|
|
2080
2240
|
if (gateApprovalStatus(runState, "UR") === "approved" && !isGateSatisfied(runState, "UR")) return durableArtefactBlock("UR", "PRD");
|
|
2081
2241
|
|
|
2082
2242
|
if (!isGateSatisfied(runState, "UR")) {
|
|
@@ -2118,7 +2278,7 @@ function transitionDecisionForRunState(runState) {
|
|
|
2118
2278
|
blocking_reason: "none",
|
|
2119
2279
|
missing_approval: "none",
|
|
2120
2280
|
allowed: [
|
|
2121
|
-
"decide whether the approved UR is quick_task, structured_slice, structured_delivery or block",
|
|
2281
|
+
"decide whether the approved UR is quick_task, verified_change, structured_slice, structured_delivery or block",
|
|
2122
2282
|
"record scope reason, evidence and required next gate depth in the selected RUN_STATE.md",
|
|
2123
2283
|
"choose the next required gate depth before drafting PRD or implementing",
|
|
2124
2284
|
],
|
|
@@ -2151,6 +2311,53 @@ function transitionDecisionForRunState(runState) {
|
|
|
2151
2311
|
};
|
|
2152
2312
|
}
|
|
2153
2313
|
|
|
2314
|
+
if (modeDecision === "verified_change") {
|
|
2315
|
+
const state = verifiedChange?.status ?? "missing";
|
|
2316
|
+
const escalationTarget = verifiedChange?.escalation_target ?? "structured_slice";
|
|
2317
|
+
if (state === "executed") {
|
|
2318
|
+
return {
|
|
2319
|
+
status: "open",
|
|
2320
|
+
current_gate: "OR",
|
|
2321
|
+
blocking_reason: "none",
|
|
2322
|
+
missing_approval: "none",
|
|
2323
|
+
allowed: ["use the Verified Change mini-closeout", "offer delivery closeout when requested"],
|
|
2324
|
+
forbidden: ["create PRD, SD, TP, QA or UAT by ritual", "commit, push, open PR or release automatically"],
|
|
2325
|
+
next_allowed_action: "Close the executed Verified Change with its compact record and offer delivery closeout; do not perform VCS actions automatically.",
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
if (state === "escalated" || (state === "invalid" && verifiedChangeEscalationTargets.has(escalationTarget))) {
|
|
2329
|
+
return {
|
|
2330
|
+
status: "open",
|
|
2331
|
+
current_gate: "PRD",
|
|
2332
|
+
blocking_reason: state === "escalated" ? "verified_change_escalated" : "verified_change_invalid_escalated",
|
|
2333
|
+
missing_approval: "Approval: PRD",
|
|
2334
|
+
allowed: ["draft the structured PRD required by the declared escalation target", "retain the Verified Change escalation evidence"],
|
|
2335
|
+
forbidden: ["implement through Verified Change", "create SD or TP before PRD approval", "claim QA or release readiness"],
|
|
2336
|
+
next_allowed_action: `Proceed as ${escalationTarget}: draft the required PRD and request exact approval: Approval: PRD.`,
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
if (state === "eligible") {
|
|
2340
|
+
return {
|
|
2341
|
+
status: "open",
|
|
2342
|
+
current_gate: "Verified Change Execution",
|
|
2343
|
+
blocking_reason: "none",
|
|
2344
|
+
missing_approval: "none",
|
|
2345
|
+
allowed: ["implement only declared source and derived paths", "run declared propagation and validation commands", "record execution evidence and mini-closeout"],
|
|
2346
|
+
forbidden: ["touch unlisted paths", "add prohibited impacts", "claim QA, UAT or release readiness"],
|
|
2347
|
+
next_allowed_action: "Implement only the eligible Verified Change record scope, then record passing propagation and validation evidence.",
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
return {
|
|
2351
|
+
status: state === "missing" || state === "draft" ? "open" : "blocked",
|
|
2352
|
+
current_gate: "Verified Change Execution",
|
|
2353
|
+
blocking_reason: state === "invalid" ? "verified_change_invalid" : "verified_change_record_required",
|
|
2354
|
+
missing_approval: "none",
|
|
2355
|
+
allowed: ["create or refine the compact Verified Change record", "capture baseline paths", "prove fail-closed eligibility with doctor and gate-check"],
|
|
2356
|
+
forbidden: ["implement candidate changes", "claim validation, QA, UAT or release readiness"],
|
|
2357
|
+
next_allowed_action: "Create or repair VERIFIED_CHANGE.md, capture the baseline and satisfy every eligibility check before implementation; escalate when any condition cannot be proven.",
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2154
2361
|
return {
|
|
2155
2362
|
status: "open",
|
|
2156
2363
|
current_gate: "PRD",
|
|
@@ -2192,6 +2399,43 @@ function transitionDecisionForRunState(runState) {
|
|
|
2192
2399
|
};
|
|
2193
2400
|
}
|
|
2194
2401
|
|
|
2402
|
+
const implementationStepsRequired = gateApprovalStatus(runState, "TP") === "approved";
|
|
2403
|
+
if (implementationStepsRequired && !isInternalStepSatisfied(runState, "Brownfield Analysis")) {
|
|
2404
|
+
return {
|
|
2405
|
+
status: "open",
|
|
2406
|
+
current_gate: "Brownfield Analysis",
|
|
2407
|
+
blocking_reason: "none",
|
|
2408
|
+
missing_approval: "none",
|
|
2409
|
+
allowed: ["run Brownfield Analysis for the approved TP scope", "verify existing owners, reuse paths and regression risks"],
|
|
2410
|
+
forbidden: ["implement before Brownfield evidence supports the approved TP path", "claim QA or release readiness"],
|
|
2411
|
+
next_allowed_action: "Run Brownfield Analysis for the approved TP scope before CD+Tests.",
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
if (implementationStepsRequired && !isInternalStepSatisfied(runState, "CD+Tests")) {
|
|
2416
|
+
return {
|
|
2417
|
+
status: "open",
|
|
2418
|
+
current_gate: "CD+Tests",
|
|
2419
|
+
blocking_reason: "none",
|
|
2420
|
+
missing_approval: "none",
|
|
2421
|
+
allowed: ["implement the approved TP tasks", "run the approved test plan", "record implementation and test evidence"],
|
|
2422
|
+
forbidden: ["claim QA pass", "request UAT approval", "release"],
|
|
2423
|
+
next_allowed_action: "Implement the approved TP scope, run its tests, and record CD+Tests evidence before CR.",
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
if (implementationStepsRequired && !isInternalStepSatisfied(runState, "CR")) {
|
|
2428
|
+
return {
|
|
2429
|
+
status: "open",
|
|
2430
|
+
current_gate: "CR",
|
|
2431
|
+
blocking_reason: "none",
|
|
2432
|
+
missing_approval: "none",
|
|
2433
|
+
allowed: ["run mandatory code review", "record correctness, regression, security and maintainability findings", "fix blocking review findings"],
|
|
2434
|
+
forbidden: ["claim QA pass", "request UAT approval", "release"],
|
|
2435
|
+
next_allowed_action: "Run Code Review for the implemented TP scope and resolve blocking findings before QA.",
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2195
2439
|
if (gateApprovalStatus(runState, "QA") === "approved" && !isGateSatisfied(runState, "QA")) return durableArtefactBlock("QA", "UAT");
|
|
2196
2440
|
|
|
2197
2441
|
if (isGateSatisfied(runState, "QA") && !isGateSatisfied(runState, "UAT")) {
|
|
@@ -2220,30 +2464,38 @@ function transitionDecisionForRunState(runState) {
|
|
|
2220
2464
|
|
|
2221
2465
|
return {
|
|
2222
2466
|
status: "open",
|
|
2223
|
-
current_gate: "
|
|
2467
|
+
current_gate: "QA",
|
|
2224
2468
|
blocking_reason: "none",
|
|
2225
|
-
missing_approval: "
|
|
2226
|
-
allowed: ["run
|
|
2227
|
-
forbidden: ["
|
|
2228
|
-
next_allowed_action: "Run
|
|
2469
|
+
missing_approval: "Approval: QA",
|
|
2470
|
+
allowed: ["run QA gate", "persist or refine the QA report", "request exact QA approval"],
|
|
2471
|
+
forbidden: ["request UAT approval", "release", "claim delivery readiness before QA approval and report evidence"],
|
|
2472
|
+
next_allowed_action: "Run the QA gate, persist the QA report, and request exact approval: Approval: QA",
|
|
2229
2473
|
};
|
|
2230
2474
|
}
|
|
2231
2475
|
|
|
2232
2476
|
function evaluateGateCheck(targetDir, selection = {}) {
|
|
2233
2477
|
const doctorReport = evaluateDoctor(targetDir, selection);
|
|
2234
2478
|
const runState = readRunState(targetDir, selection);
|
|
2235
|
-
const
|
|
2479
|
+
const verifiedChange = modeSliceDecision(runState) === "verified_change"
|
|
2480
|
+
? evaluateVerifiedChange(targetDir, runState)
|
|
2481
|
+
: null;
|
|
2482
|
+
const transitionDecision = transitionDecisionForRunState(runState, verifiedChange);
|
|
2236
2483
|
const deliveryMap = analyzeDeliveryMap(runState);
|
|
2237
2484
|
const doctorBlocker = doctorReport.findings.find((finding) => finding.severity === "block");
|
|
2238
2485
|
const doctorRevise = doctorReport.findings.find((finding) => finding.severity === "revise");
|
|
2486
|
+
const routesInvalidVerifiedChange = modeSliceDecision(runState) === "verified_change"
|
|
2487
|
+
&& verifiedChange?.status === "invalid"
|
|
2488
|
+
&& verifiedChangeEscalationTargets.has(verifiedChange.escalation_target);
|
|
2239
2489
|
|
|
2240
2490
|
let status = transitionDecision.status;
|
|
2241
|
-
let currentGate =
|
|
2491
|
+
let currentGate = transitionDecision.current_gate;
|
|
2242
2492
|
let blockingReason = transitionDecision.blocking_reason;
|
|
2243
2493
|
let missingApproval = transitionDecision.missing_approval;
|
|
2244
2494
|
let allowed = transitionDecision.allowed;
|
|
2245
2495
|
let forbidden = transitionDecision.forbidden;
|
|
2246
|
-
let nextAllowedAction =
|
|
2496
|
+
let nextAllowedAction = modeSliceDecision(runState) === "verified_change"
|
|
2497
|
+
? transitionDecision.next_allowed_action
|
|
2498
|
+
: isPlaceholderValue(runState.next_allowed_action)
|
|
2247
2499
|
? transitionDecision.next_allowed_action
|
|
2248
2500
|
: runState.next_allowed_action;
|
|
2249
2501
|
|
|
@@ -2259,13 +2511,13 @@ function evaluateGateCheck(targetDir, selection = {}) {
|
|
|
2259
2511
|
];
|
|
2260
2512
|
forbidden = ["create PRD", "create SD", "create TP", "run Brownfield Analysis", "implement code", "claim QA or release readiness"];
|
|
2261
2513
|
nextAllowedAction = "Draft the minimal UR for the request in the response, then ask for exact approval: Approval: UR. Do not write a full .agdf/control scaffold unless durable control state or deterministic setup is explicitly needed.";
|
|
2262
|
-
} else if (doctorBlocker) {
|
|
2514
|
+
} else if (doctorBlocker && !routesInvalidVerifiedChange) {
|
|
2263
2515
|
status = "blocked";
|
|
2264
2516
|
blockingReason = doctorBlocker.code;
|
|
2265
2517
|
allowed = ["repair the AGDF control scaffold", ...transitionDecision.allowed, "run doctor again"];
|
|
2266
2518
|
forbidden = ["create later-gate artefacts beyond the current allowed gate", "implement gated work", "claim QA or release readiness"];
|
|
2267
2519
|
nextAllowedAction = doctorBlocker.next_step;
|
|
2268
|
-
} else if (doctorRevise) {
|
|
2520
|
+
} else if (doctorRevise && !routesInvalidVerifiedChange) {
|
|
2269
2521
|
status = "blocked";
|
|
2270
2522
|
blockingReason = doctorRevise.code;
|
|
2271
2523
|
allowed = [...new Set(["complete the current control-state fields", ...transitionDecision.allowed, "run doctor again"])];
|
|
@@ -2311,6 +2563,7 @@ function evaluateGateCheck(targetDir, selection = {}) {
|
|
|
2311
2563
|
memory: deliveryMap.memory,
|
|
2312
2564
|
findings: deliveryMap.findings,
|
|
2313
2565
|
},
|
|
2566
|
+
verified_change: verifiedChange,
|
|
2314
2567
|
doctor_report: doctorReport,
|
|
2315
2568
|
};
|
|
2316
2569
|
}
|
|
@@ -2413,7 +2666,7 @@ function evaluateDeliveryMap(targetDir, selection = {}) {
|
|
|
2413
2666
|
const runState = readRunState(targetDir, selection);
|
|
2414
2667
|
const map = analyzeDeliveryMap(runState);
|
|
2415
2668
|
const gateDecision = transitionDecisionForRunState(runState);
|
|
2416
|
-
const currentGate =
|
|
2669
|
+
const currentGate = gateDecision.current_gate;
|
|
2417
2670
|
|
|
2418
2671
|
const severityRank = { block: 3, revise: 2, warn: 1 };
|
|
2419
2672
|
const deliverySeverity = map.findings.reduce((max, finding) => Math.max(max, severityRank[finding.severity] ?? 0), 0);
|
|
@@ -2647,7 +2900,7 @@ async function main() {
|
|
|
2647
2900
|
const result = installOpenCodeGlobalPlugin(configDir);
|
|
2648
2901
|
installOpenCodeGlobalSurface(configDir);
|
|
2649
2902
|
console.log(`AGDF OpenCode global plugin ${result.added ? "installed" : "already present"}: ${result.configPath}`);
|
|
2650
|
-
const report = evaluateOpenCodeStatus(options.dir, configDir);
|
|
2903
|
+
const report = evaluateOpenCodeStatus(options.dir, configDir, result.transition);
|
|
2651
2904
|
printOpenCodeStatus(report, false);
|
|
2652
2905
|
console.log("Restart OpenCode so it loads the updated global plugin config.");
|
|
2653
2906
|
} catch (error) {
|
|
@@ -14,6 +14,7 @@ Use this scaffold when a target repository should keep AGDF state outside chat h
|
|
|
14
14
|
| `templates/MASTER_BACKLOG.md` | Template for the living backlog pointer: active initiatives and current UR/Brownfield/PRD/SD/TP/QA/OR artefacts |
|
|
15
15
|
| `templates/artefacts/UR.md` | Template for the durable user requirement artefact of a work item |
|
|
16
16
|
| `templates/artefacts/BROWNFIELD_REVIEW.md` | Template for the durable Brownfield Review and Mode/Slice Decision |
|
|
17
|
+
| `templates/artefacts/VERIFIED_CHANGE.md` | Compact fail-closed record for an eligible Verified Change |
|
|
17
18
|
| `templates/artefacts/PRD.md` | Template for the durable product requirements artefact |
|
|
18
19
|
| `templates/artefacts/SD.md` | Template for the durable solution design artefact |
|
|
19
20
|
| `templates/artefacts/TP.md` | Template for the durable task and test plan artefact |
|
|
@@ -84,7 +85,7 @@ npx --yes @agdf/cli@latest gate-check --json
|
|
|
84
85
|
- The selected `runs/<run_id>/RUN_STATE.md` is the current run dashboard.
|
|
85
86
|
- `config.json` stores the project preference for artefact and chat language; runtime rules stay English.
|
|
86
87
|
- `MASTER_BACKLOG.md` points to active delivery work; detailed artefacts live beside the work item.
|
|
87
|
-
- Brownfield Review records the post-UR Mode/Slice Decision before PRD depth or Quick Task execution is chosen.
|
|
88
|
+
- Brownfield Review records the post-UR Mode/Slice Decision before PRD depth, Verified Change execution or Quick Task execution is chosen.
|
|
88
89
|
- UR, PRD, SD, TP and QA decisions require durable artefacts or links to the authoritative repository source of truth before the next gate can open.
|
|
89
90
|
- OR records the run closeout and next permissible step; it does not approve later gates.
|
|
90
91
|
- `SOT_REGISTRY.md` decides which document owns which domain.
|