create-agdf 0.10.1 → 0.10.2

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.
Files changed (87) hide show
  1. package/README.md +21 -13
  2. package/bin/agdf-validator.js +11 -0
  3. package/generated/.agdf/control/README.md +3 -3
  4. package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  5. package/generated/.github/skills/agdf-delivery-path-search/SKILL.md +7 -6
  6. package/generated/.github/skills/agdf-gate-check/SKILL.md +28 -91
  7. package/generated/.github/skills/agdf-interaction-locales.json +2 -0
  8. package/generated/.github/skills/contracts/gate-transition.md +4 -2
  9. package/generated/.github/skills/contracts/modes.md +12 -1
  10. package/generated/.opencode/agdf-interaction-locales.json +2 -0
  11. package/generated/.opencode/contracts/gate-transition.md +4 -2
  12. package/generated/.opencode/contracts/modes.md +12 -1
  13. package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  14. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
  15. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +28 -91
  16. package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
  17. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  18. package/generated/plugins/agdf/control/README.md +3 -3
  19. package/generated/plugins/agdf/hooks/session-start.sh +1 -1
  20. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -0
  21. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  22. package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
  23. package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
  24. package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
  25. package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
  26. package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
  27. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +418 -0
  28. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
  29. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
  30. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
  31. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
  32. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +45 -0
  34. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
  35. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
  36. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +459 -0
  37. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
  38. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
  39. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
  40. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
  43. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
  44. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
  45. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
  48. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
  49. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
  50. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
  51. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
  52. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
  53. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
  54. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
  55. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
  56. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
  57. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
  58. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
  59. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
  60. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
  61. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
  62. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
  63. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
  64. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +590 -0
  65. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
  66. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
  67. package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
  68. package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
  69. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1040 -0
  70. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
  71. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
  72. package/generated/plugins/agdf/skills/gate-check/SKILL.md +28 -91
  73. package/lib/cli/application.js +16 -54
  74. package/lib/cli/command-registry.js +9 -11
  75. package/lib/cli/validation-handlers.js +45 -0
  76. package/lib/control-evaluation/doctor.js +1 -1
  77. package/lib/control-evaluation/gate-check.js +7 -7
  78. package/lib/control-evaluation/required-files.js +9 -0
  79. package/lib/installers/local-marketplace.js +322 -0
  80. package/lib/installers/opencode.js +32 -2
  81. package/lib/installers/plugin-installers.js +102 -26
  82. package/lib/runtime/local-validator.js +142 -0
  83. package/lib/runtime/validator-application.js +40 -0
  84. package/lib/scaffold/plan.js +1 -7
  85. package/lib/scaffold/presentation.js +2 -2
  86. package/opencode-plugin.js +1 -1
  87. package/package.json +6 -2
