create-agdf 0.5.0 → 0.6.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 +5 -4
- package/bin/create-agdf.js +210 -148
- package/generated/.agdf/control/README.md +15 -3
- package/generated/.agdf/control/templates/RUN_STATE.md +48 -0
- package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +3 -3
- package/generated/.github/skills/agdf-gate-check/SKILL.md +15 -10
- package/generated/.github/skills/agdf-release-or/SKILL.md +1 -1
- package/generated/.github/skills/agdf-runtime-contract.md +18 -8
- package/generated/.opencode/agdf-runtime-contract.md +18 -8
- package/generated/.opencode/agents/agdf-brownfield-analysis.md +2 -2
- package/generated/.opencode/agents/agdf-gate-check.md +15 -10
- package/generated/.opencode/agents/agdf-release-or.md +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/README.md +15 -3
- package/generated/plugins/agdf/control/templates/RUN_STATE.md +48 -0
- package/generated/plugins/agdf/hooks/session-start.sh +2 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +18 -8
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +3 -3
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +15 -10
- package/generated/plugins/agdf/skills/release-or/SKILL.md +1 -1
- package/lib/control-state/aggregate.js +18 -0
- package/lib/control-state/index.js +6 -0
- package/lib/control-state/legacy-migration.js +94 -0
- package/lib/control-state/run-state-parser.js +196 -0
- package/lib/control-state/run-state-repository.js +68 -0
- package/lib/control-state/run-state-resolver.js +45 -0
- package/lib/control-state/run-state-writer.js +96 -0
- package/lib/delivery-path-search/state-adapter.js +94 -23
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Using OpenCode's plugin installer or global config for `create-agdf` can add the
|
|
|
92
92
|
|
|
93
93
|
The generated `.agdf/control/templates/` files are reusable starting points for durable AGDF state:
|
|
94
94
|
|
|
95
|
-
- `
|
|
95
|
+
- `runs/<run_id>/RUN_STATE.md` for each canonical current run dashboard
|
|
96
96
|
- `MASTER_BACKLOG.md` for active delivery pointers
|
|
97
97
|
- `templates/artefacts/` for durable UR, PRD, SD, TP and QA report artefact templates
|
|
98
98
|
- `SOT_REGISTRY.md` for one source of truth per domain
|
|
@@ -107,7 +107,7 @@ npm create agdf@latest -- init
|
|
|
107
107
|
|
|
108
108
|
This writes:
|
|
109
109
|
|
|
110
|
-
- `.agdf/control/
|
|
110
|
+
- `.agdf/control/runs/<run_id>/RUN_STATE.md`
|
|
111
111
|
- `.agdf/control/MASTER_BACKLOG.md`
|
|
112
112
|
- `.agdf/control/config.json`
|
|
113
113
|
- `.agdf/control/templates/artefacts/UR.md`
|
|
@@ -138,7 +138,8 @@ npx --yes create-agdf@latest doctor --json
|
|
|
138
138
|
|
|
139
139
|
The doctor reports missing live control files, missing current gate, missing next allowed action, empty evidence, empty backlog pointer, empty source-of-truth registry, duplicate active SoT rows and invalid quality contracts. It exits non-zero only for blocking control failures.
|
|
140
140
|
|
|
141
|
-
Use `gate-check` to derive the next process decision from the
|
|
141
|
+
Use `gate-check` to derive the next process decision from the selected canonical
|
|
142
|
+
`.agdf/control/runs/<run_id>/RUN_STATE.md`. Use `--run <run_id>` or `AGDF_RUN_ID` when several runs are active:
|
|
142
143
|
|
|
143
144
|
```bash
|
|
144
145
|
npm create agdf@latest -- gate-check
|
|
@@ -159,7 +160,7 @@ The runtime uses bounded best-first Delivery Path Search, not MCTS. It is read-o
|
|
|
159
160
|
|
|
160
161
|
Requirements and boundaries:
|
|
161
162
|
|
|
162
|
-
- run it only
|
|
163
|
+
- run it only with selected canonical `.agdf/control/runs/<run_id>/RUN_STATE.md` state
|
|
163
164
|
- the current control state must expose legal next actions
|
|
164
165
|
- Codex CLI must be installed and authenticated for `--surface codex`
|
|
165
166
|
- Claude Code CLI must be installed and authenticated for `--surface claude`
|
package/bin/create-agdf.js
CHANGED
|
@@ -16,6 +16,15 @@ import { claudeGenerator } from "../lib/delivery-path-search/generators/claude.j
|
|
|
16
16
|
import { fixtureGenerator } from "../lib/delivery-path-search/generators/protocol.js";
|
|
17
17
|
import { enforcementForSurface } from "../lib/delivery-path-search/surfaces/capabilities.js";
|
|
18
18
|
import { persistSearchResult } from "../lib/delivery-path-search/persistence.js";
|
|
19
|
+
import {
|
|
20
|
+
aggregate,
|
|
21
|
+
createRun,
|
|
22
|
+
resolveRuns,
|
|
23
|
+
writeLegacyProjection,
|
|
24
|
+
migrateLegacy,
|
|
25
|
+
parseControlState,
|
|
26
|
+
verifyLegacyProjection,
|
|
27
|
+
} from "../lib/control-state/index.js";
|
|
19
28
|
|
|
20
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
30
|
const packageRoot = resolve(__dirname, "..");
|
|
@@ -25,7 +34,7 @@ const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
|
|
|
25
34
|
const pluginInstallCommand = "npx --yes @agdf/cli@latest claude";
|
|
26
35
|
const npmCommand = process.platform === "win32" ? process.execPath : "npm";
|
|
27
36
|
const npmPrefixArgs = process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
28
|
-
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"]);
|
|
37
|
+
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"]);
|
|
29
38
|
const agdfFragmentPath = "AGENTS.agdf.md";
|
|
30
39
|
const openCodeConfigFragmentPath = "opencode.agdf.json";
|
|
31
40
|
const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
@@ -76,6 +85,7 @@ const codexPluginFiles = [
|
|
|
76
85
|
join("plugins", "agdf", ".codex-plugin", "plugin.json"),
|
|
77
86
|
join("plugins", "agdf", "control", "README.md"),
|
|
78
87
|
join("plugins", "agdf", "control", "templates", "AGDF_RUN.md"),
|
|
88
|
+
join("plugins", "agdf", "control", "templates", "RUN_STATE.md"),
|
|
79
89
|
join("plugins", "agdf", "control", "templates", "MASTER_BACKLOG.md"),
|
|
80
90
|
join("plugins", "agdf", "control", "templates", "SOT_REGISTRY.md"),
|
|
81
91
|
join("plugins", "agdf", "control", "templates", "CONTEXT_GRAPH.md"),
|
|
@@ -99,6 +109,7 @@ const codexPluginFiles = [
|
|
|
99
109
|
const controlFiles = [
|
|
100
110
|
join(".agdf", "control", "README.md"),
|
|
101
111
|
join(".agdf", "control", "templates", "AGDF_RUN.md"),
|
|
112
|
+
join(".agdf", "control", "templates", "RUN_STATE.md"),
|
|
102
113
|
join(".agdf", "control", "templates", "MASTER_BACKLOG.md"),
|
|
103
114
|
join(".agdf", "control", "templates", "SOT_REGISTRY.md"),
|
|
104
115
|
join(".agdf", "control", "templates", "CONTEXT_GRAPH.md"),
|
|
@@ -180,6 +191,9 @@ Preferred AGDF CLI:
|
|
|
180
191
|
npx --yes @agdf/cli@latest gate-check --json
|
|
181
192
|
npx --yes @agdf/cli@latest delivery-path-search --surface codex --json
|
|
182
193
|
npx --yes @agdf/cli@latest delivery-path-search --surface claude --json
|
|
194
|
+
npx --yes @agdf/cli@latest run-create --run <run_id>
|
|
195
|
+
npx --yes @agdf/cli@latest run-migrate [--run <run_id>]
|
|
196
|
+
npx --yes @agdf/cli@latest run-render-legacy --run <run_id>
|
|
183
197
|
|
|
184
198
|
Scaffold-compatible npm create usage:
|
|
185
199
|
npm create agdf@latest -- codex
|
|
@@ -210,6 +224,8 @@ Options:
|
|
|
210
224
|
--lang <de|en> Alias for --language
|
|
211
225
|
--json Print machine-readable command output as JSON
|
|
212
226
|
--status-card Print compact gate-check status-card output for interactive use
|
|
227
|
+
--run <run_id> Select one canonical run
|
|
228
|
+
--all-active Evaluate every active run (doctor and delivery-map only)
|
|
213
229
|
--surface <codex|claude|copilot|opencode|generic>
|
|
214
230
|
Declare the active Delivery Path Search surface
|
|
215
231
|
--fixture <path>
|
|
@@ -296,6 +312,8 @@ function parseArgs(argv) {
|
|
|
296
312
|
let maxGeneratedCandidates = 5;
|
|
297
313
|
let generationTimeoutMs = 30000;
|
|
298
314
|
let generationCostUnits = 5;
|
|
315
|
+
let runId;
|
|
316
|
+
let allActive = false;
|
|
299
317
|
|
|
300
318
|
for (let i = 0; i < args.length; i += 1) {
|
|
301
319
|
const arg = args[i];
|
|
@@ -331,6 +349,22 @@ function parseArgs(argv) {
|
|
|
331
349
|
continue;
|
|
332
350
|
}
|
|
333
351
|
|
|
352
|
+
if (arg === "--all-active") {
|
|
353
|
+
allActive = true;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (arg === "--run") {
|
|
358
|
+
const next = args[i + 1];
|
|
359
|
+
if (!next) {
|
|
360
|
+
console.error("Missing value for --run");
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
runId = next;
|
|
364
|
+
i += 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
334
368
|
if (["--surface", "--fixture", "--model", "--generator-model", "--max-generated-candidates", "--generation-timeout-ms", "--generation-cost-units"].includes(arg)) {
|
|
335
369
|
const next = args[i + 1];
|
|
336
370
|
if (!next) {
|
|
@@ -428,6 +462,8 @@ function parseArgs(argv) {
|
|
|
428
462
|
persist,
|
|
429
463
|
model,
|
|
430
464
|
generateCandidates,
|
|
465
|
+
runId,
|
|
466
|
+
allActive,
|
|
431
467
|
generatorModel,
|
|
432
468
|
maxGeneratedCandidates,
|
|
433
469
|
generationTimeoutMs,
|
|
@@ -862,7 +898,7 @@ function printNextSteps(target, destination, files, wroteAgentsFragment, wroteOp
|
|
|
862
898
|
console.log(`- AGDF language preference: artefacts=${language.artifact_language}, chat=${language.chat_language}, runtime=${language.runtime_language}.`);
|
|
863
899
|
}
|
|
864
900
|
if (target === "init") {
|
|
865
|
-
console.log("-
|
|
901
|
+
console.log("- Create or migrate a canonical run, then fill its RUN_STATE.md with the current gate, evidence and next allowed action.");
|
|
866
902
|
console.log("- Run npx --yes @agdf/cli@latest doctor to check the control state before the next agent run.");
|
|
867
903
|
console.log("- Commit the live control files once they represent the repository's current delivery state.");
|
|
868
904
|
return;
|
|
@@ -1173,6 +1209,16 @@ function hasFilledEvidenceRow(content) {
|
|
|
1173
1209
|
});
|
|
1174
1210
|
}
|
|
1175
1211
|
|
|
1212
|
+
function allowNoActiveRuns(targetDir) {
|
|
1213
|
+
const configPath = join(targetDir, ".agdf", "control", "config.json");
|
|
1214
|
+
if (!existsSync(configPath)) return false;
|
|
1215
|
+
try {
|
|
1216
|
+
return JSON.parse(readFileSync(configPath, "utf8")).allow_no_active_runs === true;
|
|
1217
|
+
} catch {
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1176
1222
|
function markdownSection(content, heading) {
|
|
1177
1223
|
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1178
1224
|
return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?:\\n## |\\n# |$)`))?.[1] ?? "";
|
|
@@ -1182,12 +1228,6 @@ function filled(value) {
|
|
|
1182
1228
|
return Boolean(value && !isPlaceholderValue(value) && value.trim() !== "");
|
|
1183
1229
|
}
|
|
1184
1230
|
|
|
1185
|
-
function nonTemplateRows(section, headerCell) {
|
|
1186
|
-
return tableRows(section)
|
|
1187
|
-
.filter((cells) => cells[0] !== headerCell)
|
|
1188
|
-
.filter((cells) => cells.some((cell) => filled(cell)));
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
1231
|
function parseQualityContracts(content) {
|
|
1192
1232
|
const parsed = JSON.parse(content);
|
|
1193
1233
|
if (!Array.isArray(parsed.contracts) || parsed.contracts.length === 0) {
|
|
@@ -1201,9 +1241,61 @@ function parseQualityContracts(content) {
|
|
|
1201
1241
|
return parsed;
|
|
1202
1242
|
}
|
|
1203
1243
|
|
|
1204
|
-
function evaluateDoctor(targetDir) {
|
|
1244
|
+
function evaluateDoctor(targetDir, selection = {}) {
|
|
1245
|
+
if (selection.allActive) {
|
|
1246
|
+
const selected = resolveRuns(targetDir, { allActive: true });
|
|
1247
|
+
const runs = selected.runs.map((run) => ({
|
|
1248
|
+
run_id: run.run_id,
|
|
1249
|
+
report: evaluateDoctor(targetDir, { runId: run.run_id }),
|
|
1250
|
+
}));
|
|
1251
|
+
const status = selected.findings.length
|
|
1252
|
+
? "block"
|
|
1253
|
+
: aggregate(
|
|
1254
|
+
runs.map((item) => ({ run_id: item.run_id, status: item.report.status })),
|
|
1255
|
+
{ allowNoActiveRuns: allowNoActiveRuns(targetDir) },
|
|
1256
|
+
).status;
|
|
1257
|
+
const findings = [
|
|
1258
|
+
...selected.findings.map((finding) => ({
|
|
1259
|
+
...finding,
|
|
1260
|
+
severity: "block",
|
|
1261
|
+
message: "Invalid canonical run entry.",
|
|
1262
|
+
next_step: "Repair or remove the invalid run entry.",
|
|
1263
|
+
})),
|
|
1264
|
+
...runs.flatMap((item) => item.report.findings.map((finding) => ({ ...finding, run_id: item.run_id }))),
|
|
1265
|
+
];
|
|
1266
|
+
return {
|
|
1267
|
+
schema_version: "1",
|
|
1268
|
+
status,
|
|
1269
|
+
checked_at: new Date().toISOString(),
|
|
1270
|
+
target_dir: targetDir,
|
|
1271
|
+
summary: {
|
|
1272
|
+
findings: findings.length,
|
|
1273
|
+
block: findings.filter((finding) => finding.severity === "block").length,
|
|
1274
|
+
revise: findings.filter((finding) => finding.severity === "revise").length,
|
|
1275
|
+
warn: findings.filter((finding) => finding.severity === "warn").length,
|
|
1276
|
+
},
|
|
1277
|
+
findings,
|
|
1278
|
+
runs,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1205
1281
|
const findings = [];
|
|
1206
|
-
const
|
|
1282
|
+
const hasCanonicalRuns = existsSync(join(targetDir, ".agdf", "control", "runs"));
|
|
1283
|
+
if (hasCanonicalRuns) {
|
|
1284
|
+
const projection = verifyLegacyProjection(targetDir);
|
|
1285
|
+
if (!["absent", "valid"].includes(projection.status)) {
|
|
1286
|
+
addFinding(
|
|
1287
|
+
findings,
|
|
1288
|
+
"block",
|
|
1289
|
+
"AGDF_LEGACY_PROJECTION_DRIFT",
|
|
1290
|
+
`Legacy compatibility state is not a valid projection: ${projection.status}.`,
|
|
1291
|
+
join(".agdf", "control", "AGDF_RUN.md"),
|
|
1292
|
+
"Regenerate the explicit legacy projection or remove it after legacy consumers are retired.",
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const missing = doctorRequiredFiles.filter(
|
|
1297
|
+
(relativePath) => !(relativePath.endsWith("AGDF_RUN.md") && hasCanonicalRuns) && !existsSync(join(targetDir, relativePath)),
|
|
1298
|
+
);
|
|
1207
1299
|
|
|
1208
1300
|
for (const relativePath of missing) {
|
|
1209
1301
|
const templatePath = join(dirname(relativePath), "templates", relativePath.split("/").at(-1));
|
|
@@ -1221,8 +1313,9 @@ function evaluateDoctor(targetDir) {
|
|
|
1221
1313
|
}
|
|
1222
1314
|
|
|
1223
1315
|
if (missing.length === 0) {
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1316
|
+
const selectedRunState = readRunState(targetDir, selection);
|
|
1317
|
+
const runPath = selectedRunState.path;
|
|
1318
|
+
const run = selectedRunState.content;
|
|
1226
1319
|
const currentGateLine = run.match(/^- current_gate:[^\S\r\n]*(.*)$/m)?.[1]?.trim() ?? "";
|
|
1227
1320
|
const nextActionLine = run.match(/^- next_allowed_action:[^\S\r\n]*(.*)$/m)?.[1]?.trim() ?? "";
|
|
1228
1321
|
const hasEvidence = hasFilledEvidenceRow(run);
|
|
@@ -1232,7 +1325,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1232
1325
|
findings,
|
|
1233
1326
|
"revise",
|
|
1234
1327
|
"AGDF_CURRENT_GATE_MISSING",
|
|
1235
|
-
"
|
|
1328
|
+
"The selected run state does not name the current gate.",
|
|
1236
1329
|
runPath,
|
|
1237
1330
|
"Set current_gate to the current delivery gate or none.",
|
|
1238
1331
|
);
|
|
@@ -1243,7 +1336,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1243
1336
|
findings,
|
|
1244
1337
|
"revise",
|
|
1245
1338
|
"AGDF_NEXT_ALLOWED_ACTION_MISSING",
|
|
1246
|
-
"
|
|
1339
|
+
"The selected run state does not state the next allowed action.",
|
|
1247
1340
|
runPath,
|
|
1248
1341
|
"Fill the next allowed action before asking an agent to continue delivery work.",
|
|
1249
1342
|
);
|
|
@@ -1254,7 +1347,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1254
1347
|
findings,
|
|
1255
1348
|
"warn",
|
|
1256
1349
|
"AGDF_EVIDENCE_EMPTY",
|
|
1257
|
-
"
|
|
1350
|
+
"The selected run state has no visible evidence row yet.",
|
|
1258
1351
|
runPath,
|
|
1259
1352
|
"Add at least one evidence row or explicitly document that no evidence exists yet.",
|
|
1260
1353
|
);
|
|
@@ -1337,7 +1430,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1337
1430
|
);
|
|
1338
1431
|
}
|
|
1339
1432
|
|
|
1340
|
-
const runState =
|
|
1433
|
+
const runState = selectedRunState;
|
|
1341
1434
|
for (const finding of analyzeDurableGateArtefactConsistency(runState)) {
|
|
1342
1435
|
addFinding(findings, finding.severity, finding.code, finding.message, finding.path, finding.next_step);
|
|
1343
1436
|
}
|
|
@@ -1389,34 +1482,22 @@ function extractField(content, field) {
|
|
|
1389
1482
|
return content.match(pattern)?.[1]?.trim() ?? "";
|
|
1390
1483
|
}
|
|
1391
1484
|
|
|
1392
|
-
function extractSectionField(section, field) {
|
|
1393
|
-
const pattern = new RegExp(`^- ${field}:[^\\S\\r\\n]*(.*)$`, "m");
|
|
1394
|
-
return section.match(pattern)?.[1]?.trim() ?? "";
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
1485
|
function cleanStatusCell(value) {
|
|
1398
1486
|
return value.replace(/^`|`$/g, "").trim();
|
|
1399
1487
|
}
|
|
1400
1488
|
|
|
1401
|
-
function
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
for (const cells of tableRows(section)) {
|
|
1409
|
-
const [gate, status, evidence] = cells;
|
|
1410
|
-
if (!userGateOrder.includes(gate)) continue;
|
|
1411
|
-
approvals.set(gate, {
|
|
1412
|
-
status: normalizeGateStatus(gate, status),
|
|
1413
|
-
evidence: evidence ?? "",
|
|
1489
|
+
function readRunState(targetDir, selection = {}) {
|
|
1490
|
+
let runPath = join(".agdf", "control", "AGDF_RUN.md");
|
|
1491
|
+
const canonicalRoot = join(targetDir, ".agdf", "control", "runs");
|
|
1492
|
+
if (existsSync(canonicalRoot)) {
|
|
1493
|
+
const selected = resolveRuns(targetDir, {
|
|
1494
|
+
runIdArg: selection.runId,
|
|
1495
|
+
runIdEnv: process.env.AGDF_RUN_ID,
|
|
1414
1496
|
});
|
|
1497
|
+
runPath = selected.run.path.startsWith(targetDir)
|
|
1498
|
+
? selected.run.path.slice(targetDir.length + 1)
|
|
1499
|
+
: selected.run.path;
|
|
1415
1500
|
}
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
function readRunState(targetDir) {
|
|
1419
|
-
const runPath = join(".agdf", "control", "AGDF_RUN.md");
|
|
1420
1501
|
if (!existsSync(join(targetDir, runPath))) {
|
|
1421
1502
|
return {
|
|
1422
1503
|
path: runPath,
|
|
@@ -1438,107 +1519,15 @@ function readRunState(targetDir) {
|
|
|
1438
1519
|
}
|
|
1439
1520
|
|
|
1440
1521
|
const content = readTargetFile(targetDir, runPath);
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const artefacts = new Map();
|
|
1446
|
-
const artefactsSection = markdownSection(content, "Artefacts");
|
|
1447
|
-
for (const cells of tableRows(artefactsSection)) {
|
|
1448
|
-
const [type, path, status, notes] = cells;
|
|
1449
|
-
if (!userGateOrder.includes(type) && !internalStepArtefacts.has(type)) continue;
|
|
1450
|
-
artefacts.set(type, {
|
|
1451
|
-
path: path ?? "",
|
|
1452
|
-
status: cleanStatusCell(status ?? ""),
|
|
1453
|
-
notes: notes ?? "",
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
const artefactChain = nonTemplateRows(markdownSection(content, "Artefact Chain"), "From")
|
|
1458
|
-
.map((cells) => ({
|
|
1459
|
-
from: cells[0] ?? "",
|
|
1460
|
-
relationship: cleanStatusCell(cells[1] ?? ""),
|
|
1461
|
-
to: cleanStatusCell(cells[2] ?? ""),
|
|
1462
|
-
evidence: cells[3] ?? "",
|
|
1463
|
-
}));
|
|
1464
|
-
|
|
1465
|
-
const evidence_refs = tableRows(markdownSection(content, "Evidence"))
|
|
1466
|
-
.filter((cells) => cells[0] !== "Evidence")
|
|
1467
|
-
.filter((cells) => filled(cells[0]) || filled(cells[1]) || filled(cells[2]))
|
|
1468
|
-
.map((cells) => ({
|
|
1469
|
-
evidence: cells[0] ?? "",
|
|
1470
|
-
source: cells[1] ?? "",
|
|
1471
|
-
covers: cells[2] ?? "",
|
|
1472
|
-
strength: cleanStatusCell(cells[3] ?? ""),
|
|
1473
|
-
}));
|
|
1474
|
-
|
|
1475
|
-
const missingEvidence = tableRows(markdownSection(content, "Missing Evidence"))
|
|
1476
|
-
.filter((cells) => cells[0] !== "Missing evidence")
|
|
1477
|
-
.filter((cells) => filled(cells[0]))
|
|
1478
|
-
.map((cells) => ({
|
|
1479
|
-
missing_evidence: cells[0] ?? "",
|
|
1480
|
-
impact: cleanStatusCell(cells[1] ?? ""),
|
|
1481
|
-
required_next_step: cells[2] ?? "",
|
|
1482
|
-
}));
|
|
1483
|
-
|
|
1484
|
-
const risks = tableRows(markdownSection(content, "Risks"))
|
|
1485
|
-
.filter((cells) => cells[0] !== "Risk")
|
|
1486
|
-
.filter((cells) => filled(cells[0]))
|
|
1487
|
-
.map((cells) => ({
|
|
1488
|
-
risk: cells[0] ?? "",
|
|
1489
|
-
impact: cleanStatusCell(cells[1] ?? ""),
|
|
1490
|
-
mitigation_or_owner: cells[2] ?? "",
|
|
1491
|
-
}));
|
|
1492
|
-
|
|
1493
|
-
const contextGraph = {
|
|
1494
|
-
impact: cleanStatusCell(extractField(content, "context_graph_impact")),
|
|
1495
|
-
refs: extractField(content, "context_graph_refs"),
|
|
1496
|
-
required_action: cleanStatusCell(extractField(content, "context_graph_required_action")),
|
|
1497
|
-
gate_effect: cleanStatusCell(extractField(content, "context_graph_gate_effect")),
|
|
1498
|
-
evidence: extractField(content, "context_graph_evidence"),
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
const modeSliceSection = markdownSection(content, "Mode / Slice Decision");
|
|
1502
|
-
const modeSliceDecision = {
|
|
1503
|
-
decision: cleanStatusCell(extractSectionField(modeSliceSection, "decision")),
|
|
1504
|
-
required_next_gate: cleanStatusCell(extractSectionField(modeSliceSection, "required_next_gate")),
|
|
1505
|
-
scope_reason: extractSectionField(modeSliceSection, "scope_reason"),
|
|
1506
|
-
evidence: extractSectionField(modeSliceSection, "evidence"),
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
const sourceScopeSection = markdownSection(content, "Source And Scope State");
|
|
1510
|
-
const sourceScope = {
|
|
1511
|
-
normative_instruction_source: extractSectionField(sourceScopeSection, "normative_instruction_source"),
|
|
1512
|
-
multi_scope_state: cleanStatusCell(extractSectionField(sourceScopeSection, "multi_scope_state")),
|
|
1513
|
-
active_scope_evidence: extractSectionField(sourceScopeSection, "active_scope_evidence"),
|
|
1514
|
-
competing_scope_lines: extractSectionField(sourceScopeSection, "competing_scope_lines"),
|
|
1515
|
-
branch_workspace_evidence: extractSectionField(sourceScopeSection, "branch_workspace_evidence"),
|
|
1516
|
-
branch_workspace_scope_effect: cleanStatusCell(extractSectionField(sourceScopeSection, "branch_workspace_scope_effect")),
|
|
1517
|
-
};
|
|
1518
|
-
|
|
1519
|
-
const memorySection = markdownSection(content, "Knowledge Persistence Decision");
|
|
1520
|
-
const memory = {
|
|
1521
|
-
target: cleanStatusCell(extractSectionField(memorySection, "memory_target")),
|
|
1522
|
-
reason: extractSectionField(memorySection, "memory_reason"),
|
|
1523
|
-
refs: extractSectionField(memorySection, "memory_refs"),
|
|
1524
|
-
};
|
|
1522
|
+
const parsed = parseControlState(content, {
|
|
1523
|
+
userGates: userGateOrder,
|
|
1524
|
+
internalSteps: [...internalStepArtefacts],
|
|
1525
|
+
});
|
|
1525
1526
|
|
|
1526
1527
|
return {
|
|
1527
1528
|
path: runPath,
|
|
1528
1529
|
content,
|
|
1529
|
-
|
|
1530
|
-
next_allowed_action: extractField(content, "next_allowed_action"),
|
|
1531
|
-
approvals,
|
|
1532
|
-
artefacts,
|
|
1533
|
-
evidence_refs,
|
|
1534
|
-
artefact_chain: artefactChain,
|
|
1535
|
-
mode_slice_decision: modeSliceDecision,
|
|
1536
|
-
missing_evidence: missingEvidence,
|
|
1537
|
-
risks,
|
|
1538
|
-
context_graph: contextGraph,
|
|
1539
|
-
quality_outlook: extractField(content, "quality_outlook"),
|
|
1540
|
-
source_scope: sourceScope,
|
|
1541
|
-
memory,
|
|
1530
|
+
...parsed,
|
|
1542
1531
|
};
|
|
1543
1532
|
}
|
|
1544
1533
|
|
|
@@ -1599,7 +1588,7 @@ function analyzeDurableGateArtefactConsistency(runState) {
|
|
|
1599
1588
|
code: "AGDF_GATE_ARTEFACT_STATUS_INCONSISTENT",
|
|
1600
1589
|
message: `${gate} approval is recorded, but the durable artefact row uses status \`${artefact.status}\`; expected ${describeDurableArtefactStatuses(gate)}.`,
|
|
1601
1590
|
path: runState.path,
|
|
1602
|
-
next_step: `Update the ${gate} artefact row in
|
|
1591
|
+
next_step: `Update the ${gate} artefact row in the selected RUN_STATE.md to use the gate-specific durable status vocabulary.`,
|
|
1603
1592
|
});
|
|
1604
1593
|
}
|
|
1605
1594
|
|
|
@@ -1847,7 +1836,7 @@ function durableArtefactBlock(gate, nextGate) {
|
|
|
1847
1836
|
missing_approval: "none",
|
|
1848
1837
|
allowed: [
|
|
1849
1838
|
`persist the approved ${label} in a stable artefact path such as ${stablePath}`,
|
|
1850
|
-
"link the artefact from
|
|
1839
|
+
"link the artefact from the selected RUN_STATE.md and MASTER_BACKLOG.md",
|
|
1851
1840
|
"run gate-check again",
|
|
1852
1841
|
],
|
|
1853
1842
|
forbidden: nextGate
|
|
@@ -1892,7 +1881,7 @@ function transitionDecisionForRunState(runState) {
|
|
|
1892
1881
|
allowed: [
|
|
1893
1882
|
"run Brownfield Review after G-00",
|
|
1894
1883
|
"identify existing workstream, owners, SoT, reuse risks and open PRD/SD questions",
|
|
1895
|
-
"mark Brownfield Review as done or not_applicable in
|
|
1884
|
+
"mark Brownfield Review as done or not_applicable in the selected RUN_STATE.md",
|
|
1896
1885
|
],
|
|
1897
1886
|
forbidden: ["create PRD before Brownfield Review is resolved", "create SD", "create TP", "implement code", "claim QA or release readiness"],
|
|
1898
1887
|
next_allowed_action: "Run Brownfield Review after G-00 before drafting PRD, or mark Brownfield Review not_applicable with evidence.",
|
|
@@ -1908,7 +1897,7 @@ function transitionDecisionForRunState(runState) {
|
|
|
1908
1897
|
missing_approval: "none",
|
|
1909
1898
|
allowed: [
|
|
1910
1899
|
"decide whether the approved UR is quick_task, structured_slice, structured_delivery or block",
|
|
1911
|
-
"record scope reason, evidence and required next gate depth in
|
|
1900
|
+
"record scope reason, evidence and required next gate depth in the selected RUN_STATE.md",
|
|
1912
1901
|
"choose the next required gate depth before drafting PRD or implementing",
|
|
1913
1902
|
],
|
|
1914
1903
|
forbidden: ["create PRD before process size is decided", "create SD", "create TP", "implement code", "claim QA or release readiness"],
|
|
@@ -2018,9 +2007,9 @@ function transitionDecisionForRunState(runState) {
|
|
|
2018
2007
|
};
|
|
2019
2008
|
}
|
|
2020
2009
|
|
|
2021
|
-
function evaluateGateCheck(targetDir) {
|
|
2022
|
-
const doctorReport = evaluateDoctor(targetDir);
|
|
2023
|
-
const runState = readRunState(targetDir);
|
|
2010
|
+
function evaluateGateCheck(targetDir, selection = {}) {
|
|
2011
|
+
const doctorReport = evaluateDoctor(targetDir, selection);
|
|
2012
|
+
const runState = readRunState(targetDir, selection);
|
|
2024
2013
|
const transitionDecision = transitionDecisionForRunState(runState);
|
|
2025
2014
|
const deliveryMap = analyzeDeliveryMap(runState);
|
|
2026
2015
|
const doctorBlocker = doctorReport.findings.find((finding) => finding.severity === "block");
|
|
@@ -2155,9 +2144,51 @@ function readBacklogPointers(targetDir) {
|
|
|
2155
2144
|
return parseBacklogSection(activeSection);
|
|
2156
2145
|
}
|
|
2157
2146
|
|
|
2158
|
-
function evaluateDeliveryMap(targetDir) {
|
|
2159
|
-
|
|
2160
|
-
|
|
2147
|
+
function evaluateDeliveryMap(targetDir, selection = {}) {
|
|
2148
|
+
if (selection.allActive) {
|
|
2149
|
+
const selected = resolveRuns(targetDir, { allActive: true });
|
|
2150
|
+
const runs = selected.runs.map((run) => ({
|
|
2151
|
+
run_id: run.run_id,
|
|
2152
|
+
report: evaluateDeliveryMap(targetDir, { runId: run.run_id }),
|
|
2153
|
+
}));
|
|
2154
|
+
const status = selected.findings.length
|
|
2155
|
+
? "block"
|
|
2156
|
+
: aggregate(
|
|
2157
|
+
runs.map((item) => ({
|
|
2158
|
+
run_id: item.run_id,
|
|
2159
|
+
status: item.report.status,
|
|
2160
|
+
})),
|
|
2161
|
+
{ allowNoActiveRuns: allowNoActiveRuns(targetDir) },
|
|
2162
|
+
).status;
|
|
2163
|
+
const findings = [
|
|
2164
|
+
...selected.findings.map((finding) => ({
|
|
2165
|
+
...finding,
|
|
2166
|
+
severity: "block",
|
|
2167
|
+
message: "Invalid canonical run entry.",
|
|
2168
|
+
next_step: "Repair or remove the invalid run entry.",
|
|
2169
|
+
})),
|
|
2170
|
+
...runs.flatMap((item) =>
|
|
2171
|
+
item.report.findings.map((finding) => ({
|
|
2172
|
+
...finding,
|
|
2173
|
+
run_id: item.run_id,
|
|
2174
|
+
})),
|
|
2175
|
+
),
|
|
2176
|
+
];
|
|
2177
|
+
return {
|
|
2178
|
+
schema_version: "1",
|
|
2179
|
+
status,
|
|
2180
|
+
current_gate: "all-active",
|
|
2181
|
+
next_allowed_action: runs.length
|
|
2182
|
+
? "Resolve per-run findings."
|
|
2183
|
+
: "Create or activate a governed run.",
|
|
2184
|
+
quality_outlook: "Keep every active run independently actionable.",
|
|
2185
|
+
relationships: runs.flatMap((item) => item.report.relationships),
|
|
2186
|
+
findings,
|
|
2187
|
+
runs,
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
const doctorReport = evaluateDoctor(targetDir, selection);
|
|
2191
|
+
const runState = readRunState(targetDir, selection);
|
|
2161
2192
|
const map = analyzeDeliveryMap(runState);
|
|
2162
2193
|
const gateDecision = transitionDecisionForRunState(runState);
|
|
2163
2194
|
const currentGate = effectiveCurrentGate(runState, gateDecision);
|
|
@@ -2238,6 +2269,7 @@ async function executeDeliveryPathSearch(options) {
|
|
|
2238
2269
|
const fixtureGeneration = fixture?.generator_response;
|
|
2239
2270
|
const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
|
|
2240
2271
|
const baseInput = fixture?.input ?? searchInputFromControl(options.dir, {
|
|
2272
|
+
scopeKey: options.runId,
|
|
2241
2273
|
enforcement,
|
|
2242
2274
|
generation: generationEnabled ? {
|
|
2243
2275
|
enabled: true,
|
|
@@ -2298,20 +2330,50 @@ async function executeDeliveryPathSearch(options) {
|
|
|
2298
2330
|
async function main() {
|
|
2299
2331
|
const options = parseArgs(process.argv.slice(2));
|
|
2300
2332
|
|
|
2333
|
+
if (
|
|
2334
|
+
options.allActive &&
|
|
2335
|
+
!["doctor", "delivery-map"].includes(options.target)
|
|
2336
|
+
) {
|
|
2337
|
+
throw new Error(
|
|
2338
|
+
"--all-active is supported only by doctor and delivery-map",
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
if (options.target === "run-create") {
|
|
2343
|
+
if (!options.runId || options.allActive)
|
|
2344
|
+
throw new Error("run-create requires --run and rejects --all-active");
|
|
2345
|
+
console.log(createRun(options.dir, options.runId));
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (options.target === "run-migrate") {
|
|
2349
|
+
console.log(
|
|
2350
|
+
JSON.stringify(migrateLegacy(options.dir, options.runId), null, 2),
|
|
2351
|
+
);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (options.target === "run-render-legacy") {
|
|
2355
|
+
if (!options.runId) throw new Error("run-render-legacy requires --run");
|
|
2356
|
+
const selected = resolveRuns(options.dir, { runIdArg: options.runId });
|
|
2357
|
+
const output = join(options.dir, ".agdf", "control", "AGDF_RUN.md");
|
|
2358
|
+
writeLegacyProjection(output, selected.run.path);
|
|
2359
|
+
console.log(output);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2301
2363
|
if (options.target === "doctor") {
|
|
2302
|
-
const report = evaluateDoctor(options.dir);
|
|
2364
|
+
const report = evaluateDoctor(options.dir, options);
|
|
2303
2365
|
printDoctorReport(report, options.json);
|
|
2304
2366
|
process.exit(report.status === "block" ? 2 : 0);
|
|
2305
2367
|
}
|
|
2306
2368
|
|
|
2307
2369
|
if (options.target === "gate-check") {
|
|
2308
|
-
const report = evaluateGateCheck(options.dir);
|
|
2370
|
+
const report = evaluateGateCheck(options.dir, options);
|
|
2309
2371
|
printGateCheckReport(report, options.json, options.statusCard);
|
|
2310
2372
|
process.exit(report.status === "blocked" ? 2 : 0);
|
|
2311
2373
|
}
|
|
2312
2374
|
|
|
2313
2375
|
if (options.target === "delivery-map") {
|
|
2314
|
-
const report = evaluateDeliveryMap(options.dir);
|
|
2376
|
+
const report = evaluateDeliveryMap(options.dir, options);
|
|
2315
2377
|
printDeliveryMapReport(report, options.json);
|
|
2316
2378
|
process.exit(report.status === "block" ? 2 : 0);
|
|
2317
2379
|
}
|
|
@@ -9,7 +9,8 @@ Use this scaffold when a target repository should keep AGDF state outside chat h
|
|
|
9
9
|
| File | Purpose |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `config.json` | Project language preference for generated AGDF artefacts and user-facing chat |
|
|
12
|
-
| `templates/
|
|
12
|
+
| `templates/RUN_STATE.md` | Canonical version-2 template for isolated per-run state |
|
|
13
|
+
| `templates/AGDF_RUN.md` | Legacy migration-input and compatibility-projection template |
|
|
13
14
|
| `templates/MASTER_BACKLOG.md` | Template for the living backlog pointer: active initiatives and current UR/Brownfield/PRD/SD/TP/QA/OR artefacts |
|
|
14
15
|
| `templates/artefacts/UR.md` | Template for the durable user requirement artefact of a work item |
|
|
15
16
|
| `templates/artefacts/BROWNFIELD_REVIEW.md` | Template for the durable Brownfield Review and Mode/Slice Decision |
|
|
@@ -27,6 +28,9 @@ Use this scaffold when a target repository should keep AGDF state outside chat h
|
|
|
27
28
|
```text
|
|
28
29
|
.agdf/
|
|
29
30
|
control/
|
|
31
|
+
runs/
|
|
32
|
+
<run_id>/
|
|
33
|
+
RUN_STATE.md
|
|
30
34
|
config.json
|
|
31
35
|
AGDF_RUN.md
|
|
32
36
|
MASTER_BACKLOG.md
|
|
@@ -71,7 +75,13 @@ npx --yes @agdf/cli@latest gate-check --json
|
|
|
71
75
|
|
|
72
76
|
## Operating Rules
|
|
73
77
|
|
|
74
|
-
-
|
|
78
|
+
- Canonical mutable state is isolated per run at `runs/<run_id>/RUN_STATE.md`. Run discovery is
|
|
79
|
+
derived; do not maintain a writable active-run index. Select explicitly with `--run` or
|
|
80
|
+
`AGDF_RUN_ID` when more than one run is active.
|
|
81
|
+
- Migrate legacy state explicitly with `run-migrate`; read-only commands never migrate. A retained
|
|
82
|
+
`AGDF_RUN.md` is migration input or a non-authoritative projection, not a second writable owner.
|
|
83
|
+
|
|
84
|
+
- The selected `runs/<run_id>/RUN_STATE.md` is the current run dashboard.
|
|
75
85
|
- `config.json` stores the project preference for artefact and chat language; runtime rules stay English.
|
|
76
86
|
- `MASTER_BACKLOG.md` points to active delivery work; detailed artefacts live beside the work item.
|
|
77
87
|
- Brownfield Review records the post-UR Mode/Slice Decision before PRD depth or Quick Task execution is chosen.
|
|
@@ -80,6 +90,8 @@ npx --yes @agdf/cli@latest gate-check --json
|
|
|
80
90
|
- `SOT_REGISTRY.md` decides which document owns which domain.
|
|
81
91
|
- `CONTEXT_GRAPH.md` records durable project knowledge only when it has evidence and an exit criterion.
|
|
82
92
|
- `AGENT_QUALITY_CONTRACTS.json` names reusable block, revise and warning conditions.
|
|
83
|
-
-
|
|
93
|
+
- Never assign `merge=union` to canonical `runs/<run_id>/RUN_STATE.md` records. Concurrent edits to the
|
|
94
|
+
same run must remain conflict-visible; independent runs naturally change different files. After a
|
|
95
|
+
merge or pull, rerun `doctor` and `gate-check` for the selected run.
|
|
84
96
|
|
|
85
97
|
Do not duplicate full product documentation in this scaffold. Link to the authoritative artefact instead.
|