create-agdf 0.3.4 → 0.4.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 +29 -11
- package/bin/create-agdf.js +546 -32
- package/generated/.agdf/control/templates/AGDF_RUN.md +29 -0
- package/generated/.agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
- package/generated/.agdf/control/templates/MASTER_BACKLOG.md +7 -10
- package/generated/.agdf/control/templates/artefacts/OR.md +20 -4
- package/generated/.github/skills/agdf-gate-check/SKILL.md +7 -0
- package/generated/.github/skills/agdf-release-or/SKILL.md +2 -0
- package/generated/.github/skills/agdf-runtime-contract.md +132 -0
- package/generated/.opencode/AGDF.md +8 -0
- package/generated/.opencode/README.md +4 -0
- package/generated/.opencode/agdf-runtime-contract.md +132 -0
- package/generated/.opencode/agents/agdf-brownfield-analysis.md +0 -5
- package/generated/.opencode/agents/agdf-clean-implementation-review.md +0 -5
- package/generated/.opencode/agents/agdf-code-review.md +0 -5
- package/generated/.opencode/agents/agdf-delivery-closeout.md +0 -5
- package/generated/.opencode/agents/agdf-gate-check.md +7 -5
- package/generated/.opencode/agents/agdf-qa-gate.md +0 -5
- package/generated/.opencode/agents/agdf-release-or.md +2 -5
- package/generated/.opencode/agents/agdf-task-plan-review.md +0 -5
- package/generated/AGENTS.md +4 -0
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/AGDF_RUN.md +29 -0
- package/generated/plugins/agdf/control/templates/AGENT_QUALITY_CONTRACTS.json +48 -0
- package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +7 -10
- package/generated/plugins/agdf/control/templates/artefacts/OR.md +20 -4
- package/generated/plugins/agdf/meta/agdf-agent-router.md +4 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +132 -0
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +7 -0
- package/generated/plugins/agdf/skills/release-or/SKILL.md +2 -0
- package/opencode-plugin.js +16 -2
- package/package.json +1 -1
package/bin/create-agdf.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join, posix, resolve } from "node:path";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
6
8
|
import process from "node:process";
|
|
7
9
|
|
|
@@ -10,8 +12,8 @@ const packageRoot = resolve(__dirname, "..");
|
|
|
10
12
|
const generatedRoot = join(packageRoot, "generated");
|
|
11
13
|
const pluginDefinitionPath = join(generatedRoot, "plugins", "agdf", "meta", "agdf-plugin.definition.json");
|
|
12
14
|
const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
|
|
13
|
-
const pluginInstallCommand = "
|
|
14
|
-
const allowedTargets = new Set(["codex", "copilot", "opencode", "both", "init", "config", "doctor", "gate-check", "delivery-map"]);
|
|
15
|
+
const pluginInstallCommand = "npx --yes @agdf/cli@latest claude";
|
|
16
|
+
const allowedTargets = new Set(["codex", "codex-repo", "claude", "copilot", "opencode", "opencode-repo", "both", "init", "config", "doctor", "gate-check", "delivery-map"]);
|
|
15
17
|
const agdfFragmentPath = "AGENTS.agdf.md";
|
|
16
18
|
const openCodeConfigFragmentPath = "opencode.agdf.json";
|
|
17
19
|
const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
@@ -24,6 +26,36 @@ const deliveryRelationships = [
|
|
|
24
26
|
{ from: "TP", relationship: "derived_from", to: "SD", requiredBy: "TP" },
|
|
25
27
|
{ from: "QA_REPORT", relationship: "tests", to: "TP", requiredBy: "QA" },
|
|
26
28
|
];
|
|
29
|
+
const nextSkillByGate = {
|
|
30
|
+
UR: "gate-check",
|
|
31
|
+
"Brownfield Review": "brownfield-analysis",
|
|
32
|
+
"Mode/Slice Decision": "gate-check",
|
|
33
|
+
"Quick Task Execution": "none",
|
|
34
|
+
PRD: "gate-check",
|
|
35
|
+
SD: "gate-check",
|
|
36
|
+
TP: "gate-check",
|
|
37
|
+
"Brownfield Analysis": "brownfield-analysis",
|
|
38
|
+
"CD+Tests": "none",
|
|
39
|
+
CR: "code-review",
|
|
40
|
+
QA: "qa-gate",
|
|
41
|
+
UAT: "delivery-closeout",
|
|
42
|
+
OR: "release-or",
|
|
43
|
+
};
|
|
44
|
+
const gateProgressOrder = [
|
|
45
|
+
"UR",
|
|
46
|
+
"Brownfield Review",
|
|
47
|
+
"Mode/Slice Decision",
|
|
48
|
+
"Quick Task Execution",
|
|
49
|
+
"PRD",
|
|
50
|
+
"SD",
|
|
51
|
+
"TP",
|
|
52
|
+
"Brownfield Analysis",
|
|
53
|
+
"CD+Tests",
|
|
54
|
+
"CR",
|
|
55
|
+
"QA",
|
|
56
|
+
"UAT",
|
|
57
|
+
"OR",
|
|
58
|
+
];
|
|
27
59
|
const codexSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.codex.skillPrefix}${skill.slug}`);
|
|
28
60
|
const copilotSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.copilot.skillPrefix}${skill.slug}`);
|
|
29
61
|
const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => `${pluginDefinition.opencode.skillPrefix}${skill.slug}`);
|
|
@@ -126,15 +158,21 @@ function printUsage() {
|
|
|
126
158
|
|
|
127
159
|
Preferred AGDF CLI:
|
|
128
160
|
npx --yes @agdf/cli@latest codex
|
|
161
|
+
npx --yes @agdf/cli@latest codex-repo
|
|
162
|
+
npx --yes @agdf/cli@latest claude
|
|
129
163
|
npx --yes @agdf/cli@latest opencode
|
|
164
|
+
npx --yes @agdf/cli@latest opencode-repo
|
|
130
165
|
npx --yes @agdf/cli@latest init
|
|
131
166
|
npx --yes @agdf/cli@latest doctor
|
|
132
167
|
npx --yes @agdf/cli@latest gate-check --json
|
|
133
168
|
|
|
134
169
|
Scaffold-compatible npm create usage:
|
|
135
170
|
npm create agdf@latest -- codex
|
|
171
|
+
npm create agdf@latest -- codex-repo
|
|
172
|
+
npm create agdf@latest -- claude
|
|
136
173
|
npm create agdf@latest -- copilot
|
|
137
174
|
npm create agdf@latest -- opencode
|
|
175
|
+
npm create agdf@latest -- opencode-repo
|
|
138
176
|
npm create agdf@latest -- both
|
|
139
177
|
npm create agdf@latest -- init
|
|
140
178
|
npm create agdf@latest -- config --language de
|
|
@@ -147,7 +185,7 @@ Backward-compatible create-agdf usage:
|
|
|
147
185
|
npx --yes create-agdf@latest gate-check --json
|
|
148
186
|
|
|
149
187
|
Options:
|
|
150
|
-
--dir <path> Write files into a specific directory
|
|
188
|
+
--dir <path> Write files into a specific directory. With opencode, use this as the OpenCode config directory.
|
|
151
189
|
--force Overwrite existing generated files
|
|
152
190
|
--language <de|en>
|
|
153
191
|
Set AGDF chat and artefact language. Defaults to detected system locale.
|
|
@@ -215,6 +253,7 @@ function parseArgs(argv) {
|
|
|
215
253
|
let force = false;
|
|
216
254
|
let json = false;
|
|
217
255
|
let language;
|
|
256
|
+
let dirExplicit = false;
|
|
218
257
|
|
|
219
258
|
for (let i = 0; i < args.length; i += 1) {
|
|
220
259
|
const arg = args[i];
|
|
@@ -258,6 +297,7 @@ function parseArgs(argv) {
|
|
|
258
297
|
process.exit(1);
|
|
259
298
|
}
|
|
260
299
|
dir = next;
|
|
300
|
+
dirExplicit = true;
|
|
261
301
|
i += 1;
|
|
262
302
|
continue;
|
|
263
303
|
}
|
|
@@ -283,7 +323,7 @@ function parseArgs(argv) {
|
|
|
283
323
|
}
|
|
284
324
|
|
|
285
325
|
if (!target || !allowedTargets.has(target)) {
|
|
286
|
-
console.error("Please choose one target: codex, copilot, opencode, both, init, config, doctor, gate-check or delivery-map.");
|
|
326
|
+
console.error("Please choose one target: codex, codex-repo, claude, copilot, opencode, opencode-repo, both, init, config, doctor, gate-check or delivery-map.");
|
|
287
327
|
printUsage();
|
|
288
328
|
process.exit(1);
|
|
289
329
|
}
|
|
@@ -293,10 +333,69 @@ function parseArgs(argv) {
|
|
|
293
333
|
dir: resolve(process.cwd(), dir),
|
|
294
334
|
force,
|
|
295
335
|
json,
|
|
336
|
+
dirExplicit,
|
|
296
337
|
language: resolveLanguagePreference(language),
|
|
297
338
|
};
|
|
298
339
|
}
|
|
299
340
|
|
|
341
|
+
function defaultOpenCodeConfigDir() {
|
|
342
|
+
return process.env.OPENCODE_CONFIG_DIR || join(homedir(), ".config", "opencode");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function installOpenCodeGlobalPlugin(configDir) {
|
|
346
|
+
const configPath = join(configDir, "opencode.json");
|
|
347
|
+
let config = {};
|
|
348
|
+
|
|
349
|
+
if (existsSync(configPath)) {
|
|
350
|
+
try {
|
|
351
|
+
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
352
|
+
} catch {
|
|
353
|
+
throw new Error(`Refusing to update unreadable OpenCode config: ${configPath}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (config.plugin !== undefined && !Array.isArray(config.plugin)) {
|
|
358
|
+
throw new Error(`Refusing to update OpenCode config with non-array plugin field: ${configPath}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const plugins = [...(config.plugin ?? [])];
|
|
362
|
+
const alreadyInstalled = plugins.includes(pluginDefinition.opencode.npmPackage);
|
|
363
|
+
if (!alreadyInstalled) {
|
|
364
|
+
plugins.push(pluginDefinition.opencode.npmPackage);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const nextConfig = {
|
|
368
|
+
"$schema": config.$schema ?? "https://opencode.ai/config.json",
|
|
369
|
+
...config,
|
|
370
|
+
plugin: plugins,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
mkdirSync(configDir, { recursive: true });
|
|
374
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
configPath,
|
|
378
|
+
added: !alreadyInstalled,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function installCodexGlobalPlugin() {
|
|
383
|
+
try {
|
|
384
|
+
execFileSync("codex", ["plugin", "marketplace", "add", "arndtgold/ai-native-governance-delivery-framework"], { stdio: "inherit" });
|
|
385
|
+
execFileSync("codex", ["plugin", "add", "agdf", "--marketplace", "agdf"], { stdio: "inherit" });
|
|
386
|
+
} catch (error) {
|
|
387
|
+
throw new Error("Failed to install the AGDF Codex plugin. Make sure the Codex CLI is installed and available on PATH, then rerun this command.");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function installClaudeGlobalPlugin() {
|
|
392
|
+
try {
|
|
393
|
+
execFileSync("claude", ["plugin", "add", "arndtgold/ai-native-governance-delivery-framework"], { stdio: "inherit" });
|
|
394
|
+
} catch {
|
|
395
|
+
throw new Error("Failed to install the AGDF Claude Code plugin. Make sure the Claude Code CLI is installed and available on PATH, then rerun this command.");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
300
399
|
function loadAsset(relativePath) {
|
|
301
400
|
return readFileSync(join(generatedRoot, relativePath), "utf8");
|
|
302
401
|
}
|
|
@@ -355,7 +454,7 @@ function generatedFilesForTarget(target, targetDir, force, languagePreference) {
|
|
|
355
454
|
return files;
|
|
356
455
|
}
|
|
357
456
|
|
|
358
|
-
if (target === "codex" || target === "both") {
|
|
457
|
+
if (target === "codex-repo" || target === "both") {
|
|
359
458
|
addLanguageConfig(files, languagePreference);
|
|
360
459
|
for (const codexPath of codexPluginFiles) {
|
|
361
460
|
files.push({
|
|
@@ -395,7 +494,7 @@ function generatedFilesForTarget(target, targetDir, force, languagePreference) {
|
|
|
395
494
|
}
|
|
396
495
|
}
|
|
397
496
|
|
|
398
|
-
if (target === "opencode") {
|
|
497
|
+
if (target === "opencode-repo") {
|
|
399
498
|
addLanguageConfig(files, languagePreference);
|
|
400
499
|
const openCodeConfigTargetPath = existsSync(join(targetDir, "opencode.json")) && !force ? openCodeConfigFragmentPath : "opencode.json";
|
|
401
500
|
files.push({
|
|
@@ -451,20 +550,23 @@ function printNextSteps(target, destination, files, wroteAgentsFragment, wroteOp
|
|
|
451
550
|
if (wroteAgentsFragment) {
|
|
452
551
|
console.log(`- Existing AGENTS.md detected. Merge ${agdfFragmentPath} into your current AGENTS.md before using Copilot with AGDF.`);
|
|
453
552
|
}
|
|
454
|
-
if (target === "codex" || target === "both") {
|
|
553
|
+
if (target === "codex-repo" || target === "both") {
|
|
455
554
|
console.log("- Restart Codex in this repository, open /plugins, select This repository and install agdf.");
|
|
456
555
|
console.log("- Start a new Codex thread in this repository and ask: Run an AGDF gate check for this request.");
|
|
457
556
|
}
|
|
458
557
|
if (target === "both") {
|
|
459
558
|
console.log(`- Optional global Claude Code install: ${pluginInstallCommand}`);
|
|
460
559
|
}
|
|
461
|
-
if (target === "opencode") {
|
|
560
|
+
if (target === "opencode-repo") {
|
|
462
561
|
if (wroteOpenCodeConfigFragment) {
|
|
463
562
|
console.log(`- Existing opencode.json detected. Merge ${openCodeConfigFragmentPath} into your current opencode.json so OpenCode loads .opencode/AGDF.md.`);
|
|
464
563
|
}
|
|
465
564
|
console.log(`- OpenCode will install the ${pluginDefinition.opencode.npmPackage} npm plugin from opencode.json at startup.`);
|
|
565
|
+
console.log("- Optional: also add create-agdf to ~/.config/opencode/opencode.json plugin[] for a user-wide OpenCode hook.");
|
|
566
|
+
console.log("- The global hook does not replace repository instructions or subagents; this repository's .opencode files remain the AGDF source of truth.");
|
|
466
567
|
console.log("- Start OpenCode in this repository; it will load opencode.json, .opencode/AGDF.md and the AGDF subagents.");
|
|
467
|
-
console.log("-
|
|
568
|
+
console.log("- AGDF OpenCode agents are mode: subagent workflow controls; they are not .opencode/skills entries or primary menu agents.");
|
|
569
|
+
console.log("- Use @agdf-gate-check as the visible entry point for new build/change intent or unclear approval before later artefacts or implementation.");
|
|
468
570
|
console.log("- Run npx --yes @agdf/cli@latest init when the repository needs live AGDF control files.");
|
|
469
571
|
}
|
|
470
572
|
if (target === "copilot" || target === "both") {
|
|
@@ -510,6 +612,222 @@ function tableRows(content) {
|
|
|
510
612
|
});
|
|
511
613
|
}
|
|
512
614
|
|
|
615
|
+
const compactBacklogHeaders = ["priority", "key", "work item", "status", "artefacts", "current spec", "next step"];
|
|
616
|
+
const legacyBacklogHeaders = ["prio", "key", "title", "status", "ur", "brownfield review", "prd", "sd", "tp", "qa", "or", "current spec", "notes"];
|
|
617
|
+
const backlogStatusLabels = new Map([
|
|
618
|
+
["needs ur", "needs_ur"],
|
|
619
|
+
["awaiting brownfield review", "awaiting_brownfield_review"],
|
|
620
|
+
["awaiting prd", "awaiting_prd"],
|
|
621
|
+
["awaiting prd approval", "awaiting_prd_approval"],
|
|
622
|
+
["awaiting sd", "awaiting_sd"],
|
|
623
|
+
["awaiting sd approval", "awaiting_sd_approval"],
|
|
624
|
+
["awaiting tp", "awaiting_tp"],
|
|
625
|
+
["awaiting tp approval", "awaiting_tp_approval"],
|
|
626
|
+
["in progress", "in_progress"],
|
|
627
|
+
["blocked", "blocked"],
|
|
628
|
+
["awaiting qa", "awaiting_qa"],
|
|
629
|
+
["awaiting uat", "awaiting_uat"],
|
|
630
|
+
["completed", "completed"],
|
|
631
|
+
["superseded", "superseded"],
|
|
632
|
+
["abandoned", "abandoned"],
|
|
633
|
+
]);
|
|
634
|
+
const backlogArtefactLabels = new Map([
|
|
635
|
+
["ur", "ur"],
|
|
636
|
+
["brownfield", "brownfield_review"],
|
|
637
|
+
["prd", "prd"],
|
|
638
|
+
["sd", "sd"],
|
|
639
|
+
["tp", "tp"],
|
|
640
|
+
["qa", "qa"],
|
|
641
|
+
["or", "or"],
|
|
642
|
+
]);
|
|
643
|
+
|
|
644
|
+
function normalizeBacklogHeader(value) {
|
|
645
|
+
return value.replace(/`/g, "").trim().toLowerCase();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function markdownLink(value) {
|
|
649
|
+
const match = value.trim().match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
650
|
+
return match ? { label: match[1].trim(), target: match[2].trim() } : null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function resolvedBacklogLinkTarget(target) {
|
|
654
|
+
if (!target
|
|
655
|
+
|| target.startsWith("/")
|
|
656
|
+
|| target.includes("\\")
|
|
657
|
+
|| /^[a-z][a-z0-9+.-]*:/i.test(target)) {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
const resolved = posix.normalize(posix.join(".agdf/control", target));
|
|
661
|
+
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
662
|
+
return resolved;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function normalizedBacklogLinkTarget(value, findings, backlogPath, label) {
|
|
666
|
+
const link = markdownLink(value);
|
|
667
|
+
if (!link) return value.trim();
|
|
668
|
+
const resolved = resolvedBacklogLinkTarget(link.target);
|
|
669
|
+
if (!resolved) {
|
|
670
|
+
if (findings) {
|
|
671
|
+
addFinding(
|
|
672
|
+
findings,
|
|
673
|
+
"revise",
|
|
674
|
+
"AGDF_BACKLOG_LINK_TARGET_INVALID",
|
|
675
|
+
`Backlog ${label} link must be relative to MASTER_BACKLOG.md: ${link.target || "<empty>"}.`,
|
|
676
|
+
backlogPath,
|
|
677
|
+
"Use a document-relative artefact target such as artefacts/<key>/UR.md.",
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
return link.target;
|
|
681
|
+
}
|
|
682
|
+
return resolved;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function normalizeBacklogStatus(value, findings, backlogPath) {
|
|
686
|
+
const cleaned = cleanStatusCell(value ?? "");
|
|
687
|
+
const lookup = cleaned.replaceAll("_", " ").toLowerCase();
|
|
688
|
+
const normalized = backlogStatusLabels.get(lookup);
|
|
689
|
+
if (normalized) return normalized;
|
|
690
|
+
if (/^[a-z][a-z0-9_]*$/.test(cleaned)) return cleaned;
|
|
691
|
+
if (findings && filled(cleaned)) {
|
|
692
|
+
addFinding(
|
|
693
|
+
findings,
|
|
694
|
+
"revise",
|
|
695
|
+
"AGDF_BACKLOG_STATUS_UNKNOWN",
|
|
696
|
+
`MASTER_BACKLOG.md uses an unknown human status label: ${cleaned}.`,
|
|
697
|
+
backlogPath,
|
|
698
|
+
"Use a documented human status label or a supported legacy snake_case value.",
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return cleaned;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function emptyBacklogPointer() {
|
|
705
|
+
return {
|
|
706
|
+
prio: "",
|
|
707
|
+
key: "",
|
|
708
|
+
title: "",
|
|
709
|
+
status: "",
|
|
710
|
+
ur: "",
|
|
711
|
+
brownfield_review: "",
|
|
712
|
+
prd: "",
|
|
713
|
+
sd: "",
|
|
714
|
+
tp: "",
|
|
715
|
+
qa: "",
|
|
716
|
+
or: "",
|
|
717
|
+
current_spec: "",
|
|
718
|
+
notes: "",
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function parseCompactArtefacts(value, pointer, findings, backlogPath) {
|
|
723
|
+
if (!filled(value)) return;
|
|
724
|
+
const seen = new Set();
|
|
725
|
+
for (const entry of value.split(/\s+·\s+/)) {
|
|
726
|
+
const link = markdownLink(entry);
|
|
727
|
+
if (!link) {
|
|
728
|
+
if (findings) {
|
|
729
|
+
addFinding(
|
|
730
|
+
findings,
|
|
731
|
+
"revise",
|
|
732
|
+
"AGDF_BACKLOG_ARTEFACT_LINK_INVALID",
|
|
733
|
+
`Compact Artefacts entry is not a Markdown link: ${entry}.`,
|
|
734
|
+
backlogPath,
|
|
735
|
+
"Use [UR](artefacts/<key>/UR.md) style links separated by a middle dot.",
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
const field = backlogArtefactLabels.get(link.label.toLowerCase());
|
|
741
|
+
if (!field) {
|
|
742
|
+
if (findings) {
|
|
743
|
+
addFinding(
|
|
744
|
+
findings,
|
|
745
|
+
"revise",
|
|
746
|
+
"AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN",
|
|
747
|
+
`Compact Artefacts uses an unknown label: ${link.label}.`,
|
|
748
|
+
backlogPath,
|
|
749
|
+
"Use UR, Brownfield, PRD, SD, TP, QA or OR.",
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (seen.has(field)) {
|
|
755
|
+
if (findings) {
|
|
756
|
+
addFinding(
|
|
757
|
+
findings,
|
|
758
|
+
"revise",
|
|
759
|
+
"AGDF_BACKLOG_ARTEFACT_LABEL_DUPLICATE",
|
|
760
|
+
`Compact Artefacts repeats the ${link.label} link.`,
|
|
761
|
+
backlogPath,
|
|
762
|
+
"Keep exactly one link per artefact type.",
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
seen.add(field);
|
|
768
|
+
pointer[field] = normalizedBacklogLinkTarget(entry, findings, backlogPath, link.label);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function parseBacklogSection(section, findings = null, backlogPath = ".agdf/control/MASTER_BACKLOG.md") {
|
|
773
|
+
const rows = tableRows(section);
|
|
774
|
+
if (rows.length === 0) return [];
|
|
775
|
+
const headers = rows[0].map(normalizeBacklogHeader);
|
|
776
|
+
const layout = headers.join("|") === compactBacklogHeaders.join("|")
|
|
777
|
+
? "compact"
|
|
778
|
+
: headers.join("|") === legacyBacklogHeaders.join("|")
|
|
779
|
+
? "legacy"
|
|
780
|
+
: "unknown";
|
|
781
|
+
|
|
782
|
+
if (layout === "unknown") {
|
|
783
|
+
if (findings) {
|
|
784
|
+
addFinding(
|
|
785
|
+
findings,
|
|
786
|
+
"revise",
|
|
787
|
+
"AGDF_BACKLOG_LAYOUT_UNKNOWN",
|
|
788
|
+
`MASTER_BACKLOG.md uses an unsupported table layout: ${headers.join(", ")}.`,
|
|
789
|
+
backlogPath,
|
|
790
|
+
"Use the canonical compact layout or the supported legacy 13-column layout.",
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return rows.slice(1)
|
|
797
|
+
.filter((cells) => cells.some((cell) => filled(cell)))
|
|
798
|
+
.map((cells) => {
|
|
799
|
+
const pointer = emptyBacklogPointer();
|
|
800
|
+
if (layout === "legacy") {
|
|
801
|
+
[
|
|
802
|
+
pointer.prio,
|
|
803
|
+
pointer.key,
|
|
804
|
+
pointer.title,
|
|
805
|
+
pointer.status,
|
|
806
|
+
pointer.ur,
|
|
807
|
+
pointer.brownfield_review,
|
|
808
|
+
pointer.prd,
|
|
809
|
+
pointer.sd,
|
|
810
|
+
pointer.tp,
|
|
811
|
+
pointer.qa,
|
|
812
|
+
pointer.or,
|
|
813
|
+
pointer.current_spec,
|
|
814
|
+
pointer.notes,
|
|
815
|
+
] = cells.map((cell) => cell ?? "");
|
|
816
|
+
pointer.status = normalizeBacklogStatus(pointer.status, findings, backlogPath);
|
|
817
|
+
return pointer;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
pointer.prio = cells[0] ?? "";
|
|
821
|
+
pointer.key = cleanStatusCell(cells[1] ?? "");
|
|
822
|
+
pointer.title = cells[2] ?? "";
|
|
823
|
+
pointer.status = normalizeBacklogStatus(cells[3], findings, backlogPath);
|
|
824
|
+
parseCompactArtefacts(cells[4] ?? "", pointer, findings, backlogPath);
|
|
825
|
+
pointer.current_spec = normalizedBacklogLinkTarget(cells[5] ?? "", findings, backlogPath, "Current spec");
|
|
826
|
+
pointer.notes = cells[6] ?? "";
|
|
827
|
+
return pointer;
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
513
831
|
function hasFilledTableRow(content, firstCellPattern) {
|
|
514
832
|
return tableRows(content)
|
|
515
833
|
.filter((cells) => firstCellPattern.test(cells[0] ?? ""))
|
|
@@ -619,7 +937,12 @@ function evaluateDoctor(targetDir) {
|
|
|
619
937
|
|
|
620
938
|
const backlogPath = join(".agdf", "control", "MASTER_BACKLOG.md");
|
|
621
939
|
const backlog = readTargetFile(targetDir, backlogPath);
|
|
622
|
-
|
|
940
|
+
parseBacklogSection(markdownSection(backlog, "Active Backlog"), findings, backlogPath);
|
|
941
|
+
parseBacklogSection(markdownSection(backlog, "Planned / Parking Lot"), findings, backlogPath);
|
|
942
|
+
const completedRows = tableRows(markdownSection(backlog, "Completed / Superseded Pointers"))
|
|
943
|
+
.slice(1)
|
|
944
|
+
.filter((cells) => cells.some((cell) => filled(cell)));
|
|
945
|
+
if (!hasFilledTableRow(backlog, /^P[0-9]/) && completedRows.length === 0) {
|
|
623
946
|
addFinding(
|
|
624
947
|
findings,
|
|
625
948
|
"warn",
|
|
@@ -747,12 +1070,18 @@ function cleanStatusCell(value) {
|
|
|
747
1070
|
return value.replace(/^`|`$/g, "").trim();
|
|
748
1071
|
}
|
|
749
1072
|
|
|
1073
|
+
function normalizeGateStatus(gate, status) {
|
|
1074
|
+
const normalized = cleanStatusCell(status ?? "");
|
|
1075
|
+
if (gate === "QA" && normalized === "passed") return "approved";
|
|
1076
|
+
return normalized;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
750
1079
|
function addApprovalRows(approvals, section) {
|
|
751
1080
|
for (const cells of tableRows(section)) {
|
|
752
1081
|
const [gate, status, evidence] = cells;
|
|
753
1082
|
if (!userGateOrder.includes(gate)) continue;
|
|
754
1083
|
approvals.set(gate, {
|
|
755
|
-
status:
|
|
1084
|
+
status: normalizeGateStatus(gate, status),
|
|
756
1085
|
evidence: evidence ?? "",
|
|
757
1086
|
});
|
|
758
1087
|
}
|
|
@@ -774,6 +1103,9 @@ function readRunState(targetDir) {
|
|
|
774
1103
|
missing_evidence: [],
|
|
775
1104
|
risks: [],
|
|
776
1105
|
context_graph: {},
|
|
1106
|
+
quality_outlook: "",
|
|
1107
|
+
source_scope: {},
|
|
1108
|
+
memory: {},
|
|
777
1109
|
};
|
|
778
1110
|
}
|
|
779
1111
|
|
|
@@ -846,6 +1178,23 @@ function readRunState(targetDir) {
|
|
|
846
1178
|
evidence: extractSectionField(modeSliceSection, "evidence"),
|
|
847
1179
|
};
|
|
848
1180
|
|
|
1181
|
+
const sourceScopeSection = markdownSection(content, "Source And Scope State");
|
|
1182
|
+
const sourceScope = {
|
|
1183
|
+
normative_instruction_source: extractSectionField(sourceScopeSection, "normative_instruction_source"),
|
|
1184
|
+
multi_scope_state: cleanStatusCell(extractSectionField(sourceScopeSection, "multi_scope_state")),
|
|
1185
|
+
active_scope_evidence: extractSectionField(sourceScopeSection, "active_scope_evidence"),
|
|
1186
|
+
competing_scope_lines: extractSectionField(sourceScopeSection, "competing_scope_lines"),
|
|
1187
|
+
branch_workspace_evidence: extractSectionField(sourceScopeSection, "branch_workspace_evidence"),
|
|
1188
|
+
branch_workspace_scope_effect: cleanStatusCell(extractSectionField(sourceScopeSection, "branch_workspace_scope_effect")),
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
const memorySection = markdownSection(content, "Knowledge Persistence Decision");
|
|
1192
|
+
const memory = {
|
|
1193
|
+
target: cleanStatusCell(extractSectionField(memorySection, "memory_target")),
|
|
1194
|
+
reason: extractSectionField(memorySection, "memory_reason"),
|
|
1195
|
+
refs: extractSectionField(memorySection, "memory_refs"),
|
|
1196
|
+
};
|
|
1197
|
+
|
|
849
1198
|
return {
|
|
850
1199
|
path: runPath,
|
|
851
1200
|
content,
|
|
@@ -859,6 +1208,9 @@ function readRunState(targetDir) {
|
|
|
859
1208
|
missing_evidence: missingEvidence,
|
|
860
1209
|
risks,
|
|
861
1210
|
context_graph: contextGraph,
|
|
1211
|
+
quality_outlook: extractField(content, "quality_outlook"),
|
|
1212
|
+
source_scope: sourceScope,
|
|
1213
|
+
memory,
|
|
862
1214
|
};
|
|
863
1215
|
}
|
|
864
1216
|
|
|
@@ -890,7 +1242,7 @@ function isDurableGateArtefactSatisfied(runState, gate) {
|
|
|
890
1242
|
if (!durableGateArtefacts.has(gate)) return true;
|
|
891
1243
|
const artefact = gateArtefactStatus(runState, gate);
|
|
892
1244
|
if (!artefact.path || isPlaceholderValue(artefact.path)) return false;
|
|
893
|
-
if (gate === "QA") return artefact.status === "pass";
|
|
1245
|
+
if (gate === "QA") return artefact.status === "pass" || artefact.status === "passed";
|
|
894
1246
|
return artefact.status === "approved";
|
|
895
1247
|
}
|
|
896
1248
|
|
|
@@ -997,15 +1349,97 @@ function analyzeDeliveryMap(runState) {
|
|
|
997
1349
|
});
|
|
998
1350
|
}
|
|
999
1351
|
|
|
1352
|
+
const multiScopeState = runState.source_scope?.multi_scope_state;
|
|
1353
|
+
if (multiScopeState === "ambiguous" || multiScopeState === "blocked") {
|
|
1354
|
+
findings.push({
|
|
1355
|
+
severity: multiScopeState === "blocked" ? "block" : "revise",
|
|
1356
|
+
code: "AGDF_SCOPE_AMBIGUOUS",
|
|
1357
|
+
message: "Multiple active scope lines are plausible; the agent must not choose one silently.",
|
|
1358
|
+
path: runState.path,
|
|
1359
|
+
next_step: "List competing scope lines with gate and artefact evidence, then clarify the active scope.",
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const branchEffect = runState.source_scope?.branch_workspace_scope_effect;
|
|
1364
|
+
if (branchEffect === "conflicts" || branchEffect === "insufficient") {
|
|
1365
|
+
findings.push({
|
|
1366
|
+
severity: branchEffect === "conflicts" ? "revise" : "warn",
|
|
1367
|
+
code: "AGDF_BRANCH_NOT_SCOPE_PROOF",
|
|
1368
|
+
message: "Branch or workspace evidence is not sufficient scope proof.",
|
|
1369
|
+
path: runState.path,
|
|
1370
|
+
next_step: "Confirm the active scope from durable artefacts or record why branch/workspace evidence is only supporting evidence.",
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (runState.memory?.target && runState.memory.target !== "none" && !filled(runState.memory.reason)) {
|
|
1375
|
+
findings.push({
|
|
1376
|
+
severity: "warn",
|
|
1377
|
+
code: "AGDF_MEMORY_TARGET_REASON_MISSING",
|
|
1378
|
+
message: "Knowledge persistence target is set without a reason.",
|
|
1379
|
+
path: runState.path,
|
|
1380
|
+
next_step: "Fill memory_reason or set memory_target to none.",
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1000
1384
|
return {
|
|
1001
1385
|
relationships,
|
|
1002
1386
|
missing_evidence: runState.missing_evidence,
|
|
1003
1387
|
risks: runState.risks,
|
|
1004
1388
|
context_graph: runState.context_graph,
|
|
1389
|
+
source_scope: runState.source_scope,
|
|
1390
|
+
memory: runState.memory,
|
|
1005
1391
|
findings,
|
|
1006
1392
|
};
|
|
1007
1393
|
}
|
|
1008
1394
|
|
|
1395
|
+
function deriveQualityOutlook(runState, findings = []) {
|
|
1396
|
+
if (filled(runState.quality_outlook)) return runState.quality_outlook;
|
|
1397
|
+
if (findings.some((finding) => finding.severity === "block" || finding.severity === "revise")) {
|
|
1398
|
+
return "Resolve blocking or revise-level delivery-map findings before making stronger quality claims.";
|
|
1399
|
+
}
|
|
1400
|
+
if (findings.some((finding) => finding.severity === "warn")) {
|
|
1401
|
+
return "Review warning-level findings when investing further in delivery confidence.";
|
|
1402
|
+
}
|
|
1403
|
+
return "No additional quality follow-up identified from the current control state.";
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function buildStatusCard({
|
|
1407
|
+
status,
|
|
1408
|
+
currentGate,
|
|
1409
|
+
allowed = [],
|
|
1410
|
+
forbidden = [],
|
|
1411
|
+
blockingReason = "none",
|
|
1412
|
+
missingApproval = "none",
|
|
1413
|
+
nextAllowedAction,
|
|
1414
|
+
runState,
|
|
1415
|
+
findings = [],
|
|
1416
|
+
}) {
|
|
1417
|
+
const qualityOutlook = deriveQualityOutlook(runState, findings);
|
|
1418
|
+
return {
|
|
1419
|
+
mode: extractField(runState.content ?? "", "mode") || "unknown",
|
|
1420
|
+
status,
|
|
1421
|
+
current_gate: currentGate,
|
|
1422
|
+
mode_slice_decision: runState.mode_slice_decision?.decision || "undecided",
|
|
1423
|
+
allowed_now: allowed,
|
|
1424
|
+
forbidden_now: forbidden,
|
|
1425
|
+
blocking_condition: blockingReason || "none",
|
|
1426
|
+
missing_approval: missingApproval || "none",
|
|
1427
|
+
evidence: runState.evidence_refs,
|
|
1428
|
+
next_skill: nextSkillByGate[currentGate] ?? "gate-check",
|
|
1429
|
+
next_step: nextAllowedAction,
|
|
1430
|
+
quality_outlook: qualityOutlook,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function effectiveCurrentGate(runState, transitionDecision) {
|
|
1435
|
+
if (transitionDecision.status !== "open") return transitionDecision.current_gate;
|
|
1436
|
+
const explicitGate = normalizeCurrentGate(runState.current_gate, "");
|
|
1437
|
+
const explicitIndex = gateProgressOrder.indexOf(explicitGate);
|
|
1438
|
+
const fallbackIndex = gateProgressOrder.indexOf(transitionDecision.current_gate);
|
|
1439
|
+
if (explicitIndex === -1 || fallbackIndex === -1) return transitionDecision.current_gate;
|
|
1440
|
+
return explicitIndex > fallbackIndex ? explicitGate : transitionDecision.current_gate;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1009
1443
|
function durableArtefactBlock(gate, nextGate) {
|
|
1010
1444
|
const label = gate === "QA" ? "QA report" : `${gate} artefact`;
|
|
1011
1445
|
const stablePath = gate === "QA" ? ".agdf/control/artefacts/<key>/QA_REPORT.md" : `.agdf/control/artefacts/<key>/${gate}.md`;
|
|
@@ -1030,6 +1464,7 @@ function isGateSatisfied(runState, gate) {
|
|
|
1030
1464
|
const status = gateApprovalStatus(runState, gate);
|
|
1031
1465
|
if (status === "not_applicable") return true;
|
|
1032
1466
|
if (status !== "approved") return false;
|
|
1467
|
+
if (gate === "UAT") return true;
|
|
1033
1468
|
return isDurableGateArtefactSatisfied(runState, gate);
|
|
1034
1469
|
}
|
|
1035
1470
|
|
|
@@ -1151,6 +1586,30 @@ function transitionDecisionForRunState(runState) {
|
|
|
1151
1586
|
|
|
1152
1587
|
if (gateApprovalStatus(runState, "QA") === "approved" && !isGateSatisfied(runState, "QA")) return durableArtefactBlock("QA", "UAT");
|
|
1153
1588
|
|
|
1589
|
+
if (isGateSatisfied(runState, "QA") && !isGateSatisfied(runState, "UAT")) {
|
|
1590
|
+
return {
|
|
1591
|
+
status: "open",
|
|
1592
|
+
current_gate: "UAT",
|
|
1593
|
+
blocking_reason: "none",
|
|
1594
|
+
missing_approval: "Approval: UAT",
|
|
1595
|
+
allowed: ["request exact UAT approval", "prepare non-operative delivery summary"],
|
|
1596
|
+
forbidden: ["release", "push", "open PR", "commit without explicit user instruction and required approval"],
|
|
1597
|
+
next_allowed_action: "Request exact approval: Approval: UAT before delivery handoff.",
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
if (isGateSatisfied(runState, "UAT")) {
|
|
1602
|
+
return {
|
|
1603
|
+
status: "open",
|
|
1604
|
+
current_gate: "OR",
|
|
1605
|
+
blocking_reason: "none",
|
|
1606
|
+
missing_approval: "none",
|
|
1607
|
+
allowed: ["produce OR or delivery closeout", "prepare commit, push or PR handoff when requested"],
|
|
1608
|
+
forbidden: ["commit, push, open PR or release automatically"],
|
|
1609
|
+
next_allowed_action: "Produce delivery closeout or requested handoff; do not perform VCS actions automatically.",
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1154
1613
|
return {
|
|
1155
1614
|
status: "open",
|
|
1156
1615
|
current_gate: "Brownfield Analysis",
|
|
@@ -1171,7 +1630,7 @@ function evaluateGateCheck(targetDir) {
|
|
|
1171
1630
|
const doctorRevise = doctorReport.findings.find((finding) => finding.severity === "revise");
|
|
1172
1631
|
|
|
1173
1632
|
let status = transitionDecision.status;
|
|
1174
|
-
let currentGate = transitionDecision
|
|
1633
|
+
let currentGate = effectiveCurrentGate(runState, transitionDecision);
|
|
1175
1634
|
let blockingReason = transitionDecision.blocking_reason;
|
|
1176
1635
|
let missingApproval = transitionDecision.missing_approval;
|
|
1177
1636
|
let allowed = transitionDecision.allowed;
|
|
@@ -1220,10 +1679,24 @@ function evaluateGateCheck(targetDir) {
|
|
|
1220
1679
|
doctor_status: doctorReport.status,
|
|
1221
1680
|
doctor_summary: doctorReport.summary,
|
|
1222
1681
|
evidence_refs: runState.evidence_refs,
|
|
1682
|
+
quality_outlook: deriveQualityOutlook(runState, deliveryMap.findings),
|
|
1683
|
+
status_card: buildStatusCard({
|
|
1684
|
+
status,
|
|
1685
|
+
currentGate,
|
|
1686
|
+
allowed,
|
|
1687
|
+
forbidden,
|
|
1688
|
+
blockingReason,
|
|
1689
|
+
missingApproval,
|
|
1690
|
+
nextAllowedAction,
|
|
1691
|
+
runState,
|
|
1692
|
+
findings: deliveryMap.findings,
|
|
1693
|
+
}),
|
|
1223
1694
|
delivery_map: {
|
|
1224
1695
|
relationships: deliveryMap.relationships,
|
|
1225
1696
|
mode_slice_decision: runState.mode_slice_decision,
|
|
1226
1697
|
context_graph: deliveryMap.context_graph,
|
|
1698
|
+
source_scope: deliveryMap.source_scope,
|
|
1699
|
+
memory: deliveryMap.memory,
|
|
1227
1700
|
findings: deliveryMap.findings,
|
|
1228
1701
|
},
|
|
1229
1702
|
doctor_report: doctorReport,
|
|
@@ -1240,6 +1713,7 @@ function printGateCheckReport(report, json) {
|
|
|
1240
1713
|
console.log(`Current gate: ${report.current_gate}`);
|
|
1241
1714
|
console.log(`Blocking reason: ${report.blocking_reason}`);
|
|
1242
1715
|
console.log(`Missing approval: ${report.missing_approval}`);
|
|
1716
|
+
console.log(`Quality outlook: ${report.quality_outlook}`);
|
|
1243
1717
|
console.log(`Doctor: ${report.doctor_status} (${report.doctor_summary.findings} findings)`);
|
|
1244
1718
|
console.log("");
|
|
1245
1719
|
console.log("Allowed:");
|
|
@@ -1257,22 +1731,7 @@ function readBacklogPointers(targetDir) {
|
|
|
1257
1731
|
|
|
1258
1732
|
const backlog = readTargetFile(targetDir, backlogPath);
|
|
1259
1733
|
const activeSection = markdownSection(backlog, "Active Backlog");
|
|
1260
|
-
return
|
|
1261
|
-
.map((cells) => ({
|
|
1262
|
-
prio: cells[0] ?? "",
|
|
1263
|
-
key: cells[1] ?? "",
|
|
1264
|
-
title: cells[2] ?? "",
|
|
1265
|
-
status: cleanStatusCell(cells[3] ?? ""),
|
|
1266
|
-
ur: cells[4] ?? "",
|
|
1267
|
-
brownfield_review: cells[5] ?? "",
|
|
1268
|
-
prd: cells[6] ?? "",
|
|
1269
|
-
sd: cells[7] ?? "",
|
|
1270
|
-
tp: cells[8] ?? "",
|
|
1271
|
-
qa: cells[9] ?? "",
|
|
1272
|
-
or: cells[10] ?? "",
|
|
1273
|
-
current_spec: cells[11] ?? "",
|
|
1274
|
-
notes: cells[12] ?? "",
|
|
1275
|
-
}));
|
|
1734
|
+
return parseBacklogSection(activeSection);
|
|
1276
1735
|
}
|
|
1277
1736
|
|
|
1278
1737
|
function evaluateDeliveryMap(targetDir) {
|
|
@@ -1280,6 +1739,7 @@ function evaluateDeliveryMap(targetDir) {
|
|
|
1280
1739
|
const runState = readRunState(targetDir);
|
|
1281
1740
|
const map = analyzeDeliveryMap(runState);
|
|
1282
1741
|
const gateDecision = transitionDecisionForRunState(runState);
|
|
1742
|
+
const currentGate = effectiveCurrentGate(runState, gateDecision);
|
|
1283
1743
|
|
|
1284
1744
|
const severityRank = { block: 3, revise: 2, warn: 1 };
|
|
1285
1745
|
const deliverySeverity = map.findings.reduce((max, finding) => Math.max(max, severityRank[finding.severity] ?? 0), 0);
|
|
@@ -1287,13 +1747,28 @@ function evaluateDeliveryMap(targetDir) {
|
|
|
1287
1747
|
const maxSeverity = Math.max(deliverySeverity, doctorSeverity);
|
|
1288
1748
|
const status = maxSeverity >= 3 ? "block" : maxSeverity === 2 ? "revise" : maxSeverity === 1 ? "warn" : "pass";
|
|
1289
1749
|
|
|
1750
|
+
const qualityOutlook = deriveQualityOutlook(runState, map.findings);
|
|
1751
|
+
const nextAllowedAction = isPlaceholderValue(runState.next_allowed_action) ? gateDecision.next_allowed_action : runState.next_allowed_action;
|
|
1752
|
+
|
|
1290
1753
|
return {
|
|
1291
1754
|
schema_version: "1",
|
|
1292
1755
|
status,
|
|
1293
1756
|
checked_at: new Date().toISOString(),
|
|
1294
1757
|
target_dir: targetDir,
|
|
1295
|
-
current_gate:
|
|
1296
|
-
next_allowed_action:
|
|
1758
|
+
current_gate: currentGate,
|
|
1759
|
+
next_allowed_action: nextAllowedAction,
|
|
1760
|
+
quality_outlook: qualityOutlook,
|
|
1761
|
+
status_card: buildStatusCard({
|
|
1762
|
+
status,
|
|
1763
|
+
currentGate,
|
|
1764
|
+
allowed: gateDecision.allowed,
|
|
1765
|
+
forbidden: gateDecision.forbidden,
|
|
1766
|
+
blockingReason: gateDecision.blocking_reason,
|
|
1767
|
+
missingApproval: gateDecision.missing_approval,
|
|
1768
|
+
nextAllowedAction,
|
|
1769
|
+
runState,
|
|
1770
|
+
findings: map.findings,
|
|
1771
|
+
}),
|
|
1297
1772
|
backlog_pointers: readBacklogPointers(targetDir),
|
|
1298
1773
|
artefacts: Object.fromEntries([...runState.artefacts.entries()]),
|
|
1299
1774
|
approvals: Object.fromEntries([...runState.approvals.entries()]),
|
|
@@ -1303,6 +1778,8 @@ function evaluateDeliveryMap(targetDir) {
|
|
|
1303
1778
|
missing_evidence: map.missing_evidence,
|
|
1304
1779
|
risks: map.risks,
|
|
1305
1780
|
context_graph: map.context_graph,
|
|
1781
|
+
source_scope: map.source_scope,
|
|
1782
|
+
memory: map.memory,
|
|
1306
1783
|
findings: map.findings,
|
|
1307
1784
|
doctor_status: doctorReport.status,
|
|
1308
1785
|
doctor_summary: doctorReport.summary,
|
|
@@ -1319,6 +1796,7 @@ function printDeliveryMapReport(report, json) {
|
|
|
1319
1796
|
console.log(`AGDF delivery-map: ${report.status}`);
|
|
1320
1797
|
console.log(`Current gate: ${report.current_gate}`);
|
|
1321
1798
|
console.log(`Next allowed action: ${report.next_allowed_action}`);
|
|
1799
|
+
console.log(`Quality outlook: ${report.quality_outlook}`);
|
|
1322
1800
|
console.log(`Relationships: ${report.relationships.filter((item) => item.status === "pass").length}/${report.relationships.length} evidenced`);
|
|
1323
1801
|
console.log(`Findings: ${report.findings.length}`);
|
|
1324
1802
|
|
|
@@ -1351,6 +1829,42 @@ function main() {
|
|
|
1351
1829
|
process.exit(report.status === "block" ? 2 : 0);
|
|
1352
1830
|
}
|
|
1353
1831
|
|
|
1832
|
+
if (options.target === "codex") {
|
|
1833
|
+
try {
|
|
1834
|
+
installCodexGlobalPlugin();
|
|
1835
|
+
console.log("AGDF Codex plugin installed globally.");
|
|
1836
|
+
console.log("Run npx --yes @agdf/cli@latest codex-repo in a repository when you want to test AGDF from repository-local plugin files.");
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
console.error(error.message);
|
|
1839
|
+
process.exit(1);
|
|
1840
|
+
}
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (options.target === "claude") {
|
|
1845
|
+
try {
|
|
1846
|
+
installClaudeGlobalPlugin();
|
|
1847
|
+
console.log("AGDF Claude Code plugin installed globally.");
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
console.error(error.message);
|
|
1850
|
+
process.exit(1);
|
|
1851
|
+
}
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
if (options.target === "opencode") {
|
|
1856
|
+
const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
|
|
1857
|
+
try {
|
|
1858
|
+
const result = installOpenCodeGlobalPlugin(configDir);
|
|
1859
|
+
console.log(`AGDF OpenCode global plugin ${result.added ? "installed" : "already present"}: ${result.configPath}`);
|
|
1860
|
+
console.log("Run npx --yes @agdf/cli@latest opencode-repo in each repository where AGDF governance should be active and reviewable.");
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
console.error(error.message);
|
|
1863
|
+
process.exit(1);
|
|
1864
|
+
}
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1354
1868
|
const files = generatedFilesForTarget(options.target, options.dir, options.force, options.language);
|
|
1355
1869
|
const wroteAgentsFragment = files.some(file => file.path === agdfFragmentPath);
|
|
1356
1870
|
const wroteOpenCodeConfigFragment = files.some(file => file.path === openCodeConfigFragmentPath);
|