create-agdf 0.14.1 → 0.14.3
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 +13 -3
- package/generated/.opencode/contracts/control-scaffold.md +17 -2
- package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +6 -1
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/plugins/agdf/meta/copilot-payload-baseline.json +7 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +6 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +51 -19
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +37 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +14 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +126 -36
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +40 -92
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +29 -17
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +50 -1
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -1
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +7 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +771 -0
- package/generated/plugins/copilot/agdf/LICENSE +201 -0
- package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-delivery-path-search/SKILL.md +16 -4
- package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/control-scaffold.md +17 -2
- package/generated/plugins/copilot/agdf/meta/agdf-agent-router.md +129 -0
- package/generated/plugins/copilot/agdf/meta/agdf-constitution.md +329 -0
- package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +331 -0
- package/generated/plugins/copilot/agdf/meta/agdf-runtime-contract.md +16 -0
- package/generated/plugins/{agdf → copilot/agdf}/plugin.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/agdf-local.js +18 -0
- package/generated/plugins/copilot/agdf/runtime/agdf-session-check.js +97 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/NOTICE +10 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +512 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +331 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +144 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +171 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +173 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +46 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +301 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +272 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +455 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +305 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/parent-reconciliation.js +284 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +345 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +265 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +274 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +35 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +44 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +51 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +245 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +109 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +27 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +842 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/local-validator.js +239 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +255 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +73 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +6 -0
- package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +8 -0
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +6 -1
- package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +17 -2
- package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +7 -0
- package/generated/submissions/openai/agdf/skills/delivery-path-search/SKILL.md +16 -4
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +15 -11
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +7 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/cli/delivery-path-search-command.js +51 -19
- package/lib/delivery-path-search/contracts.js +37 -0
- package/lib/delivery-path-search/persistence.js +14 -2
- package/lib/delivery-path-search/search-engine.js +126 -36
- package/lib/delivery-path-search/state-adapter.js +40 -92
- package/lib/installers/local-marketplace.js +112 -30
- package/lib/installers/plugin-installers.js +45 -9
- package/lib/public-plugin/copilot-profile.js +126 -0
- package/lib/release/version-coherence.js +4 -1
- package/lib/runtime/local-validator.js +29 -17
- package/lib/runtime/plugin-provenance.js +50 -1
- package/package.json +3 -2
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-brownfield-analysis/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-clean-implementation-review/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-code-review/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-delivery-closeout/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-gate-check/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-interaction-locales.json +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-qa-gate/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-release-or/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-runtime-contract.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-task-plan-review/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/agdf-ux-intent-definition/SKILL.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/closeout.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/context-graph.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/gate-transition.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/interaction.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/modes.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/quality.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/copilot-skills/contracts/task-target-resolution.md +0 -0
- /package/generated/plugins/{agdf → copilot/agdf}/hooks/copilot-hooks.json +0 -0
package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { parseControlState, resolveRuns } from "../control-state/index.js";
|
|
5
|
+
import { buildRunCandidates } from "../interaction-presentation.js";
|
|
6
|
+
import { cleanStatusCell, filled, isPlaceholderValue, readTargetFile } from "./shared.js";
|
|
7
|
+
import { extractField, isSafeRepoRelativePath, readVerifiedChangeRecord } from "./verified-change.js";
|
|
8
|
+
|
|
9
|
+
export const userGateOrder = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
10
|
+
export const durableGateArtefacts = new Set(["UR", "PRD", "SD", "TP", "QA"]);
|
|
11
|
+
export const internalStepArtefacts = new Set(["Brownfield Review", "Verified Change", "Brownfield Analysis", "CD+Tests", "TP Review", "Clean Implementation Review", "Clean Review", "CR", "Code Review"]);
|
|
12
|
+
export const closeoutArtefacts = new Set(["OR"]);
|
|
13
|
+
|
|
14
|
+
export function resolvedArtefactFile(targetDir, rawPath) {
|
|
15
|
+
const normalizedPath = String(rawPath ?? "").replace(/^`|`$/g, "").trim();
|
|
16
|
+
if (!normalizedPath || isAbsolute(normalizedPath) || normalizedPath.includes("<") || normalizedPath.includes(">")) return "";
|
|
17
|
+
const absolutePath = resolve(targetDir, normalizedPath);
|
|
18
|
+
if (relative(targetDir, absolutePath).startsWith("..") || !existsSync(absolutePath)) return "";
|
|
19
|
+
try {
|
|
20
|
+
const realPath = realpathSync(absolutePath);
|
|
21
|
+
return relative(realpathSync(targetDir), realPath).startsWith("..") || !statSync(realPath).isFile() ? "" : realPath;
|
|
22
|
+
} catch {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readArtefactHeading(targetDir, artefact) {
|
|
28
|
+
const artefactPath = resolvedArtefactFile(targetDir, artefact?.path);
|
|
29
|
+
if (!artefactPath) return "";
|
|
30
|
+
try {
|
|
31
|
+
return readFileSync(artefactPath, "utf8").match(/^#\s+(.+)$/m)?.[1]?.trim() ?? "";
|
|
32
|
+
} catch {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readRunState(targetDir, selection = {}) {
|
|
38
|
+
let runPath = join(".agdf", "control", "AGDF_RUN.md");
|
|
39
|
+
const canonicalRoot = join(targetDir, ".agdf", "control", "runs");
|
|
40
|
+
let resolutionError;
|
|
41
|
+
let candidateRuns = [];
|
|
42
|
+
if (existsSync(canonicalRoot)) {
|
|
43
|
+
try {
|
|
44
|
+
const selected = resolveRuns(targetDir, {
|
|
45
|
+
runIdArg: selection.runId,
|
|
46
|
+
runIdEnv: process.env.AGDF_RUN_ID,
|
|
47
|
+
});
|
|
48
|
+
runPath = selected.run.path.startsWith(targetDir)
|
|
49
|
+
? selected.run.path.slice(targetDir.length + 1)
|
|
50
|
+
: selected.run.path;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
resolutionError = error.message;
|
|
53
|
+
if (resolutionError.startsWith("AGDF_ACTIVE_RUN_AMBIGUOUS")) {
|
|
54
|
+
const active = resolveRuns(targetDir, { allActive: true }).runs.map((run) => {
|
|
55
|
+
const controlState = parseControlState(run.content, {
|
|
56
|
+
userGates: userGateOrder,
|
|
57
|
+
internalSteps: [...internalStepArtefacts],
|
|
58
|
+
closeoutArtefacts: [...closeoutArtefacts],
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
...run,
|
|
62
|
+
control_state: controlState,
|
|
63
|
+
current_artefact_heading: readArtefactHeading(targetDir, controlState.artefacts.get(controlState.current_gate)),
|
|
64
|
+
ur_heading: readArtefactHeading(targetDir, controlState.artefacts.get("UR")),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
candidateRuns = buildRunCandidates(active);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (resolutionError || !existsSync(join(targetDir, runPath))) {
|
|
72
|
+
return {
|
|
73
|
+
path: runPath,
|
|
74
|
+
content: "",
|
|
75
|
+
current_gate: "",
|
|
76
|
+
next_allowed_action: "",
|
|
77
|
+
approvals: new Map(),
|
|
78
|
+
artefacts: new Map(),
|
|
79
|
+
evidence_refs: [],
|
|
80
|
+
artefact_chain: [],
|
|
81
|
+
mode_slice_decision: {},
|
|
82
|
+
missing_evidence: [],
|
|
83
|
+
risks: [],
|
|
84
|
+
context_graph: {},
|
|
85
|
+
quality_outlook: "",
|
|
86
|
+
source_scope: {},
|
|
87
|
+
memory: {},
|
|
88
|
+
candidate_runs: candidateRuns,
|
|
89
|
+
resolution_error: resolutionError,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const content = readTargetFile(targetDir, runPath);
|
|
94
|
+
const parsed = parseControlState(content, {
|
|
95
|
+
userGates: userGateOrder,
|
|
96
|
+
internalSteps: [...internalStepArtefacts],
|
|
97
|
+
closeoutArtefacts: [...closeoutArtefacts],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
path: runPath,
|
|
102
|
+
content,
|
|
103
|
+
...parsed,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function gateApprovalStatus(runState, gate) {
|
|
108
|
+
if (!userGateOrder.includes(gate)) return "not_applicable";
|
|
109
|
+
return runState.approvals.get(gate)?.status ?? "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function gateArtefactStatus(runState, gate) {
|
|
113
|
+
if (!userGateOrder.includes(gate)) return { status: "not_applicable", path: "" };
|
|
114
|
+
return runState.artefacts.get(gate) ?? { status: "", path: "" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isDurableGateArtefactSatisfied(runState, gate) {
|
|
118
|
+
if (!durableGateArtefacts.has(gate)) return true;
|
|
119
|
+
const artefact = gateArtefactStatus(runState, gate);
|
|
120
|
+
if (!artefact.path || isPlaceholderValue(artefact.path)) return false;
|
|
121
|
+
if (gate === "QA") return artefact.status === "pass" || artefact.status === "passed";
|
|
122
|
+
return artefact.status === "approved";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function expectedDurableArtefactStatuses(gate) {
|
|
126
|
+
return gate === "QA" ? ["pass", "passed"] : ["approved"];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function describeDurableArtefactStatuses(gate) {
|
|
130
|
+
return expectedDurableArtefactStatuses(gate).map((status) => `\`${status}\``).join(" or ");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function analyzeDurableGateArtefactConsistency(runState) {
|
|
134
|
+
const findings = [];
|
|
135
|
+
|
|
136
|
+
for (const gate of durableGateArtefacts) {
|
|
137
|
+
if (gateApprovalStatus(runState, gate) !== "approved") continue;
|
|
138
|
+
|
|
139
|
+
const artefact = gateArtefactStatus(runState, gate);
|
|
140
|
+
if (!artefact.path || isPlaceholderValue(artefact.path) || !artefact.status) continue;
|
|
141
|
+
|
|
142
|
+
const expectedStatuses = expectedDurableArtefactStatuses(gate);
|
|
143
|
+
if (expectedStatuses.includes(artefact.status)) continue;
|
|
144
|
+
|
|
145
|
+
findings.push({
|
|
146
|
+
severity: "revise",
|
|
147
|
+
code: "AGDF_GATE_ARTEFACT_STATUS_INCONSISTENT",
|
|
148
|
+
message: `${gate} approval is recorded, but the durable artefact row uses status \`${artefact.status}\`; expected ${describeDurableArtefactStatuses(gate)}.`,
|
|
149
|
+
path: runState.path,
|
|
150
|
+
next_step: `Update the ${gate} artefact row in the selected RUN_STATE.md to use the gate-specific durable status vocabulary.`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return findings;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function analyzeArtefactRoleConsistency(targetDir, runState) {
|
|
158
|
+
const findings = [];
|
|
159
|
+
for (const [type, artefact] of runState.artefacts) {
|
|
160
|
+
if (artefact.path_format === "invalid") {
|
|
161
|
+
findings.push({
|
|
162
|
+
severity: "block",
|
|
163
|
+
code: "AGDF_ARTEFACT_PATH_FORMAT_INVALID",
|
|
164
|
+
message: `${type} uses an invalid artefact path cell (${artefact.path_reason}).`,
|
|
165
|
+
path: runState.path,
|
|
166
|
+
next_step: "Use a plain repository-relative path or one complete Markdown code span.",
|
|
167
|
+
});
|
|
168
|
+
} else if (artefact.path && !isSafeRepoRelativePath(artefact.path)) {
|
|
169
|
+
findings.push({
|
|
170
|
+
severity: "block",
|
|
171
|
+
code: "AGDF_ARTEFACT_PATH_INVALID",
|
|
172
|
+
message: `${type} must use a normalized repository-relative artefact path.`,
|
|
173
|
+
path: runState.path,
|
|
174
|
+
next_step: "Remove absolute, traversal, unsupported-backslash or otherwise non-normalized path text.",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const rolesByPath = new Map();
|
|
179
|
+
for (const [type, artefact] of runState.artefacts) {
|
|
180
|
+
if (!artefact.path) continue;
|
|
181
|
+
rolesByPath.set(artefact.path, [...(rolesByPath.get(artefact.path) ?? []), type]);
|
|
182
|
+
}
|
|
183
|
+
for (const [path, roles] of rolesByPath) {
|
|
184
|
+
if (roles.length < 2) continue;
|
|
185
|
+
const allowedRoles = new Set(["Brownfield Review", "Verified Change", "OR"]);
|
|
186
|
+
if (modeSliceDecision(runState) !== "verified_change" || roles.some((role) => !allowedRoles.has(role))) {
|
|
187
|
+
findings.push({ severity: "block", code: "AGDF_ARTEFACT_ROLE_ALIAS_INVALID", message: `Artefact path is reused across incompatible roles: ${roles.join(", ")}.`, path, next_step: "Use distinct artefacts or a lifecycle-consistent Verified Change compact record." });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const record = readVerifiedChangeRecord(targetDir, runState);
|
|
191
|
+
if (record.status !== "present") continue;
|
|
192
|
+
const recordStatus = cleanStatusCell(extractField(record.content, "status"));
|
|
193
|
+
const brownfieldComplete = extractField(record.content, "decision") === "verified_change" && filled(extractField(record.content, "scope_reason")) && filled(extractField(record.content, "evidence"));
|
|
194
|
+
const miniCloseoutComplete = ["delivered", "intentionally_not_delivered", "residual_risk", "next_step"].every((field) => filled(extractField(record.content, field)));
|
|
195
|
+
if (roles.includes("Brownfield Review") && (runState.artefacts.get("Brownfield Review")?.status !== "done" || !brownfieldComplete)) findings.push({ severity: "block", code: "AGDF_VERIFIED_CHANGE_BROWNFIELD_ROLE_INVALID", message: "Consolidated Brownfield Review role is incomplete.", path, next_step: "Complete Brownfield Selection before eligibility." });
|
|
196
|
+
if (roles.includes("Verified Change") && runState.artefacts.get("Verified Change")?.status !== recordStatus) findings.push({ severity: "block", code: "AGDF_VERIFIED_CHANGE_ROLE_STATUS_MISMATCH", message: "Verified Change artefact row status does not match the compact record.", path, next_step: "Align the run-state row and record status." });
|
|
197
|
+
if (roles.includes("OR") && runState.artefacts.get("OR")?.status === "done" && (recordStatus !== "executed" || !miniCloseoutComplete)) findings.push({ severity: "block", code: "AGDF_VERIFIED_CHANGE_OR_ROLE_INVALID", message: "Consolidated OR is complete before execution and Mini-Closeout evidence are complete.", path, next_step: "Complete execution and Mini-Closeout evidence before marking OR done." });
|
|
198
|
+
}
|
|
199
|
+
return findings;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function isInternalStepSatisfied(runState, step) {
|
|
203
|
+
const artefact = runState.artefacts.get(step);
|
|
204
|
+
if (!artefact) return false;
|
|
205
|
+
if (artefact.status === "done") return true;
|
|
206
|
+
return artefact.status === "not_applicable"
|
|
207
|
+
&& (step === "Brownfield Review" || step === "Brownfield Analysis");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function modeSliceDecision(runState) {
|
|
211
|
+
const decision = runState.mode_slice_decision?.decision ?? "";
|
|
212
|
+
const scopeReason = runState.mode_slice_decision?.scope_reason ?? "";
|
|
213
|
+
const evidence = runState.mode_slice_decision?.evidence ?? "";
|
|
214
|
+
if (!decision || isPlaceholderValue(decision) || !filled(scopeReason) || !filled(evidence)) return "undecided";
|
|
215
|
+
return decision;
|
|
216
|
+
}
|
|
217
|
+
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, posix } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function cleanStatusCell(value) {
|
|
5
|
+
return (value ?? "").replace(/\x60/g, "").trim();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function readTargetFile(targetDir, relativePath) {
|
|
9
|
+
return readFileSync(join(targetDir, relativePath), "utf8");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function addFinding(findings, severity, code, message, path, nextStep) {
|
|
13
|
+
findings.push({
|
|
14
|
+
severity,
|
|
15
|
+
code,
|
|
16
|
+
message,
|
|
17
|
+
path,
|
|
18
|
+
next_step: nextStep,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function nonEmptyTableRows(content) {
|
|
23
|
+
return content
|
|
24
|
+
.split("\n")
|
|
25
|
+
.filter((line) => line.trim().startsWith("|"))
|
|
26
|
+
.filter((line) => !line.includes("---"))
|
|
27
|
+
.map((line) => line.split("|").map((cell) => cell.trim()).filter(Boolean))
|
|
28
|
+
.filter((cells) => cells.some((cell) => cell && !cell.startsWith("`")));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function tableRows(content) {
|
|
32
|
+
return content
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter((line) => line.trim().startsWith("|"))
|
|
35
|
+
.filter((line) => !line.includes("---"))
|
|
36
|
+
.map((line) => {
|
|
37
|
+
const cells = line.split("|").map((cell) => cell.trim());
|
|
38
|
+
if (cells[0] === "") cells.shift();
|
|
39
|
+
if (cells.at(-1) === "") cells.pop();
|
|
40
|
+
return cells;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const compactBacklogHeaders = ["priority", "key", "work item", "status", "artefacts", "current spec", "next step"];
|
|
45
|
+
const legacyBacklogHeaders = ["prio", "key", "title", "status", "ur", "brownfield review", "prd", "sd", "tp", "qa", "or", "current spec", "notes"];
|
|
46
|
+
const backlogStatusLabels = new Map([
|
|
47
|
+
["needs ur", "needs_ur"],
|
|
48
|
+
["awaiting brownfield review", "awaiting_brownfield_review"],
|
|
49
|
+
["awaiting prd", "awaiting_prd"],
|
|
50
|
+
["awaiting prd approval", "awaiting_prd_approval"],
|
|
51
|
+
["awaiting sd", "awaiting_sd"],
|
|
52
|
+
["awaiting sd approval", "awaiting_sd_approval"],
|
|
53
|
+
["awaiting tp", "awaiting_tp"],
|
|
54
|
+
["awaiting tp approval", "awaiting_tp_approval"],
|
|
55
|
+
["in progress", "in_progress"],
|
|
56
|
+
["blocked", "blocked"],
|
|
57
|
+
["awaiting qa", "awaiting_qa"],
|
|
58
|
+
["awaiting uat", "awaiting_uat"],
|
|
59
|
+
["awaiting or", "awaiting_or"],
|
|
60
|
+
["completed", "completed"],
|
|
61
|
+
["superseded", "superseded"],
|
|
62
|
+
["abandoned", "abandoned"],
|
|
63
|
+
]);
|
|
64
|
+
const backlogArtefactLabels = new Map([
|
|
65
|
+
["ur", "ur"],
|
|
66
|
+
["brownfield", "brownfield_review"],
|
|
67
|
+
["prd", "prd"],
|
|
68
|
+
["sd", "sd"],
|
|
69
|
+
["tp", "tp"],
|
|
70
|
+
["qa", "qa"],
|
|
71
|
+
["or", "or"],
|
|
72
|
+
]);
|
|
73
|
+
const backlogScopeLabels = new Map([
|
|
74
|
+
["framework-maintenance", "framework_maintenance"],
|
|
75
|
+
["external-delivery", "external_delivery"],
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
export function normalizeBacklogHeader(value) {
|
|
79
|
+
return value.replace(/`/g, "").trim().toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function markdownLink(value) {
|
|
83
|
+
const match = value.trim().match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
84
|
+
return match ? { label: match[1].trim(), target: match[2].trim() } : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function resolvedBacklogLinkTarget(target) {
|
|
88
|
+
if (!target
|
|
89
|
+
|| target.startsWith("/")
|
|
90
|
+
|| target.includes("\\")
|
|
91
|
+
|| /^[a-z][a-z0-9+.-]*:/i.test(target)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const resolved = posix.normalize(posix.join(".agdf/control", target));
|
|
95
|
+
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function normalizedBacklogLinkTarget(value, findings, backlogPath, label) {
|
|
100
|
+
const link = markdownLink(value);
|
|
101
|
+
if (!link) return value.trim();
|
|
102
|
+
const resolved = resolvedBacklogLinkTarget(link.target);
|
|
103
|
+
if (!resolved) {
|
|
104
|
+
if (findings) {
|
|
105
|
+
addFinding(
|
|
106
|
+
findings,
|
|
107
|
+
"revise",
|
|
108
|
+
"AGDF_BACKLOG_LINK_TARGET_INVALID",
|
|
109
|
+
`Backlog ${label} link must be relative to MASTER_BACKLOG.md: ${link.target || "<empty>"}.`,
|
|
110
|
+
backlogPath,
|
|
111
|
+
"Use a document-relative artefact target such as artefacts/<key>/UR.md.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return link.target;
|
|
115
|
+
}
|
|
116
|
+
return resolved;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function normalizeBacklogStatus(value, findings, backlogPath) {
|
|
120
|
+
const cleaned = cleanStatusCell(value ?? "");
|
|
121
|
+
const lookup = cleaned.replaceAll("_", " ").toLowerCase();
|
|
122
|
+
const normalized = backlogStatusLabels.get(lookup);
|
|
123
|
+
if (normalized) return normalized;
|
|
124
|
+
if (/^[a-z][a-z0-9_]*$/.test(cleaned)) return cleaned;
|
|
125
|
+
if (findings && filled(cleaned)) {
|
|
126
|
+
addFinding(
|
|
127
|
+
findings,
|
|
128
|
+
"revise",
|
|
129
|
+
"AGDF_BACKLOG_STATUS_UNKNOWN",
|
|
130
|
+
`MASTER_BACKLOG.md uses an unknown human status label: ${cleaned}.`,
|
|
131
|
+
backlogPath,
|
|
132
|
+
"Use a documented human status label or a supported legacy snake_case value.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return cleaned;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function normalizeBacklogScope(workItem, findings, backlogPath) {
|
|
139
|
+
const match = (workItem ?? "").match(/^\[([^\]]+)\]/);
|
|
140
|
+
if (!match) return undefined;
|
|
141
|
+
const cleaned = match[1].trim();
|
|
142
|
+
const lookup = cleaned.replaceAll("_", " ").toLowerCase().replaceAll(" ", "-");
|
|
143
|
+
const normalized = backlogScopeLabels.get(lookup);
|
|
144
|
+
if (normalized) return normalized;
|
|
145
|
+
if (findings) {
|
|
146
|
+
addFinding(
|
|
147
|
+
findings,
|
|
148
|
+
"revise",
|
|
149
|
+
"AGDF_BACKLOG_SCOPE_LABEL_UNKNOWN",
|
|
150
|
+
`MASTER_BACKLOG.md uses an unknown Work item scope tag: [${cleaned}].`,
|
|
151
|
+
backlogPath,
|
|
152
|
+
"Use [framework-maintenance] or [external-delivery], or remove the bracketed tag.",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return cleaned;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function emptyBacklogPointer() {
|
|
159
|
+
return {
|
|
160
|
+
prio: "",
|
|
161
|
+
key: "",
|
|
162
|
+
title: "",
|
|
163
|
+
scope: "",
|
|
164
|
+
status: "",
|
|
165
|
+
ur: "",
|
|
166
|
+
brownfield_review: "",
|
|
167
|
+
prd: "",
|
|
168
|
+
sd: "",
|
|
169
|
+
tp: "",
|
|
170
|
+
qa: "",
|
|
171
|
+
or: "",
|
|
172
|
+
current_spec: "",
|
|
173
|
+
notes: "",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function parseCompactArtefacts(value, pointer, findings, backlogPath) {
|
|
178
|
+
if (!filled(value)) return;
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const entry of value.split(/\s+·\s+/)) {
|
|
181
|
+
const link = markdownLink(entry);
|
|
182
|
+
if (!link) {
|
|
183
|
+
if (findings) {
|
|
184
|
+
addFinding(
|
|
185
|
+
findings,
|
|
186
|
+
"revise",
|
|
187
|
+
"AGDF_BACKLOG_ARTEFACT_LINK_INVALID",
|
|
188
|
+
`Compact Artefacts entry is not a Markdown link: ${entry}.`,
|
|
189
|
+
backlogPath,
|
|
190
|
+
"Use [UR](artefacts/<key>/UR.md) style links separated by a middle dot.",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const field = backlogArtefactLabels.get(link.label.toLowerCase());
|
|
196
|
+
if (!field) {
|
|
197
|
+
if (findings) {
|
|
198
|
+
addFinding(
|
|
199
|
+
findings,
|
|
200
|
+
"revise",
|
|
201
|
+
"AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN",
|
|
202
|
+
`Compact Artefacts uses an unknown label: ${link.label}.`,
|
|
203
|
+
backlogPath,
|
|
204
|
+
"Use UR, Brownfield, PRD, SD, TP, QA or OR.",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (seen.has(field)) {
|
|
210
|
+
if (findings) {
|
|
211
|
+
addFinding(
|
|
212
|
+
findings,
|
|
213
|
+
"revise",
|
|
214
|
+
"AGDF_BACKLOG_ARTEFACT_LABEL_DUPLICATE",
|
|
215
|
+
`Compact Artefacts repeats the ${link.label} link.`,
|
|
216
|
+
backlogPath,
|
|
217
|
+
"Keep exactly one link per artefact type.",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
seen.add(field);
|
|
223
|
+
pointer[field] = normalizedBacklogLinkTarget(entry, findings, backlogPath, link.label);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function parseBacklogSection(section, findings = null, backlogPath = ".agdf/control/MASTER_BACKLOG.md") {
|
|
228
|
+
const rows = tableRows(section);
|
|
229
|
+
if (rows.length === 0) return [];
|
|
230
|
+
const headers = rows[0].map(normalizeBacklogHeader);
|
|
231
|
+
const layout = headers.join("|") === compactBacklogHeaders.join("|")
|
|
232
|
+
? "compact"
|
|
233
|
+
: headers.join("|") === legacyBacklogHeaders.join("|")
|
|
234
|
+
? "legacy"
|
|
235
|
+
: "unknown";
|
|
236
|
+
|
|
237
|
+
if (layout === "unknown") {
|
|
238
|
+
if (findings) {
|
|
239
|
+
addFinding(
|
|
240
|
+
findings,
|
|
241
|
+
"revise",
|
|
242
|
+
"AGDF_BACKLOG_LAYOUT_UNKNOWN",
|
|
243
|
+
`MASTER_BACKLOG.md uses an unsupported table layout: ${headers.join(", ")}.`,
|
|
244
|
+
backlogPath,
|
|
245
|
+
"Use the canonical compact layout or the supported legacy 13-column layout.",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return rows.slice(1)
|
|
252
|
+
.filter((cells) => cells.some((cell) => filled(cell)))
|
|
253
|
+
.map((cells) => {
|
|
254
|
+
const pointer = emptyBacklogPointer();
|
|
255
|
+
if (layout === "legacy") {
|
|
256
|
+
[
|
|
257
|
+
pointer.prio,
|
|
258
|
+
pointer.key,
|
|
259
|
+
pointer.title,
|
|
260
|
+
pointer.status,
|
|
261
|
+
pointer.ur,
|
|
262
|
+
pointer.brownfield_review,
|
|
263
|
+
pointer.prd,
|
|
264
|
+
pointer.sd,
|
|
265
|
+
pointer.tp,
|
|
266
|
+
pointer.qa,
|
|
267
|
+
pointer.or,
|
|
268
|
+
pointer.current_spec,
|
|
269
|
+
pointer.notes,
|
|
270
|
+
] = cells.map((cell) => cell ?? "");
|
|
271
|
+
pointer.status = normalizeBacklogStatus(pointer.status, findings, backlogPath);
|
|
272
|
+
return pointer;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
pointer.prio = cells[0] ?? "";
|
|
276
|
+
pointer.key = cleanStatusCell(cells[1] ?? "");
|
|
277
|
+
pointer.title = cells[2] ?? "";
|
|
278
|
+
pointer.scope = normalizeBacklogScope(cells[2], findings, backlogPath) ?? "";
|
|
279
|
+
pointer.status = normalizeBacklogStatus(cells[3], findings, backlogPath);
|
|
280
|
+
parseCompactArtefacts(cells[4] ?? "", pointer, findings, backlogPath);
|
|
281
|
+
pointer.current_spec = normalizedBacklogLinkTarget(cells[5] ?? "", findings, backlogPath, "Current spec");
|
|
282
|
+
pointer.notes = cells[6] ?? "";
|
|
283
|
+
return pointer;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function hasFilledTableRow(content, firstCellPattern) {
|
|
288
|
+
return tableRows(content)
|
|
289
|
+
.filter((cells) => firstCellPattern.test(cells[0] ?? ""))
|
|
290
|
+
.some((cells) => cells.slice(1).some((cell) => cell && !cell.startsWith("`")));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function isPlaceholderValue(value) {
|
|
294
|
+
return !value || (value.startsWith("`") && value.includes("|"));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function hasFilledEvidenceRow(content) {
|
|
298
|
+
const evidenceSection = content.match(/## Evidence([\s\S]*?)(?:\n## |\n# |$)/)?.[1] ?? "";
|
|
299
|
+
return tableRows(evidenceSection)
|
|
300
|
+
.filter((cells) => cells[0] !== "Evidence")
|
|
301
|
+
.some((cells) => {
|
|
302
|
+
const [evidence, source, covers] = cells;
|
|
303
|
+
return Boolean(evidence || source || covers);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function allowNoActiveRuns(targetDir) {
|
|
308
|
+
const configPath = join(targetDir, ".agdf", "control", "config.json");
|
|
309
|
+
if (!existsSync(configPath)) return false;
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(readFileSync(configPath, "utf8")).allow_no_active_runs === true;
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function markdownSection(content, heading) {
|
|
318
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
319
|
+
return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?:\\n## |\\n# |$)`))?.[1] ?? "";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function filled(value) {
|
|
323
|
+
return Boolean(value && !isPlaceholderValue(value) && value.trim() !== "");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function runSelectionRecovery(target) {
|
|
327
|
+
const directSelection = "Pass --run <run_id> or set AGDF_RUN_ID to select the intended run";
|
|
328
|
+
return ["doctor", "delivery-map"].includes(target)
|
|
329
|
+
? `${directSelection}, or use --all-active to evaluate every active run independently.`
|
|
330
|
+
: `${directSelection}.`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function parseQualityContracts(content) {
|
|
334
|
+
const parsed = JSON.parse(content);
|
|
335
|
+
if (!Array.isArray(parsed.contracts) || parsed.contracts.length === 0) {
|
|
336
|
+
throw new Error("contracts must be a non-empty array");
|
|
337
|
+
}
|
|
338
|
+
for (const contract of parsed.contracts) {
|
|
339
|
+
if (!contract.code || !contract.impact || !contract.required_evidence) {
|
|
340
|
+
throw new Error(`contract ${contract.code ?? "<unknown>"} is missing code, impact or required_evidence`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return parsed;
|
|
344
|
+
}
|
|
345
|
+
|