@@ -0,0 +1,590 @@
1
+ const REQUIRED_GATES = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
2
+ const ARTEFACT_ORDER = ["UR", "PRD", "SD", "TP"];
3
+ const OUTCOMES = new Set(["approve", "revise", "decline", "cancel", "no_response", "timeout", "empty", "invalid", "stale"]);
4
+ const ATTEMPT_OUTCOMES = new Set(["presented", "unavailable_before_invocation", "attempted_not_applied", "unsafe_to_wait"]);
5
+ const APPROVAL_VALUE_TRANSPORTS = new Set(["exact_option_value", "separate_label_and_value", "decorated_label_only", "unknown"]);
6
+ const APPROVAL_SEQUENCE = Object.freeze(["run_status_card", "gate_transition_card", "approval_interaction"]);
7
+ const QUALITY_STATUS_RANK = { pass: 0, warn: 1, revise: 2, block: 3 };
8
+ const QUALITY_DIMENSIONS = Object.freeze([
9
+ Object.freeze({ id: "plan_coverage", owner: "task-plan-review" }),
10
+ Object.freeze({ id: "solution_integrity", owner: "clean-implementation-review" }),
11
+ Object.freeze({ id: "code_quality", owner: "code-review" }),
12
+ Object.freeze({ id: "qa_decision", owner: "qa-gate" }),
13
+ ]);
14
+
15
+ function plainObject(value) {
16
+ return value && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+
19
+ function flattenKeys(value, prefix = "") {
20
+ const keys = [];
21
+ for (const [key, child] of Object.entries(value ?? {})) {
22
+ const path = prefix ? `${prefix}.${key}` : key;
23
+ if (plainObject(child)) keys.push(...flattenKeys(child, path));
24
+ else keys.push(path);
25
+ }
26
+ return keys.sort();
27
+ }
28
+
29
+ function visibleStrings(value, prefix = "") {
30
+ const entries = [];
31
+ for (const [key, child] of Object.entries(value ?? {})) {
32
+ const path = prefix ? `${prefix}.${key}` : key;
33
+ if (plainObject(child)) entries.push(...visibleStrings(child, path));
34
+ else if (typeof child === "string") entries.push([path, child]);
35
+ }
36
+ return entries;
37
+ }
38
+
39
+ export function canonicalizeLanguageTag(value) {
40
+ const raw = String(value ?? "").trim().replaceAll("_", "-").replace(/\.(.+)$/, "");
41
+ if (!raw || !/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(raw)) return "";
42
+ try {
43
+ return Intl.getCanonicalLocales(raw)[0]?.toLowerCase() ?? "";
44
+ } catch {
45
+ return "";
46
+ }
47
+ }
48
+
49
+ export function validateLocaleRegistry(registry) {
50
+ const errors = [];
51
+ if (!plainObject(registry) || registry.schemaVersion !== 1) errors.push("schema_version");
52
+ const fallback = canonicalizeLanguageTag(registry?.fallbackLocale);
53
+ const locales = registry?.locales;
54
+ if (!fallback || !plainObject(locales) || !plainObject(locales[fallback])) errors.push("fallback_locale");
55
+ const baseline = plainObject(locales?.[fallback]) ? flattenKeys(locales[fallback]) : [];
56
+ const budgets = registry?.lengthBudgets ?? {};
57
+ for (const [locale, pack] of Object.entries(locales ?? {})) {
58
+ if (!canonicalizeLanguageTag(locale) || !plainObject(pack)) {
59
+ errors.push(`invalid_locale:${locale}`);
60
+ continue;
61
+ }
62
+ const keys = flattenKeys(pack);
63
+ if (JSON.stringify(keys) !== JSON.stringify(baseline)) errors.push(`incomplete_locale:${locale}`);
64
+ for (const [key, value] of visibleStrings(pack)) {
65
+ if (!value.trim()) errors.push(`empty_copy:${locale}:${key}`);
66
+ const budget = key.startsWith("gateTitles.") || key.startsWith("gateActionTitles.") ? budgets.title
67
+ : key.includes("Description") || key.includes("fallbackReasons") || key.startsWith("gateRequiredDecisions.") || key.startsWith("primary.actions.") || key.startsWith("primary.afterApproval.") || key.startsWith("primary.narration.") || key.startsWith("gateRationale.") || key.startsWith("interaction.why.") || ["interaction.decisionInstruction", "interaction.decisionPrompt", "interaction.exactTextRequest", "interaction.decisionFollows", "interaction.presentationFailure", "interaction.nonReadyDecision", "primary.quality"].includes(key)
68
+ ? budgets.description
69
+ : budgets.label;
70
+ if (Number.isInteger(budget) && value.length > budget) errors.push(`length_budget:${locale}:${key}`);
71
+ }
72
+ for (const key of REQUIRED_GATES) {
73
+ if (typeof pack.gateTitles?.[key] !== "string" || !pack.gateTitles[key].trim()) errors.push(`missing_gate_title:${locale}:${key}`);
74
+ if (typeof pack.gateActionTitles?.[key] !== "string" || !pack.gateActionTitles[key].trim()) errors.push(`missing_gate_action_title:${locale}:${key}`);
75
+ if (typeof pack.gateRequiredDecisions?.[key] !== "string" || !pack.gateRequiredDecisions[key].trim()) errors.push(`missing_gate_required_decision:${locale}:${key}`);
76
+ }
77
+ }
78
+ if (JSON.stringify(registry?.optionOrder) !== JSON.stringify(["approve", "revise", "decline", "cancel"])) errors.push("option_order");
79
+ return { valid: errors.length === 0, errors };
80
+ }
81
+
82
+ export function resolvePresentationLocale(registry, requestedLocale) {
83
+ const validation = validateLocaleRegistry(registry);
84
+ if (!validation.valid) throw new Error(`Invalid AGDF interaction locale registry: ${validation.errors.join(",")}`);
85
+ const fallback = canonicalizeLanguageTag(registry.fallbackLocale);
86
+ const requested = canonicalizeLanguageTag(requestedLocale);
87
+ if (requested && registry.locales[requested]) return requested;
88
+ const language = requested.split("-")[0];
89
+ if (language && registry.locales[language]) return language;
90
+ return fallback;
91
+ }
92
+
93
+ export function localePack(registry, requestedLocale) {
94
+ return registry.locales[resolvePresentationLocale(registry, requestedLocale)];
95
+ }
96
+
97
+ export function gateTitle(registry, requestedLocale, gate) {
98
+ return localePack(registry, requestedLocale).gateTitles[gate] || String(gate || "");
99
+ }
100
+
101
+ export function gateRationale(registry, requestedLocale, gate) {
102
+ return localePack(registry, requestedLocale).gateRationale?.[gate] || String(gate || "");
103
+ }
104
+
105
+ function stripMarkdown(value) {
106
+ return String(value ?? "")
107
+ .replace(/^#+\s*/, "")
108
+ .replace(/[`*_]/g, "")
109
+ .trim();
110
+ }
111
+
112
+ function section(content, heading) {
113
+ const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
114
+ return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`))?.[1]?.trim() ?? "";
115
+ }
116
+
117
+ export function normalizedRunTitle(runId) {
118
+ const words = String(runId ?? "")
119
+ .replace(/[._-]+/g, " ")
120
+ .replace(/\s+/g, " ")
121
+ .trim();
122
+ return words ? words.replace(/\b\p{L}/gu, (letter) => letter.toUpperCase()) : "AGDF run";
123
+ }
124
+
125
+ export function resolveHumanRunTitle({ currentArtefactHeading, urHeading, runContent, runId }) {
126
+ const objective = section(String(runContent ?? ""), "Objective").split(/\r?\n/).map(stripMarkdown).find(Boolean) ?? "";
127
+ return [currentArtefactHeading, urHeading, objective].map(stripMarkdown).find(Boolean) || normalizedRunTitle(runId);
128
+ }
129
+
130
+ function displaySafeTitle(value, fallback) {
131
+ const title = stripMarkdown(value).replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
132
+ return (title || fallback).slice(0, 100);
133
+ }
134
+
135
+ export function buildRunCandidates(runs) {
136
+ return [...(runs ?? [])]
137
+ .filter((run) => run?.valid && run?.meta?.lifecycle === "active" && typeof run.run_id === "string")
138
+ .map((run) => {
139
+ const currentGate = run.control_state?.current_gate || run.meta?.current_gate || "unknown";
140
+ const title = resolveHumanRunTitle({
141
+ currentArtefactHeading: run.current_artefact_heading,
142
+ urHeading: run.ur_heading,
143
+ runContent: "",
144
+ runId: run.run_id,
145
+ });
146
+ return {
147
+ run_id: run.run_id,
148
+ display_title: displaySafeTitle(title, normalizedRunTitle(run.run_id)),
149
+ current_gate: currentGate,
150
+ next_allowed_action: String(run.control_state?.next_allowed_action ?? "").trim(),
151
+ revision_id: String(run.meta?.revision_id ?? "").trim(),
152
+ };
153
+ })
154
+ .sort((left, right) => left.display_title.localeCompare(right.display_title) || left.run_id.localeCompare(right.run_id));
155
+ }
156
+
157
+ export function normalizeReconciliationText(value) {
158
+ return String(value ?? "")
159
+ .normalize("NFKC")
160
+ .toLowerCase()
161
+ .replace(/[._:/\\-]+/g, " ")
162
+ .replace(/[^\p{L}\p{N}\s]+/gu, " ")
163
+ .replace(/\s+/g, " ")
164
+ .trim();
165
+ }
166
+
167
+ export function reconcileRunScope({ requestText = "", scopeKey = "", runs = [] } = {}) {
168
+ const requested = normalizeReconciliationText(scopeKey || requestText);
169
+ if (!requested) return Object.freeze({ outcome: "match_uncertain", matches: [], reason: "scope_input_missing" });
170
+ const matches = [...(runs ?? [])]
171
+ .filter((run) => run?.valid && ["active", "completed"].includes(run?.meta?.lifecycle) && typeof run.run_id === "string")
172
+ .filter((run) => {
173
+ const keys = [run.scope_key, run.display_title, run.current_artefact_heading, run.ur_heading]
174
+ .map(normalizeReconciliationText)
175
+ .filter(Boolean);
176
+ return keys.includes(requested);
177
+ })
178
+ .map((run) => Object.freeze({
179
+ run_id: run.run_id,
180
+ lifecycle: run.meta.lifecycle,
181
+ display_title: displaySafeTitle(run.display_title || run.ur_heading || run.run_id, normalizedRunTitle(run.run_id)),
182
+ current_gate: run.control_state?.current_gate || run.meta?.current_gate || "unknown",
183
+ }))
184
+ .sort((left, right) => left.run_id.localeCompare(right.run_id));
185
+ if (matches.length !== 1) return Object.freeze({ outcome: matches.length ? "match_uncertain" : "no_match", matches, reason: matches.length > 1 ? "multiple_exact_matches" : "no_exact_match" });
186
+ return Object.freeze({ outcome: matches[0].lifecycle === "active" ? "active_match" : "completed_match", matches, reason: "exact_match" });
187
+ }
188
+
189
+ export function evaluateNativeApprovalCapability({ staticCapability, runtimeCapability } = {}) {
190
+ const staticValue = staticCapability?.approvalValueTransport;
191
+ const runtimeValue = runtimeCapability?.approvalValueTransport;
192
+ if (runtimeCapability && staticCapability && (runtimeValue !== staticValue || runtimeCapability.waitSafety !== staticCapability.waitSafety)) {
193
+ return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unavailable_before_invocation", reason: "capability_conflict", evidence_source: "runtime" });
194
+ }
195
+ const capability = runtimeCapability ?? staticCapability;
196
+ const evidenceSource = runtimeCapability ? "runtime" : staticCapability ? "static" : "none";
197
+ if (!capability || !APPROVAL_VALUE_TRANSPORTS.has(capability.approvalValueTransport)) return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unavailable_before_invocation", reason: "capability_missing", evidence_source: evidenceSource });
198
+ if (capability.waitSafety === "unsafe") return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unsafe_to_wait", reason: "unsafe_wait", evidence_source: evidenceSource });
199
+ if (capability.waitSafety !== "deliberate_no_auto_resolution") return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unavailable_before_invocation", reason: "capability_missing", evidence_source: evidenceSource });
200
+ if (!new Set(["exact_option_value", "separate_label_and_value"]).has(capability.approvalValueTransport)) return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unavailable_before_invocation", reason: capability.approvalValueTransport === "decorated_label_only" ? "decorated_only" : "capability_missing", evidence_source: evidenceSource });
201
+ return Object.freeze({ eligible: true, native_attempt_required: true, preflight_outcome: "eligible_for_invocation", reason: capability.approvalValueTransport === "exact_option_value" ? "exact_transport" : "separate_value_transport", evidence_source: evidenceSource });
202
+ }
203
+
204
+ export function executeNativeApprovalAttempt({ ready = false, orientationSnapshot, capabilityPreflight, invokeNative, fallback } = {}) {
205
+ if (!ready) return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "gate_not_ready", authorizes: false });
206
+ const preflight = validateApprovalOrientationSnapshot(orientationSnapshot);
207
+ if (!preflight.valid) return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "orientation_preflight_failed", authorizes: false });
208
+ if (!capabilityPreflight?.eligible) {
209
+ const outcome = capabilityPreflight?.preflight_outcome === "unsafe_to_wait" ? "unsafe_to_wait" : "unavailable_before_invocation";
210
+ const reason = capabilityPreflight?.reason || "capability_missing";
211
+ if (typeof fallback === "function") fallback({ outcome, reason });
212
+ return Object.freeze({ attempted: false, outcome, reason, authorizes: false });
213
+ }
214
+ if (typeof invokeNative !== "function") return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "adapter_unavailable", authorizes: false });
215
+ let adapterResult;
216
+ try {
217
+ adapterResult = invokeNative();
218
+ } catch (error) {
219
+ adapterResult = { outcome: "attempted_not_applied", reason: String(error?.message || "adapter_error") };
220
+ }
221
+ const outcome = ATTEMPT_OUTCOMES.has(adapterResult?.outcome)
222
+ ? adapterResult.outcome
223
+ : adapterResult?.presented === true || adapterResult?.applied === true
224
+ ? "presented"
225
+ : "attempted_not_applied";
226
+ const reason = String(adapterResult?.reason || "").trim();
227
+ if (outcome !== "presented" && typeof fallback === "function") fallback({ outcome, reason });
228
+ return Object.freeze({ attempted: true, outcome, reason, authorizes: false });
229
+ }
230
+
231
+ export function buildInteractionAttempt({ interactionId, runId, currentGate, surface, attemptOutcome, expectedApproval, fallbackReason = "" }) {
232
+ if (!ATTEMPT_OUTCOMES.has(attemptOutcome)) throw new Error("invalid interaction attempt outcome");
233
+ if (!String(interactionId ?? "").trim() || !String(runId ?? "").trim() || !String(currentGate ?? "").trim())
234
+ throw new Error("interaction attempt identity missing");
235
+ return Object.freeze({
236
+ interaction_id: String(interactionId), run_id: String(runId), current_gate: String(currentGate),
237
+ surface: String(surface ?? "fallback"), attempt_outcome: attemptOutcome,
238
+ expected_approval: String(expectedApproval ?? ""), fallback_reason: String(fallbackReason ?? ""),
239
+ authorizes: false,
240
+ });
241
+ }
242
+
243
+ function safeArtefactPath(value) {
244
+ const path = String(value ?? "").replace(/^`|`$/g, "").trim();
245
+ if (!path || !/^[A-Za-z0-9._/-]+$/.test(path) || /^([a-z]+:|\/)/i.test(path)) return "";
246
+ const parts = path.replaceAll("\\", "/").split("/");
247
+ if (parts.includes("..")) return "";
248
+ return path;
249
+ }
250
+
251
+ export function buildArtefactRefs(artefacts, registry, requestedLocale, { pathExists } = {}) {
252
+ const pack = localePack(registry, requestedLocale);
253
+ return ARTEFACT_ORDER.map((type) => {
254
+ const artefact = artefacts instanceof Map ? artefacts.get(type) : artefacts?.[type];
255
+ const candidatePath = safeArtefactPath(artefact?.path);
256
+ const path = candidatePath && (!pathExists || pathExists(candidatePath, type)) ? candidatePath : "";
257
+ return {
258
+ type,
259
+ exists: Boolean(path),
260
+ status: String(artefact?.status ?? ""),
261
+ path,
262
+ label: type,
263
+ missingLabel: `${type} (${pack.artefacts.notYetCreated})`,
264
+ };
265
+ });
266
+ }
267
+
268
+ export function formatArtefactRefs(refs) {
269
+ return refs.map((ref) => ref.exists ? `[${ref.label}](${ref.path})` : ref.missingLabel).join(" · ");
270
+ }
271
+
272
+ export function gateOptions(registry, requestedLocale, gate, { includeCancel = false } = {}) {
273
+ const pack = localePack(registry, requestedLocale);
274
+ const options = [
275
+ { outcome: "approve", label: `Approval: ${gate}`, description: pack.interaction.approveDescription, value: `Approval: ${gate}`, authorizes: true },
276
+ { outcome: "revise", label: pack.interaction.reviseLabel, description: pack.interaction.reviseDescription, value: "revise", authorizes: false },
277
+ { outcome: "decline", label: pack.interaction.declineLabel, description: pack.interaction.declineDescription, value: "decline", authorizes: false },
278
+ ];
279
+ if (includeCancel) options.push({ outcome: "cancel", label: pack.interaction.cancelLabel, description: pack.interaction.cancelDescription, value: "cancel", authorizes: false });
280
+ return options;
281
+ }
282
+
283
+ export function normalizeInteractionOutcome(value, { expectedApproval, stale = false, timedOut = false, noResponse = false } = {}) {
284
+ if (stale) return "stale";
285
+ if (timedOut) return "timeout";
286
+ if (noResponse || value === undefined || value === null) return "no_response";
287
+ if (typeof value !== "string") return "invalid";
288
+ const normalized = value.trim();
289
+ if (!normalized) return "empty";
290
+ if (normalized === expectedApproval) return "approve";
291
+ if (OUTCOMES.has(normalized) && normalized !== "approve") return normalized;
292
+ return "invalid";
293
+ }
294
+
295
+ export function buildApprovalOrientationSnapshot({
296
+ ready = false,
297
+ statusCard,
298
+ humanPresentation,
299
+ revisionId = "",
300
+ registry,
301
+ requestedLocale,
302
+ } = {}) {
303
+ if (!ready || !plainObject(statusCard) || !plainObject(humanPresentation)) return null;
304
+ const gate = String(statusCard.current_gate ?? "").trim();
305
+ const runId = String(statusCard.run_id ?? "").trim();
306
+ const expectedApproval = `Approval: ${gate}`;
307
+ if (!REQUIRED_GATES.includes(gate) || !/^[A-Za-z0-9._-]+$/.test(runId) || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
308
+
309
+ const locale = resolvePresentationLocale(registry, requestedLocale);
310
+ const pack = localePack(registry, locale);
311
+ const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
312
+ const currentGateTitle = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, gate);
313
+ const primaryHeading = String(pack.gateActionTitles?.[gate] ?? "").trim();
314
+ const artefactRefs = Object.freeze([...(humanPresentation.artefactRefs ?? [])].map((ref) => Object.freeze({ ...ref })));
315
+ const nextGate = String(statusCard.next_gate_after_approval ?? "none");
316
+ const narration = pack.primary.narration.gates[gate] ?? {};
317
+ const nextDecision = narration.userAction
318
+ || (nextGate === "none" ? pack.primary.narration.noAction : `${pack.gateTitles[nextGate] ?? nextGate} ${pack.interaction.decisionFollows}`);
319
+ const statusFields = Object.freeze([
320
+ Object.freeze({ id: "selected_run", label: pack.statusCard.run, value: `${runTitle} · \`${runId}\`` }),
321
+ Object.freeze({ id: "readiness_status", label: pack.statusCard.title, value: pack.interaction.ready }),
322
+ Object.freeze({ id: "current_gate", label: pack.statusCard.gate, value: `${currentGateTitle} (\`${gate}\`)` }),
323
+ Object.freeze({ id: "required_decision", label: pack.statusCard.requiredDecision, value: pack.gateRequiredDecisions[gate] }),
324
+ Object.freeze({ id: "next_action", label: pack.statusCard.step, value: pack.interaction.decisionInstruction }),
325
+ ]);
326
+ const options = Object.freeze(gateOptions(registry, locale, gate).map((option) => Object.freeze({ ...option })));
327
+
328
+ return Object.freeze({
329
+ schema_version: "1",
330
+ run_id: runId,
331
+ revision_id: String(revisionId ?? "").trim(),
332
+ current_gate: gate,
333
+ presentation_language: locale,
334
+ sequence: APPROVAL_SEQUENCE,
335
+ compact_status_card: Object.freeze({
336
+ semantic_block: "run_status_card",
337
+ title: primaryHeading,
338
+ primary_heading: primaryHeading,
339
+ primary_heading_level: 2,
340
+ fields: statusFields,
341
+ }),
342
+ gate_transition_card: Object.freeze({
343
+ semantic_block: "gate_transition_card",
344
+ title: `${currentGateTitle} · ${runTitle} · \`${runId}\``,
345
+ artefact_refs: artefactRefs,
346
+ ready: pack.interaction.ready,
347
+ approve_heading: pack.interaction.approveHeading,
348
+ exact_approval: expectedApproval,
349
+ approval_effect: pack.primary.afterApproval[gate] ?? pack.primary.actions[gate] ?? "",
350
+ next_heading: pack.interaction.nextHeading,
351
+ next_gate: nextGate,
352
+ next_transition: `${narration.agentNext ?? pack.primary.actions[gate] ?? ""}. ${nextDecision}.`,
353
+ }),
354
+ approval_interaction: Object.freeze({
355
+ semantic_block: "approval_interaction",
356
+ prompt: pack.interaction.decisionPrompt.replace("{gate}", currentGateTitle).replace("{run_id}", runId),
357
+ expected_approval: expectedApproval,
358
+ options,
359
+ exact_text_fallback: pack.interaction.exactTextRequest.replace("{approval}", expectedApproval),
360
+ authorizes: false,
361
+ }),
362
+ authorizes: false,
363
+ });
364
+ }
365
+
366
+ export function validateApprovalOrientationSnapshot(snapshot, { registry, expectedIdentity } = {}) {
367
+ const errors = [];
368
+ if (!plainObject(snapshot)) return Object.freeze({ valid: false, errors: Object.freeze(["snapshot_missing"]) });
369
+ if (JSON.stringify(snapshot.sequence) !== JSON.stringify(APPROVAL_SEQUENCE)) errors.push("sequence");
370
+ if (snapshot.compact_status_card?.semantic_block !== "run_status_card") errors.push("run_status_card");
371
+ if (snapshot.gate_transition_card?.semantic_block !== "gate_transition_card") errors.push("gate_transition_card");
372
+ if (snapshot.approval_interaction?.semantic_block !== "approval_interaction") errors.push("approval_interaction");
373
+ const heading = String(snapshot.compact_status_card?.primary_heading ?? "").trim();
374
+ if (!heading || snapshot.compact_status_card?.primary_heading_level !== 2) errors.push("primary_heading");
375
+ if (String(snapshot.compact_status_card?.title ?? "").trim() !== heading) errors.push("primary_heading_owner");
376
+ if (/^(?:(?:agdf[- ]?status(?: card|karte)?|run status card|gate transition card)(?:\b|\s*[-:—·])|(?:UR|PRD|SD|TP|QA|UAT)(?:\s*[-:—·]|$))/i.test(heading)) errors.push("generic_primary_heading");
377
+ if (/^(?:approve|accept|authorize|freigeben|genehmigen|abnehmen)\b|(?:freigeben|genehmigen|abnehmen)$/i.test(heading)) errors.push("approval_biased_heading");
378
+ const fieldIds = snapshot.compact_status_card?.fields?.map((field) => field?.id);
379
+ if (JSON.stringify(fieldIds) !== JSON.stringify(["selected_run", "readiness_status", "current_gate", "required_decision", "next_action"])) errors.push("status_fields");
380
+ if (!String(snapshot.revision_id ?? "").trim()) errors.push("revision_identity");
381
+ const runIdentitySuffix = ` · \`${String(snapshot.run_id ?? "")}\``;
382
+ if (!String(snapshot.compact_status_card?.fields?.[0]?.value ?? "").endsWith(runIdentitySuffix)
383
+ || !String(snapshot.gate_transition_card?.title ?? "").endsWith(runIdentitySuffix)) errors.push("identity_projection");
384
+ const expectedApproval = `Approval: ${String(snapshot.current_gate ?? "").trim()}`;
385
+ const inCardApprovalCount = JSON.stringify({
386
+ compact_status_card: snapshot.compact_status_card,
387
+ gate_transition_card: snapshot.gate_transition_card,
388
+ }).split(expectedApproval).length - 1;
389
+ if (inCardApprovalCount !== 1) errors.push("approval_card_occurrence");
390
+ const artefactOrder = snapshot.gate_transition_card?.artefact_refs?.map((ref) => ref?.type);
391
+ if (JSON.stringify(artefactOrder) !== JSON.stringify(ARTEFACT_ORDER)) errors.push("artefact_order");
392
+ if (snapshot.gate_transition_card?.artefact_refs?.some((ref) => ref?.exists && safeArtefactPath(ref.path) !== ref.path)) errors.push("unsafe_artefact_ref");
393
+ if (!String(snapshot.gate_transition_card?.approval_effect ?? "").trim() || !String(snapshot.gate_transition_card?.next_transition ?? "").trim()) errors.push("transition_content");
394
+ if (!String(snapshot.approval_interaction?.prompt ?? "").trim() || !String(snapshot.approval_interaction?.exact_text_fallback ?? "").includes(expectedApproval)) errors.push("approval_interaction_copy");
395
+ if (JSON.stringify(snapshot.approval_interaction?.options?.map((option) => option?.outcome)) !== JSON.stringify(["approve", "revise", "decline"])) errors.push("option_order");
396
+ if (!REQUIRED_GATES.includes(snapshot.current_gate)
397
+ || snapshot.gate_transition_card?.exact_approval !== expectedApproval
398
+ || snapshot.approval_interaction?.expected_approval !== expectedApproval
399
+ || snapshot.approval_interaction?.options?.[0]?.value !== expectedApproval) errors.push("canonical_approval");
400
+ if (plainObject(expectedIdentity)) {
401
+ if (["run_id", "revision_id", "current_gate", "presentation_language"]
402
+ .some((key) => String(snapshot[key] ?? "") !== String(expectedIdentity[key] ?? ""))) errors.push("stale_identity");
403
+ }
404
+ if (plainObject(registry) && REQUIRED_GATES.includes(snapshot.current_gate)) {
405
+ const locale = String(snapshot.presentation_language ?? "");
406
+ const pack = localePack(registry, locale);
407
+ const gate = snapshot.current_gate;
408
+ const fields = snapshot.compact_status_card?.fields ?? [];
409
+ const localizedOptions = gateOptions(registry, locale, gate);
410
+ const localizedGateTitle = gateTitle(registry, locale, gate);
411
+ const nextGate = String(snapshot.gate_transition_card?.next_gate ?? "none");
412
+ const narration = pack.primary.narration.gates[gate] ?? {};
413
+ const nextDecision = narration.userAction
414
+ || (nextGate === "none" ? pack.primary.narration.noAction : `${pack.gateTitles[nextGate] ?? nextGate} ${pack.interaction.decisionFollows}`);
415
+ const nextTransition = `${narration.agentNext ?? pack.primary.actions[gate] ?? ""}. ${nextDecision}.`;
416
+ const localeConsistent = resolvePresentationLocale(registry, locale) === locale
417
+ && heading === pack.gateActionTitles?.[gate]
418
+ && fields[0]?.label === pack.statusCard.run
419
+ && fields[1]?.label === pack.statusCard.title
420
+ && fields[1]?.value === pack.interaction.ready
421
+ && fields[2]?.label === pack.statusCard.gate
422
+ && fields[2]?.value === `${localizedGateTitle} (\`${gate}\`)`
423
+ && fields[3]?.label === pack.statusCard.requiredDecision
424
+ && fields[3]?.value === pack.gateRequiredDecisions?.[gate]
425
+ && fields[4]?.label === pack.statusCard.step
426
+ && fields[4]?.value === pack.interaction.decisionInstruction
427
+ && String(snapshot.gate_transition_card?.title ?? "").startsWith(`${localizedGateTitle} · `)
428
+ && snapshot.gate_transition_card?.ready === pack.interaction.ready
429
+ && snapshot.gate_transition_card?.approve_heading === pack.interaction.approveHeading
430
+ && snapshot.gate_transition_card?.approval_effect === (pack.primary.afterApproval?.[gate] ?? pack.primary.actions?.[gate] ?? "")
431
+ && snapshot.gate_transition_card?.next_heading === pack.interaction.nextHeading
432
+ && snapshot.gate_transition_card?.next_transition === nextTransition
433
+ && snapshot.approval_interaction?.prompt === pack.interaction.decisionPrompt.replace("{gate}", localizedGateTitle).replace("{run_id}", snapshot.run_id)
434
+ && snapshot.approval_interaction?.exact_text_fallback === pack.interaction.exactTextRequest.replace("{approval}", expectedApproval)
435
+ && JSON.stringify(snapshot.approval_interaction?.options) === JSON.stringify(localizedOptions);
436
+ if (!localeConsistent) errors.push("locale_consistency");
437
+ }
438
+ return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
439
+ }
440
+
441
+ export function renderApprovalOrientationSnapshot(snapshot, validation = {}) {
442
+ if (!validateApprovalOrientationSnapshot(snapshot, validation).valid) return null;
443
+ const statusMarkdown = [
444
+ `## ${snapshot.compact_status_card.primary_heading}`,
445
+ ...snapshot.compact_status_card.fields.map((field) => `${field.label}: ${field.value}`),
446
+ ].join("\n");
447
+ const transition = snapshot.gate_transition_card;
448
+ const transitionMarkdown = [
449
+ transition.title,
450
+ formatArtefactRefs(transition.artefact_refs),
451
+ transition.ready,
452
+ "",
453
+ transition.approve_heading,
454
+ `\`${transition.exact_approval}\` ${transition.approval_effect}`,
455
+ "",
456
+ transition.next_heading,
457
+ transition.next_transition,
458
+ ].join("\n");
459
+ return Object.freeze({
460
+ schema_version: "1",
461
+ run_id: snapshot.run_id,
462
+ revision_id: snapshot.revision_id,
463
+ current_gate: snapshot.current_gate,
464
+ presentation_language: snapshot.presentation_language,
465
+ sequence: snapshot.sequence,
466
+ blocks: Object.freeze({
467
+ run_status_card: Object.freeze({ markdown: statusMarkdown }),
468
+ gate_transition_card: Object.freeze({ markdown: transitionMarkdown }),
469
+ }),
470
+ approval_interaction: snapshot.approval_interaction,
471
+ authorizes: false,
472
+ });
473
+ }
474
+
475
+ export function attachApprovalOrientationSnapshot(statusCard, options = {}) {
476
+ if (!plainObject(statusCard)) throw new Error("approval orientation status card missing");
477
+ const snapshot = buildApprovalOrientationSnapshot({ ...options, statusCard });
478
+ Object.defineProperty(statusCard, "approvalOrientation", {
479
+ value: snapshot,
480
+ enumerable: false,
481
+ });
482
+ return snapshot;
483
+ }
484
+
485
+ function qualityStatus(value) {
486
+ const normalized = String(value ?? "").trim().toLowerCase();
487
+ return Object.hasOwn(QUALITY_STATUS_RANK, normalized) ? normalized : "unknown";
488
+ }
489
+
490
+ export function buildQualityReadiness({
491
+ planCoverage,
492
+ solutionIntegrity,
493
+ codeQuality,
494
+ qaDecision,
495
+ decisiveReason = "",
496
+ nextAction = "",
497
+ } = {}) {
498
+ const values = [planCoverage, solutionIntegrity, codeQuality, qaDecision];
499
+ const rows = QUALITY_DIMENSIONS.map((dimension, index) => Object.freeze({
500
+ ...dimension,
501
+ status: qualityStatus(values[index]),
502
+ }));
503
+ const known = rows.filter((row) => row.status !== "unknown");
504
+ if (known.length === 0) return null;
505
+ const status = rows.some((row) => row.status === "unknown")
506
+ ? "revise"
507
+ : known.reduce((current, row) =>
508
+ QUALITY_STATUS_RANK[row.status] > QUALITY_STATUS_RANK[current] ? row.status : current,
509
+ "pass");
510
+ const decisiveRow = rows.find((row) => row.status === status && status !== "pass")
511
+ ?? rows.find((row) => row.id === "qa_decision")
512
+ ?? rows[0];
513
+ return Object.freeze({
514
+ status,
515
+ rows: Object.freeze(rows),
516
+ decisive_reason: String(decisiveReason ?? "").trim(),
517
+ decisive_dimension: decisiveRow.id,
518
+ next_action: String(nextAction ?? "").trim(),
519
+ decision_owner: "qa-gate",
520
+ authorizes: false,
521
+ });
522
+ }
523
+
524
+ export function collapseInternalState(
525
+ { modeSliceDecision = "", verifiedChangeState = "", contextGraphRequiredAction = "", multiScopeState = "" } = {},
526
+ registry,
527
+ requestedLocale,
528
+ ) {
529
+ const pack = localePack(registry, requestedLocale);
530
+ const labels = pack.internalStateLabels || {};
531
+ const result = {};
532
+ if (modeSliceDecision === "verified_change") {
533
+ if (verifiedChangeState === "escalated") {
534
+ result.verified_change = labels.verifiedChangeEscalated || "Escalated to structured delivery";
535
+ } else if (["missing", "draft", "invalid", "eligible", "executed"].includes(verifiedChangeState)) {
536
+ result.verified_change = labels.verifiedChange || "Compact change under review";
537
+ }
538
+ }
539
+ if (contextGraphRequiredAction === "open_gap") {
540
+ result.context_graph = labels.contextGraphOpenGap || "Graph gap open";
541
+ } else if (["link", "update", "create", "resolve_drift"].includes(contextGraphRequiredAction)) {
542
+ result.context_graph = labels.contextGraphMaintained || "Project memory maintained";
543
+ }
544
+ if (multiScopeState === "blocked") {
545
+ result.multi_scope = labels.multiScopeBlocked || "Ambiguous scope, clarification needed";
546
+ }
547
+ return result;
548
+ }
549
+
550
+ export function buildBreadcrumb(breadcrumb, registry, requestedLocale) {
551
+ if (!Array.isArray(breadcrumb)) return "";
552
+ const pack = localePack(registry, requestedLocale);
553
+ const cardLabels = pack.statusCard || {};
554
+ const titles = pack.gateTitles || {};
555
+ const sep = cardLabels.breadcrumbSeparator || " \u00b7 ";
556
+ const symbols = {
557
+ fulfilled: cardLabels.breadcrumbFulfilled || "\u2713",
558
+ current: cardLabels.breadcrumbCurrent || "\u25cf",
559
+ open: cardLabels.breadcrumbOpen || "\u25cb",
560
+ };
561
+ return breadcrumb
562
+ .map((entry) => {
563
+ const title = titles[entry.gate] || entry.gate;
564
+ const symbol = symbols[entry.status] || symbols.open;
565
+ return `${title} ${symbol}`;
566
+ })
567
+ .join(sep);
568
+ }
569
+
570
+ export function buildTransitionNarration(gate, registry, requestedLocale) {
571
+ const pack = localePack(registry, requestedLocale);
572
+ const narration = pack.primary?.narration || {};
573
+ const gateTitles = pack.gateTitles || {};
574
+ const gateTitle = gateTitles[gate] || gate;
575
+ const gateConfig = narration.gates?.[gate] || {};
576
+ const agentNext = gateConfig.agentNext || "";
577
+ const userAction = gateConfig.userAction || narration.noAction || "no user action required now";
578
+ if (!agentNext) return "";
579
+ const gateSatisfiedTemplate = narration.gateSatisfied || "{gate} approved";
580
+ const gateSatisfied = gateSatisfiedTemplate.replace("{gate}", gateTitle);
581
+ return `${gateSatisfied} \u2192 ${agentNext} \u2192 ${userAction}`;
582
+ }
583
+
584
+ export const interactionPresentationConstants = Object.freeze({
585
+ artefactOrder: Object.freeze([...ARTEFACT_ORDER]),
586
+ outcomes: Object.freeze([...OUTCOMES]),
587
+ attemptOutcomes: Object.freeze([...ATTEMPT_OUTCOMES]),
588
+ approvalSequence: APPROVAL_SEQUENCE,
589
+ qualityDimensions: QUALITY_DIMENSIONS,
590
+ });