create-agdf 0.4.9 → 0.6.0
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 +9 -7
- package/bin/create-agdf.js +284 -152
- 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-delivery-path-search/SKILL.md +14 -0
- 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 +24 -8
- package/generated/.opencode/agdf-runtime-contract.md +24 -8
- package/generated/.opencode/agents/agdf-brownfield-analysis.md +2 -2
- package/generated/.opencode/agents/agdf-delivery-path-search.md +14 -0
- 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 +24 -8
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +3 -3
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +14 -0
- 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/candidate-policy.js +67 -2
- package/lib/delivery-path-search/contracts.js +127 -0
- package/lib/delivery-path-search/evaluators/claude.js +2 -16
- package/lib/delivery-path-search/evaluators/codex.js +2 -16
- package/lib/delivery-path-search/generators/claude.js +40 -0
- package/lib/delivery-path-search/generators/codex.js +38 -0
- package/lib/delivery-path-search/generators/protocol.js +12 -0
- package/lib/delivery-path-search/persistence.js +5 -0
- package/lib/delivery-path-search/search-engine.js +64 -2
- package/lib/delivery-path-search/state-adapter.js +119 -16
- package/lib/delivery-path-search/transports/read-only-guard.js +43 -0
- package/package.json +4 -2
package/bin/create-agdf.js
CHANGED
|
@@ -11,8 +11,20 @@ import { runDeliveryPathSearch } from "../lib/delivery-path-search/search-engine
|
|
|
11
11
|
import { codexEvaluator } from "../lib/delivery-path-search/evaluators/codex.js";
|
|
12
12
|
import { claudeEvaluator } from "../lib/delivery-path-search/evaluators/claude.js";
|
|
13
13
|
import { fixtureEvaluator } from "../lib/delivery-path-search/evaluators/protocol.js";
|
|
14
|
+
import { codexGenerator } from "../lib/delivery-path-search/generators/codex.js";
|
|
15
|
+
import { claudeGenerator } from "../lib/delivery-path-search/generators/claude.js";
|
|
16
|
+
import { fixtureGenerator } from "../lib/delivery-path-search/generators/protocol.js";
|
|
14
17
|
import { enforcementForSurface } from "../lib/delivery-path-search/surfaces/capabilities.js";
|
|
15
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";
|
|
16
28
|
|
|
17
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
30
|
const packageRoot = resolve(__dirname, "..");
|
|
@@ -22,7 +34,7 @@ const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
|
|
|
22
34
|
const pluginInstallCommand = "npx --yes @agdf/cli@latest claude";
|
|
23
35
|
const npmCommand = process.platform === "win32" ? process.execPath : "npm";
|
|
24
36
|
const npmPrefixArgs = process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
25
|
-
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"]);
|
|
26
38
|
const agdfFragmentPath = "AGENTS.agdf.md";
|
|
27
39
|
const openCodeConfigFragmentPath = "opencode.agdf.json";
|
|
28
40
|
const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
@@ -73,6 +85,7 @@ const codexPluginFiles = [
|
|
|
73
85
|
join("plugins", "agdf", ".codex-plugin", "plugin.json"),
|
|
74
86
|
join("plugins", "agdf", "control", "README.md"),
|
|
75
87
|
join("plugins", "agdf", "control", "templates", "AGDF_RUN.md"),
|
|
88
|
+
join("plugins", "agdf", "control", "templates", "RUN_STATE.md"),
|
|
76
89
|
join("plugins", "agdf", "control", "templates", "MASTER_BACKLOG.md"),
|
|
77
90
|
join("plugins", "agdf", "control", "templates", "SOT_REGISTRY.md"),
|
|
78
91
|
join("plugins", "agdf", "control", "templates", "CONTEXT_GRAPH.md"),
|
|
@@ -96,6 +109,7 @@ const codexPluginFiles = [
|
|
|
96
109
|
const controlFiles = [
|
|
97
110
|
join(".agdf", "control", "README.md"),
|
|
98
111
|
join(".agdf", "control", "templates", "AGDF_RUN.md"),
|
|
112
|
+
join(".agdf", "control", "templates", "RUN_STATE.md"),
|
|
99
113
|
join(".agdf", "control", "templates", "MASTER_BACKLOG.md"),
|
|
100
114
|
join(".agdf", "control", "templates", "SOT_REGISTRY.md"),
|
|
101
115
|
join(".agdf", "control", "templates", "CONTEXT_GRAPH.md"),
|
|
@@ -177,6 +191,9 @@ Preferred AGDF CLI:
|
|
|
177
191
|
npx --yes @agdf/cli@latest gate-check --json
|
|
178
192
|
npx --yes @agdf/cli@latest delivery-path-search --surface codex --json
|
|
179
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>
|
|
180
197
|
|
|
181
198
|
Scaffold-compatible npm create usage:
|
|
182
199
|
npm create agdf@latest -- codex
|
|
@@ -207,12 +224,21 @@ Options:
|
|
|
207
224
|
--lang <de|en> Alias for --language
|
|
208
225
|
--json Print machine-readable command output as JSON
|
|
209
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)
|
|
210
229
|
--surface <codex|claude|copilot|opencode|generic>
|
|
211
230
|
Declare the active Delivery Path Search surface
|
|
212
231
|
--fixture <path>
|
|
213
232
|
Use deterministic evaluator/candidate fixtures instead of a live evaluator
|
|
214
233
|
--persist Persist the redacted Delivery Path Search result under the current scope
|
|
215
234
|
--model <id> Optional Codex evaluator model
|
|
235
|
+
--generate-candidates
|
|
236
|
+
Add one bounded AI-native candidate-generation call before evaluation
|
|
237
|
+
--generator-model <id>
|
|
238
|
+
Optional candidate-generator model override
|
|
239
|
+
--max-generated-candidates <1-5>
|
|
240
|
+
--generation-timeout-ms <1-30000>
|
|
241
|
+
--generation-cost-units <1-5>
|
|
216
242
|
--help Show this help
|
|
217
243
|
`);
|
|
218
244
|
}
|
|
@@ -281,6 +307,13 @@ function parseArgs(argv) {
|
|
|
281
307
|
let fixture;
|
|
282
308
|
let persist = false;
|
|
283
309
|
let model;
|
|
310
|
+
let generateCandidates = false;
|
|
311
|
+
let generatorModel;
|
|
312
|
+
let maxGeneratedCandidates = 5;
|
|
313
|
+
let generationTimeoutMs = 30000;
|
|
314
|
+
let generationCostUnits = 5;
|
|
315
|
+
let runId;
|
|
316
|
+
let allActive = false;
|
|
284
317
|
|
|
285
318
|
for (let i = 0; i < args.length; i += 1) {
|
|
286
319
|
const arg = args[i];
|
|
@@ -311,7 +344,28 @@ function parseArgs(argv) {
|
|
|
311
344
|
continue;
|
|
312
345
|
}
|
|
313
346
|
|
|
314
|
-
if (
|
|
347
|
+
if (arg === "--generate-candidates") {
|
|
348
|
+
generateCandidates = true;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
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
|
+
|
|
368
|
+
if (["--surface", "--fixture", "--model", "--generator-model", "--max-generated-candidates", "--generation-timeout-ms", "--generation-cost-units"].includes(arg)) {
|
|
315
369
|
const next = args[i + 1];
|
|
316
370
|
if (!next) {
|
|
317
371
|
console.error(`Missing value for ${arg}`);
|
|
@@ -324,7 +378,19 @@ function parseArgs(argv) {
|
|
|
324
378
|
}
|
|
325
379
|
surface = next;
|
|
326
380
|
} else if (arg === "--fixture") fixture = next;
|
|
327
|
-
else model = next;
|
|
381
|
+
else if (arg === "--model") model = next;
|
|
382
|
+
else if (arg === "--generator-model") generatorModel = next;
|
|
383
|
+
else {
|
|
384
|
+
const value = Number(next);
|
|
385
|
+
const maximum = arg === "--generation-timeout-ms" ? 30000 : 5;
|
|
386
|
+
if (!Number.isInteger(value) || value < 1 || value > maximum) {
|
|
387
|
+
console.error(`${arg} must be an integer from 1 to ${maximum}.`);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
if (arg === "--max-generated-candidates") maxGeneratedCandidates = value;
|
|
391
|
+
else if (arg === "--generation-timeout-ms") generationTimeoutMs = value;
|
|
392
|
+
else generationCostUnits = value;
|
|
393
|
+
}
|
|
328
394
|
i += 1;
|
|
329
395
|
continue;
|
|
330
396
|
}
|
|
@@ -395,6 +461,13 @@ function parseArgs(argv) {
|
|
|
395
461
|
fixture: fixture ? resolve(process.cwd(), fixture) : null,
|
|
396
462
|
persist,
|
|
397
463
|
model,
|
|
464
|
+
generateCandidates,
|
|
465
|
+
runId,
|
|
466
|
+
allActive,
|
|
467
|
+
generatorModel,
|
|
468
|
+
maxGeneratedCandidates,
|
|
469
|
+
generationTimeoutMs,
|
|
470
|
+
generationCostUnits,
|
|
398
471
|
};
|
|
399
472
|
}
|
|
400
473
|
|
|
@@ -825,7 +898,7 @@ function printNextSteps(target, destination, files, wroteAgentsFragment, wroteOp
|
|
|
825
898
|
console.log(`- AGDF language preference: artefacts=${language.artifact_language}, chat=${language.chat_language}, runtime=${language.runtime_language}.`);
|
|
826
899
|
}
|
|
827
900
|
if (target === "init") {
|
|
828
|
-
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.");
|
|
829
902
|
console.log("- Run npx --yes @agdf/cli@latest doctor to check the control state before the next agent run.");
|
|
830
903
|
console.log("- Commit the live control files once they represent the repository's current delivery state.");
|
|
831
904
|
return;
|
|
@@ -1136,6 +1209,16 @@ function hasFilledEvidenceRow(content) {
|
|
|
1136
1209
|
});
|
|
1137
1210
|
}
|
|
1138
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
|
+
|
|
1139
1222
|
function markdownSection(content, heading) {
|
|
1140
1223
|
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1141
1224
|
return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?:\\n## |\\n# |$)`))?.[1] ?? "";
|
|
@@ -1145,12 +1228,6 @@ function filled(value) {
|
|
|
1145
1228
|
return Boolean(value && !isPlaceholderValue(value) && value.trim() !== "");
|
|
1146
1229
|
}
|
|
1147
1230
|
|
|
1148
|
-
function nonTemplateRows(section, headerCell) {
|
|
1149
|
-
return tableRows(section)
|
|
1150
|
-
.filter((cells) => cells[0] !== headerCell)
|
|
1151
|
-
.filter((cells) => cells.some((cell) => filled(cell)));
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
1231
|
function parseQualityContracts(content) {
|
|
1155
1232
|
const parsed = JSON.parse(content);
|
|
1156
1233
|
if (!Array.isArray(parsed.contracts) || parsed.contracts.length === 0) {
|
|
@@ -1164,9 +1241,61 @@ function parseQualityContracts(content) {
|
|
|
1164
1241
|
return parsed;
|
|
1165
1242
|
}
|
|
1166
1243
|
|
|
1167
|
-
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
|
+
}
|
|
1168
1281
|
const findings = [];
|
|
1169
|
-
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
|
+
);
|
|
1170
1299
|
|
|
1171
1300
|
for (const relativePath of missing) {
|
|
1172
1301
|
const templatePath = join(dirname(relativePath), "templates", relativePath.split("/").at(-1));
|
|
@@ -1184,8 +1313,9 @@ function evaluateDoctor(targetDir) {
|
|
|
1184
1313
|
}
|
|
1185
1314
|
|
|
1186
1315
|
if (missing.length === 0) {
|
|
1187
|
-
const
|
|
1188
|
-
const
|
|
1316
|
+
const selectedRunState = readRunState(targetDir, selection);
|
|
1317
|
+
const runPath = selectedRunState.path;
|
|
1318
|
+
const run = selectedRunState.content;
|
|
1189
1319
|
const currentGateLine = run.match(/^- current_gate:[^\S\r\n]*(.*)$/m)?.[1]?.trim() ?? "";
|
|
1190
1320
|
const nextActionLine = run.match(/^- next_allowed_action:[^\S\r\n]*(.*)$/m)?.[1]?.trim() ?? "";
|
|
1191
1321
|
const hasEvidence = hasFilledEvidenceRow(run);
|
|
@@ -1195,7 +1325,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1195
1325
|
findings,
|
|
1196
1326
|
"revise",
|
|
1197
1327
|
"AGDF_CURRENT_GATE_MISSING",
|
|
1198
|
-
"
|
|
1328
|
+
"The selected run state does not name the current gate.",
|
|
1199
1329
|
runPath,
|
|
1200
1330
|
"Set current_gate to the current delivery gate or none.",
|
|
1201
1331
|
);
|
|
@@ -1206,7 +1336,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1206
1336
|
findings,
|
|
1207
1337
|
"revise",
|
|
1208
1338
|
"AGDF_NEXT_ALLOWED_ACTION_MISSING",
|
|
1209
|
-
"
|
|
1339
|
+
"The selected run state does not state the next allowed action.",
|
|
1210
1340
|
runPath,
|
|
1211
1341
|
"Fill the next allowed action before asking an agent to continue delivery work.",
|
|
1212
1342
|
);
|
|
@@ -1217,7 +1347,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1217
1347
|
findings,
|
|
1218
1348
|
"warn",
|
|
1219
1349
|
"AGDF_EVIDENCE_EMPTY",
|
|
1220
|
-
"
|
|
1350
|
+
"The selected run state has no visible evidence row yet.",
|
|
1221
1351
|
runPath,
|
|
1222
1352
|
"Add at least one evidence row or explicitly document that no evidence exists yet.",
|
|
1223
1353
|
);
|
|
@@ -1300,7 +1430,7 @@ function evaluateDoctor(targetDir) {
|
|
|
1300
1430
|
);
|
|
1301
1431
|
}
|
|
1302
1432
|
|
|
1303
|
-
const runState =
|
|
1433
|
+
const runState = selectedRunState;
|
|
1304
1434
|
for (const finding of analyzeDurableGateArtefactConsistency(runState)) {
|
|
1305
1435
|
addFinding(findings, finding.severity, finding.code, finding.message, finding.path, finding.next_step);
|
|
1306
1436
|
}
|
|
@@ -1352,34 +1482,22 @@ function extractField(content, field) {
|
|
|
1352
1482
|
return content.match(pattern)?.[1]?.trim() ?? "";
|
|
1353
1483
|
}
|
|
1354
1484
|
|
|
1355
|
-
function extractSectionField(section, field) {
|
|
1356
|
-
const pattern = new RegExp(`^- ${field}:[^\\S\\r\\n]*(.*)$`, "m");
|
|
1357
|
-
return section.match(pattern)?.[1]?.trim() ?? "";
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
1485
|
function cleanStatusCell(value) {
|
|
1361
1486
|
return value.replace(/^`|`$/g, "").trim();
|
|
1362
1487
|
}
|
|
1363
1488
|
|
|
1364
|
-
function
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
for (const cells of tableRows(section)) {
|
|
1372
|
-
const [gate, status, evidence] = cells;
|
|
1373
|
-
if (!userGateOrder.includes(gate)) continue;
|
|
1374
|
-
approvals.set(gate, {
|
|
1375
|
-
status: normalizeGateStatus(gate, status),
|
|
1376
|
-
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,
|
|
1377
1496
|
});
|
|
1497
|
+
runPath = selected.run.path.startsWith(targetDir)
|
|
1498
|
+
? selected.run.path.slice(targetDir.length + 1)
|
|
1499
|
+
: selected.run.path;
|
|
1378
1500
|
}
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
function readRunState(targetDir) {
|
|
1382
|
-
const runPath = join(".agdf", "control", "AGDF_RUN.md");
|
|
1383
1501
|
if (!existsSync(join(targetDir, runPath))) {
|
|
1384
1502
|
return {
|
|
1385
1503
|
path: runPath,
|
|
@@ -1401,107 +1519,15 @@ function readRunState(targetDir) {
|
|
|
1401
1519
|
}
|
|
1402
1520
|
|
|
1403
1521
|
const content = readTargetFile(targetDir, runPath);
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const artefacts = new Map();
|
|
1409
|
-
const artefactsSection = markdownSection(content, "Artefacts");
|
|
1410
|
-
for (const cells of tableRows(artefactsSection)) {
|
|
1411
|
-
const [type, path, status, notes] = cells;
|
|
1412
|
-
if (!userGateOrder.includes(type) && !internalStepArtefacts.has(type)) continue;
|
|
1413
|
-
artefacts.set(type, {
|
|
1414
|
-
path: path ?? "",
|
|
1415
|
-
status: cleanStatusCell(status ?? ""),
|
|
1416
|
-
notes: notes ?? "",
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
const artefactChain = nonTemplateRows(markdownSection(content, "Artefact Chain"), "From")
|
|
1421
|
-
.map((cells) => ({
|
|
1422
|
-
from: cells[0] ?? "",
|
|
1423
|
-
relationship: cleanStatusCell(cells[1] ?? ""),
|
|
1424
|
-
to: cleanStatusCell(cells[2] ?? ""),
|
|
1425
|
-
evidence: cells[3] ?? "",
|
|
1426
|
-
}));
|
|
1427
|
-
|
|
1428
|
-
const evidence_refs = tableRows(markdownSection(content, "Evidence"))
|
|
1429
|
-
.filter((cells) => cells[0] !== "Evidence")
|
|
1430
|
-
.filter((cells) => filled(cells[0]) || filled(cells[1]) || filled(cells[2]))
|
|
1431
|
-
.map((cells) => ({
|
|
1432
|
-
evidence: cells[0] ?? "",
|
|
1433
|
-
source: cells[1] ?? "",
|
|
1434
|
-
covers: cells[2] ?? "",
|
|
1435
|
-
strength: cleanStatusCell(cells[3] ?? ""),
|
|
1436
|
-
}));
|
|
1437
|
-
|
|
1438
|
-
const missingEvidence = tableRows(markdownSection(content, "Missing Evidence"))
|
|
1439
|
-
.filter((cells) => cells[0] !== "Missing evidence")
|
|
1440
|
-
.filter((cells) => filled(cells[0]))
|
|
1441
|
-
.map((cells) => ({
|
|
1442
|
-
missing_evidence: cells[0] ?? "",
|
|
1443
|
-
impact: cleanStatusCell(cells[1] ?? ""),
|
|
1444
|
-
required_next_step: cells[2] ?? "",
|
|
1445
|
-
}));
|
|
1446
|
-
|
|
1447
|
-
const risks = tableRows(markdownSection(content, "Risks"))
|
|
1448
|
-
.filter((cells) => cells[0] !== "Risk")
|
|
1449
|
-
.filter((cells) => filled(cells[0]))
|
|
1450
|
-
.map((cells) => ({
|
|
1451
|
-
risk: cells[0] ?? "",
|
|
1452
|
-
impact: cleanStatusCell(cells[1] ?? ""),
|
|
1453
|
-
mitigation_or_owner: cells[2] ?? "",
|
|
1454
|
-
}));
|
|
1455
|
-
|
|
1456
|
-
const contextGraph = {
|
|
1457
|
-
impact: cleanStatusCell(extractField(content, "context_graph_impact")),
|
|
1458
|
-
refs: extractField(content, "context_graph_refs"),
|
|
1459
|
-
required_action: cleanStatusCell(extractField(content, "context_graph_required_action")),
|
|
1460
|
-
gate_effect: cleanStatusCell(extractField(content, "context_graph_gate_effect")),
|
|
1461
|
-
evidence: extractField(content, "context_graph_evidence"),
|
|
1462
|
-
};
|
|
1463
|
-
|
|
1464
|
-
const modeSliceSection = markdownSection(content, "Mode / Slice Decision");
|
|
1465
|
-
const modeSliceDecision = {
|
|
1466
|
-
decision: cleanStatusCell(extractSectionField(modeSliceSection, "decision")),
|
|
1467
|
-
required_next_gate: cleanStatusCell(extractSectionField(modeSliceSection, "required_next_gate")),
|
|
1468
|
-
scope_reason: extractSectionField(modeSliceSection, "scope_reason"),
|
|
1469
|
-
evidence: extractSectionField(modeSliceSection, "evidence"),
|
|
1470
|
-
};
|
|
1471
|
-
|
|
1472
|
-
const sourceScopeSection = markdownSection(content, "Source And Scope State");
|
|
1473
|
-
const sourceScope = {
|
|
1474
|
-
normative_instruction_source: extractSectionField(sourceScopeSection, "normative_instruction_source"),
|
|
1475
|
-
multi_scope_state: cleanStatusCell(extractSectionField(sourceScopeSection, "multi_scope_state")),
|
|
1476
|
-
active_scope_evidence: extractSectionField(sourceScopeSection, "active_scope_evidence"),
|
|
1477
|
-
competing_scope_lines: extractSectionField(sourceScopeSection, "competing_scope_lines"),
|
|
1478
|
-
branch_workspace_evidence: extractSectionField(sourceScopeSection, "branch_workspace_evidence"),
|
|
1479
|
-
branch_workspace_scope_effect: cleanStatusCell(extractSectionField(sourceScopeSection, "branch_workspace_scope_effect")),
|
|
1480
|
-
};
|
|
1481
|
-
|
|
1482
|
-
const memorySection = markdownSection(content, "Knowledge Persistence Decision");
|
|
1483
|
-
const memory = {
|
|
1484
|
-
target: cleanStatusCell(extractSectionField(memorySection, "memory_target")),
|
|
1485
|
-
reason: extractSectionField(memorySection, "memory_reason"),
|
|
1486
|
-
refs: extractSectionField(memorySection, "memory_refs"),
|
|
1487
|
-
};
|
|
1522
|
+
const parsed = parseControlState(content, {
|
|
1523
|
+
userGates: userGateOrder,
|
|
1524
|
+
internalSteps: [...internalStepArtefacts],
|
|
1525
|
+
});
|
|
1488
1526
|
|
|
1489
1527
|
return {
|
|
1490
1528
|
path: runPath,
|
|
1491
1529
|
content,
|
|
1492
|
-
|
|
1493
|
-
next_allowed_action: extractField(content, "next_allowed_action"),
|
|
1494
|
-
approvals,
|
|
1495
|
-
artefacts,
|
|
1496
|
-
evidence_refs,
|
|
1497
|
-
artefact_chain: artefactChain,
|
|
1498
|
-
mode_slice_decision: modeSliceDecision,
|
|
1499
|
-
missing_evidence: missingEvidence,
|
|
1500
|
-
risks,
|
|
1501
|
-
context_graph: contextGraph,
|
|
1502
|
-
quality_outlook: extractField(content, "quality_outlook"),
|
|
1503
|
-
source_scope: sourceScope,
|
|
1504
|
-
memory,
|
|
1530
|
+
...parsed,
|
|
1505
1531
|
};
|
|
1506
1532
|
}
|
|
1507
1533
|
|
|
@@ -1562,7 +1588,7 @@ function analyzeDurableGateArtefactConsistency(runState) {
|
|
|
1562
1588
|
code: "AGDF_GATE_ARTEFACT_STATUS_INCONSISTENT",
|
|
1563
1589
|
message: `${gate} approval is recorded, but the durable artefact row uses status \`${artefact.status}\`; expected ${describeDurableArtefactStatuses(gate)}.`,
|
|
1564
1590
|
path: runState.path,
|
|
1565
|
-
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.`,
|
|
1566
1592
|
});
|
|
1567
1593
|
}
|
|
1568
1594
|
|
|
@@ -1810,7 +1836,7 @@ function durableArtefactBlock(gate, nextGate) {
|
|
|
1810
1836
|
missing_approval: "none",
|
|
1811
1837
|
allowed: [
|
|
1812
1838
|
`persist the approved ${label} in a stable artefact path such as ${stablePath}`,
|
|
1813
|
-
"link the artefact from
|
|
1839
|
+
"link the artefact from the selected RUN_STATE.md and MASTER_BACKLOG.md",
|
|
1814
1840
|
"run gate-check again",
|
|
1815
1841
|
],
|
|
1816
1842
|
forbidden: nextGate
|
|
@@ -1855,7 +1881,7 @@ function transitionDecisionForRunState(runState) {
|
|
|
1855
1881
|
allowed: [
|
|
1856
1882
|
"run Brownfield Review after G-00",
|
|
1857
1883
|
"identify existing workstream, owners, SoT, reuse risks and open PRD/SD questions",
|
|
1858
|
-
"mark Brownfield Review as done or not_applicable in
|
|
1884
|
+
"mark Brownfield Review as done or not_applicable in the selected RUN_STATE.md",
|
|
1859
1885
|
],
|
|
1860
1886
|
forbidden: ["create PRD before Brownfield Review is resolved", "create SD", "create TP", "implement code", "claim QA or release readiness"],
|
|
1861
1887
|
next_allowed_action: "Run Brownfield Review after G-00 before drafting PRD, or mark Brownfield Review not_applicable with evidence.",
|
|
@@ -1871,7 +1897,7 @@ function transitionDecisionForRunState(runState) {
|
|
|
1871
1897
|
missing_approval: "none",
|
|
1872
1898
|
allowed: [
|
|
1873
1899
|
"decide whether the approved UR is quick_task, structured_slice, structured_delivery or block",
|
|
1874
|
-
"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",
|
|
1875
1901
|
"choose the next required gate depth before drafting PRD or implementing",
|
|
1876
1902
|
],
|
|
1877
1903
|
forbidden: ["create PRD before process size is decided", "create SD", "create TP", "implement code", "claim QA or release readiness"],
|
|
@@ -1981,9 +2007,9 @@ function transitionDecisionForRunState(runState) {
|
|
|
1981
2007
|
};
|
|
1982
2008
|
}
|
|
1983
2009
|
|
|
1984
|
-
function evaluateGateCheck(targetDir) {
|
|
1985
|
-
const doctorReport = evaluateDoctor(targetDir);
|
|
1986
|
-
const runState = readRunState(targetDir);
|
|
2010
|
+
function evaluateGateCheck(targetDir, selection = {}) {
|
|
2011
|
+
const doctorReport = evaluateDoctor(targetDir, selection);
|
|
2012
|
+
const runState = readRunState(targetDir, selection);
|
|
1987
2013
|
const transitionDecision = transitionDecisionForRunState(runState);
|
|
1988
2014
|
const deliveryMap = analyzeDeliveryMap(runState);
|
|
1989
2015
|
const doctorBlocker = doctorReport.findings.find((finding) => finding.severity === "block");
|
|
@@ -2118,9 +2144,51 @@ function readBacklogPointers(targetDir) {
|
|
|
2118
2144
|
return parseBacklogSection(activeSection);
|
|
2119
2145
|
}
|
|
2120
2146
|
|
|
2121
|
-
function evaluateDeliveryMap(targetDir) {
|
|
2122
|
-
|
|
2123
|
-
|
|
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);
|
|
2124
2192
|
const map = analyzeDeliveryMap(runState);
|
|
2125
2193
|
const gateDecision = transitionDecisionForRunState(runState);
|
|
2126
2194
|
const currentGate = effectiveCurrentGate(runState, gateDecision);
|
|
@@ -2198,7 +2266,28 @@ function printDeliveryMapReport(report, json) {
|
|
|
2198
2266
|
async function executeDeliveryPathSearch(options) {
|
|
2199
2267
|
const fixture = options.fixture ? JSON.parse(readFileSync(options.fixture, "utf8")) : null;
|
|
2200
2268
|
const enforcement = fixture?.input?.enforcement ?? enforcementForSurface(options.surface);
|
|
2201
|
-
const
|
|
2269
|
+
const fixtureGeneration = fixture?.generator_response;
|
|
2270
|
+
const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
|
|
2271
|
+
const baseInput = fixture?.input ?? searchInputFromControl(options.dir, {
|
|
2272
|
+
scopeKey: options.runId,
|
|
2273
|
+
enforcement,
|
|
2274
|
+
generation: generationEnabled ? {
|
|
2275
|
+
enabled: true,
|
|
2276
|
+
maxProposals: options.maxGeneratedCandidates,
|
|
2277
|
+
maxDurationMs: options.generationTimeoutMs,
|
|
2278
|
+
maxCostUnits: options.generationCostUnits,
|
|
2279
|
+
} : undefined,
|
|
2280
|
+
});
|
|
2281
|
+
const input = generationEnabled && !baseInput.generation ? {
|
|
2282
|
+
...baseInput,
|
|
2283
|
+
generation: {
|
|
2284
|
+
enabled: true,
|
|
2285
|
+
max_calls: 1,
|
|
2286
|
+
max_proposals: options.maxGeneratedCandidates,
|
|
2287
|
+
max_duration_ms: options.generationTimeoutMs,
|
|
2288
|
+
max_cost_units: options.generationCostUnits,
|
|
2289
|
+
},
|
|
2290
|
+
} : baseInput;
|
|
2202
2291
|
const evaluator = fixture
|
|
2203
2292
|
? fixtureEvaluator(fixture.evaluations ?? {})
|
|
2204
2293
|
: options.surface === "codex"
|
|
@@ -2209,7 +2298,17 @@ async function executeDeliveryPathSearch(options) {
|
|
|
2209
2298
|
if (!evaluator) {
|
|
2210
2299
|
throw new Error(`${options.surface} has no executable evaluator in this release. Codex and Claude are the reference adapters; --fixture is available for deterministic contract testing.`);
|
|
2211
2300
|
}
|
|
2212
|
-
const
|
|
2301
|
+
const generator = fixtureGeneration
|
|
2302
|
+
? fixtureGenerator(fixtureGeneration)
|
|
2303
|
+
: options.generateCandidates && options.surface === "codex"
|
|
2304
|
+
? codexGenerator({ cwd: options.dir, model: options.generatorModel, timeoutMs: options.generationTimeoutMs })
|
|
2305
|
+
: options.generateCandidates && options.surface === "claude"
|
|
2306
|
+
? claudeGenerator({ cwd: options.dir, model: options.generatorModel, timeoutMs: options.generationTimeoutMs })
|
|
2307
|
+
: null;
|
|
2308
|
+
if (options.generateCandidates && !generator) {
|
|
2309
|
+
throw new Error(`${options.surface} has no executable candidate generator; Codex and Claude are tool-enforced reference transports.`);
|
|
2310
|
+
}
|
|
2311
|
+
const result = await runDeliveryPathSearch(input, evaluator, { candidates: fixture?.candidates, generator });
|
|
2213
2312
|
if (options.persist) result.persistence = persistSearchResult(options.dir, result);
|
|
2214
2313
|
if (options.json) {
|
|
2215
2314
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -2220,6 +2319,9 @@ async function executeDeliveryPathSearch(options) {
|
|
|
2220
2319
|
console.log(`Enforcement: ${result.enforcement.level}`);
|
|
2221
2320
|
console.log(`Recommendation: ${result.recommendation?.action ?? "none"}`);
|
|
2222
2321
|
console.log(`Evaluations: ${result.budgets.evaluations}`);
|
|
2322
|
+
console.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
|
|
2323
|
+
console.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
|
|
2324
|
+
if (result.generation.failure_code) console.log(`Generation failure: ${result.generation.failure_code}`);
|
|
2223
2325
|
console.log(`Stopping reason: ${result.stopping_reason}`);
|
|
2224
2326
|
console.log(result.next_gate_action);
|
|
2225
2327
|
return result;
|
|
@@ -2228,20 +2330,50 @@ async function executeDeliveryPathSearch(options) {
|
|
|
2228
2330
|
async function main() {
|
|
2229
2331
|
const options = parseArgs(process.argv.slice(2));
|
|
2230
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
|
+
|
|
2231
2363
|
if (options.target === "doctor") {
|
|
2232
|
-
const report = evaluateDoctor(options.dir);
|
|
2364
|
+
const report = evaluateDoctor(options.dir, options);
|
|
2233
2365
|
printDoctorReport(report, options.json);
|
|
2234
2366
|
process.exit(report.status === "block" ? 2 : 0);
|
|
2235
2367
|
}
|
|
2236
2368
|
|
|
2237
2369
|
if (options.target === "gate-check") {
|
|
2238
|
-
const report = evaluateGateCheck(options.dir);
|
|
2370
|
+
const report = evaluateGateCheck(options.dir, options);
|
|
2239
2371
|
printGateCheckReport(report, options.json, options.statusCard);
|
|
2240
2372
|
process.exit(report.status === "blocked" ? 2 : 0);
|
|
2241
2373
|
}
|
|
2242
2374
|
|
|
2243
2375
|
if (options.target === "delivery-map") {
|
|
2244
|
-
const report = evaluateDeliveryMap(options.dir);
|
|
2376
|
+
const report = evaluateDeliveryMap(options.dir, options);
|
|
2245
2377
|
printDeliveryMapReport(report, options.json);
|
|
2246
2378
|
process.exit(report.status === "block" ? 2 : 0);
|
|
2247
2379
|
}
|