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
|
@@ -0,0 +1,842 @@
|
|
|
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
|
+
const SCOPE_CLASSIFICATION_LIMITS = Object.freeze({
|
|
15
|
+
maxCodePointsPerField: 240,
|
|
16
|
+
minEscalationTriggers: 1,
|
|
17
|
+
maxEscalationTriggers: 3,
|
|
18
|
+
});
|
|
19
|
+
const SCOPE_CLASSIFICATION_MARKDOWN_CONTROL = /[\\`*_\[\]<>|]/;
|
|
20
|
+
const SCOPE_CLASSIFICATION_LINE_STRUCTURE = /^(?:[#>+\-]\s|\d+\.\s)/;
|
|
21
|
+
|
|
22
|
+
function plainObject(value) {
|
|
23
|
+
return value && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function scopeClassificationText(value) {
|
|
27
|
+
if (typeof value !== "string") return null;
|
|
28
|
+
const normalized = value.trim();
|
|
29
|
+
if (!normalized || /[\r\n\u2028\u2029]/.test(value)) return null;
|
|
30
|
+
if (Array.from(normalized).length > SCOPE_CLASSIFICATION_LIMITS.maxCodePointsPerField) return null;
|
|
31
|
+
if (SCOPE_CLASSIFICATION_MARKDOWN_CONTROL.test(normalized)) return null;
|
|
32
|
+
if (SCOPE_CLASSIFICATION_LINE_STRUCTURE.test(normalized)) return null;
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function flattenKeys(value, prefix = "") {
|
|
37
|
+
const keys = [];
|
|
38
|
+
for (const [key, child] of Object.entries(value ?? {})) {
|
|
39
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
40
|
+
if (plainObject(child)) keys.push(...flattenKeys(child, path));
|
|
41
|
+
else keys.push(path);
|
|
42
|
+
}
|
|
43
|
+
return keys.sort();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function visibleStrings(value, prefix = "") {
|
|
47
|
+
const entries = [];
|
|
48
|
+
for (const [key, child] of Object.entries(value ?? {})) {
|
|
49
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
50
|
+
if (plainObject(child)) entries.push(...visibleStrings(child, path));
|
|
51
|
+
else if (typeof child === "string") entries.push([path, child]);
|
|
52
|
+
}
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function canonicalizeLanguageTag(value) {
|
|
57
|
+
const raw = String(value ?? "").trim().replaceAll("_", "-").replace(/\.(.+)$/, "");
|
|
58
|
+
if (!raw || !/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(raw)) return "";
|
|
59
|
+
try {
|
|
60
|
+
return Intl.getCanonicalLocales(raw)[0]?.toLowerCase() ?? "";
|
|
61
|
+
} catch {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function validateLocaleRegistry(registry) {
|
|
67
|
+
const errors = [];
|
|
68
|
+
if (!plainObject(registry) || registry.schemaVersion !== 1) errors.push("schema_version");
|
|
69
|
+
const fallback = canonicalizeLanguageTag(registry?.fallbackLocale);
|
|
70
|
+
const locales = registry?.locales;
|
|
71
|
+
if (!fallback || !plainObject(locales) || !plainObject(locales[fallback])) errors.push("fallback_locale");
|
|
72
|
+
const baseline = plainObject(locales?.[fallback]) ? flattenKeys(locales[fallback]) : [];
|
|
73
|
+
const budgets = registry?.lengthBudgets ?? {};
|
|
74
|
+
for (const [locale, pack] of Object.entries(locales ?? {})) {
|
|
75
|
+
if (!canonicalizeLanguageTag(locale) || !plainObject(pack)) {
|
|
76
|
+
errors.push(`invalid_locale:${locale}`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const keys = flattenKeys(pack);
|
|
80
|
+
if (JSON.stringify(keys) !== JSON.stringify(baseline)) errors.push(`incomplete_locale:${locale}`);
|
|
81
|
+
for (const [key, value] of visibleStrings(pack)) {
|
|
82
|
+
if (!value.trim()) errors.push(`empty_copy:${locale}:${key}`);
|
|
83
|
+
const budget = key.startsWith("gateTitles.") || key.startsWith("gateActionTitles.") ? budgets.title
|
|
84
|
+
: 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)
|
|
85
|
+
? budgets.description
|
|
86
|
+
: budgets.label;
|
|
87
|
+
if (Number.isInteger(budget) && value.length > budget) errors.push(`length_budget:${locale}:${key}`);
|
|
88
|
+
}
|
|
89
|
+
for (const key of REQUIRED_GATES) {
|
|
90
|
+
if (typeof pack.gateTitles?.[key] !== "string" || !pack.gateTitles[key].trim()) errors.push(`missing_gate_title:${locale}:${key}`);
|
|
91
|
+
if (typeof pack.gateActionTitles?.[key] !== "string" || !pack.gateActionTitles[key].trim()) errors.push(`missing_gate_action_title:${locale}:${key}`);
|
|
92
|
+
if (typeof pack.gateRequiredDecisions?.[key] !== "string" || !pack.gateRequiredDecisions[key].trim()) errors.push(`missing_gate_required_decision:${locale}:${key}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (JSON.stringify(registry?.optionOrder) !== JSON.stringify(["approve", "revise", "decline", "cancel"])) errors.push("option_order");
|
|
96
|
+
return { valid: errors.length === 0, errors };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolvePresentationLocale(registry, requestedLocale) {
|
|
100
|
+
const validation = validateLocaleRegistry(registry);
|
|
101
|
+
if (!validation.valid) throw new Error(`Invalid AGDF interaction locale registry: ${validation.errors.join(",")}`);
|
|
102
|
+
const fallback = canonicalizeLanguageTag(registry.fallbackLocale);
|
|
103
|
+
const requested = canonicalizeLanguageTag(requestedLocale);
|
|
104
|
+
if (requested && registry.locales[requested]) return requested;
|
|
105
|
+
const language = requested.split("-")[0];
|
|
106
|
+
if (language && registry.locales[language]) return language;
|
|
107
|
+
return fallback;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function localePack(registry, requestedLocale) {
|
|
111
|
+
return registry.locales[resolvePresentationLocale(registry, requestedLocale)];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function gateTitle(registry, requestedLocale, gate) {
|
|
115
|
+
return localePack(registry, requestedLocale).gateTitles[gate] || String(gate || "");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function gateRationale(registry, requestedLocale, gate) {
|
|
119
|
+
return localePack(registry, requestedLocale).gateRationale?.[gate] || String(gate || "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function stripMarkdown(value) {
|
|
123
|
+
return String(value ?? "")
|
|
124
|
+
.replace(/^#+\s*/, "")
|
|
125
|
+
.replace(/[`*_]/g, "")
|
|
126
|
+
.trim();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function section(content, heading) {
|
|
130
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
131
|
+
return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`))?.[1]?.trim() ?? "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function normalizedRunTitle(runId) {
|
|
135
|
+
const words = String(runId ?? "")
|
|
136
|
+
.replace(/[._-]+/g, " ")
|
|
137
|
+
.replace(/\s+/g, " ")
|
|
138
|
+
.trim();
|
|
139
|
+
return words ? words.replace(/\b\p{L}/gu, (letter) => letter.toUpperCase()) : "AGDF run";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function resolveHumanRunTitle({ currentArtefactHeading, urHeading, runContent, runId }) {
|
|
143
|
+
const objective = section(String(runContent ?? ""), "Objective").split(/\r?\n/).map(stripMarkdown).find(Boolean) ?? "";
|
|
144
|
+
return [currentArtefactHeading, urHeading, objective].map(stripMarkdown).find(Boolean) || normalizedRunTitle(runId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function displaySafeTitle(value, fallback) {
|
|
148
|
+
const title = stripMarkdown(value).replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
149
|
+
return (title || fallback).slice(0, 100);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function buildRunCandidates(runs) {
|
|
153
|
+
return [...(runs ?? [])]
|
|
154
|
+
.filter((run) => run?.valid && run?.meta?.lifecycle === "active" && typeof run.run_id === "string")
|
|
155
|
+
.map((run) => {
|
|
156
|
+
const currentGate = run.control_state?.current_gate || run.meta?.current_gate || "unknown";
|
|
157
|
+
const title = resolveHumanRunTitle({
|
|
158
|
+
currentArtefactHeading: run.current_artefact_heading,
|
|
159
|
+
urHeading: run.ur_heading,
|
|
160
|
+
runContent: "",
|
|
161
|
+
runId: run.run_id,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
run_id: run.run_id,
|
|
165
|
+
display_title: displaySafeTitle(title, normalizedRunTitle(run.run_id)),
|
|
166
|
+
current_gate: currentGate,
|
|
167
|
+
next_allowed_action: String(run.control_state?.next_allowed_action ?? "").trim(),
|
|
168
|
+
revision_id: String(run.meta?.revision_id ?? "").trim(),
|
|
169
|
+
};
|
|
170
|
+
})
|
|
171
|
+
.sort((left, right) => left.display_title.localeCompare(right.display_title) || left.run_id.localeCompare(right.run_id));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function normalizeReconciliationText(value) {
|
|
175
|
+
return String(value ?? "")
|
|
176
|
+
.normalize("NFKC")
|
|
177
|
+
.toLowerCase()
|
|
178
|
+
.replace(/[._:/\\-]+/g, " ")
|
|
179
|
+
.replace(/[^\p{L}\p{N}\s]+/gu, " ")
|
|
180
|
+
.replace(/\s+/g, " ")
|
|
181
|
+
.trim();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function reconcileRunScope({ requestText = "", scopeKey = "", runs = [] } = {}) {
|
|
185
|
+
const requested = normalizeReconciliationText(scopeKey || requestText);
|
|
186
|
+
if (!requested) return Object.freeze({ outcome: "match_uncertain", matches: [], reason: "scope_input_missing" });
|
|
187
|
+
const matches = [...(runs ?? [])]
|
|
188
|
+
.filter((run) => run?.valid && ["active", "completed"].includes(run?.meta?.lifecycle) && typeof run.run_id === "string")
|
|
189
|
+
.filter((run) => {
|
|
190
|
+
const keys = [run.scope_key, run.display_title, run.current_artefact_heading, run.ur_heading]
|
|
191
|
+
.map(normalizeReconciliationText)
|
|
192
|
+
.filter(Boolean);
|
|
193
|
+
return keys.includes(requested);
|
|
194
|
+
})
|
|
195
|
+
.map((run) => Object.freeze({
|
|
196
|
+
run_id: run.run_id,
|
|
197
|
+
lifecycle: run.meta.lifecycle,
|
|
198
|
+
display_title: displaySafeTitle(run.display_title || run.ur_heading || run.run_id, normalizedRunTitle(run.run_id)),
|
|
199
|
+
current_gate: run.control_state?.current_gate || run.meta?.current_gate || "unknown",
|
|
200
|
+
}))
|
|
201
|
+
.sort((left, right) => left.run_id.localeCompare(right.run_id));
|
|
202
|
+
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" });
|
|
203
|
+
return Object.freeze({ outcome: matches[0].lifecycle === "active" ? "active_match" : "completed_match", matches, reason: "exact_match" });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function evaluateNativeApprovalCapability({ staticCapability, runtimeCapability } = {}) {
|
|
207
|
+
const staticValue = staticCapability?.approvalValueTransport;
|
|
208
|
+
const runtimeValue = runtimeCapability?.approvalValueTransport;
|
|
209
|
+
if (runtimeCapability && staticCapability && (runtimeValue !== staticValue || runtimeCapability.waitSafety !== staticCapability.waitSafety)) {
|
|
210
|
+
return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unavailable_before_invocation", reason: "capability_conflict", evidence_source: "runtime" });
|
|
211
|
+
}
|
|
212
|
+
const capability = runtimeCapability ?? staticCapability;
|
|
213
|
+
const evidenceSource = runtimeCapability ? "runtime" : staticCapability ? "static" : "none";
|
|
214
|
+
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 });
|
|
215
|
+
if (capability.waitSafety === "unsafe") return Object.freeze({ eligible: false, native_attempt_required: false, preflight_outcome: "unsafe_to_wait", reason: "unsafe_wait", evidence_source: evidenceSource });
|
|
216
|
+
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 });
|
|
217
|
+
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 });
|
|
218
|
+
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 });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function executeNativeApprovalAttempt({ ready = false, orientationSnapshot, capabilityPreflight, invokeNative, fallback } = {}) {
|
|
222
|
+
if (!ready) return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "gate_not_ready", authorizes: false });
|
|
223
|
+
const preflight = validateApprovalOrientationSnapshot(orientationSnapshot);
|
|
224
|
+
if (!preflight.valid) return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "orientation_preflight_failed", authorizes: false });
|
|
225
|
+
if (!capabilityPreflight?.eligible) {
|
|
226
|
+
const outcome = capabilityPreflight?.preflight_outcome === "unsafe_to_wait" ? "unsafe_to_wait" : "unavailable_before_invocation";
|
|
227
|
+
const reason = capabilityPreflight?.reason || "capability_missing";
|
|
228
|
+
if (typeof fallback === "function") fallback({ outcome, reason });
|
|
229
|
+
return Object.freeze({ attempted: false, outcome, reason, authorizes: false });
|
|
230
|
+
}
|
|
231
|
+
if (typeof invokeNative !== "function") return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "adapter_unavailable", authorizes: false });
|
|
232
|
+
let adapterResult;
|
|
233
|
+
try {
|
|
234
|
+
adapterResult = invokeNative();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
adapterResult = { outcome: "attempted_not_applied", reason: String(error?.message || "adapter_error") };
|
|
237
|
+
}
|
|
238
|
+
const outcome = ATTEMPT_OUTCOMES.has(adapterResult?.outcome)
|
|
239
|
+
? adapterResult.outcome
|
|
240
|
+
: adapterResult?.presented === true || adapterResult?.applied === true
|
|
241
|
+
? "presented"
|
|
242
|
+
: "attempted_not_applied";
|
|
243
|
+
const reason = String(adapterResult?.reason || "").trim();
|
|
244
|
+
if (outcome !== "presented" && typeof fallback === "function") fallback({ outcome, reason });
|
|
245
|
+
return Object.freeze({ attempted: true, outcome, reason, authorizes: false });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function buildInteractionAttempt({ interactionId, runId, currentGate, surface, attemptOutcome, expectedApproval, fallbackReason = "" }) {
|
|
249
|
+
if (!ATTEMPT_OUTCOMES.has(attemptOutcome)) throw new Error("invalid interaction attempt outcome");
|
|
250
|
+
if (!String(interactionId ?? "").trim() || !String(runId ?? "").trim() || !String(currentGate ?? "").trim())
|
|
251
|
+
throw new Error("interaction attempt identity missing");
|
|
252
|
+
return Object.freeze({
|
|
253
|
+
interaction_id: String(interactionId), run_id: String(runId), current_gate: String(currentGate),
|
|
254
|
+
surface: String(surface ?? "fallback"), attempt_outcome: attemptOutcome,
|
|
255
|
+
expected_approval: String(expectedApproval ?? ""), fallback_reason: String(fallbackReason ?? ""),
|
|
256
|
+
authorizes: false,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function safeArtefactPath(value) {
|
|
261
|
+
const path = String(value ?? "").replace(/^`|`$/g, "").trim();
|
|
262
|
+
if (!path || !/^[A-Za-z0-9._/-]+$/.test(path) || /^([a-z]+:|\/)/i.test(path)) return "";
|
|
263
|
+
const parts = path.replaceAll("\\", "/").split("/");
|
|
264
|
+
if (parts.includes("..")) return "";
|
|
265
|
+
return path;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function buildArtefactRefs(artefacts, registry, requestedLocale, { pathExists } = {}) {
|
|
269
|
+
const pack = localePack(registry, requestedLocale);
|
|
270
|
+
return ARTEFACT_ORDER.map((type) => {
|
|
271
|
+
const artefact = artefacts instanceof Map ? artefacts.get(type) : artefacts?.[type];
|
|
272
|
+
const candidatePath = safeArtefactPath(artefact?.path);
|
|
273
|
+
const path = candidatePath && (!pathExists || pathExists(candidatePath, type)) ? candidatePath : "";
|
|
274
|
+
return {
|
|
275
|
+
type,
|
|
276
|
+
exists: Boolean(path),
|
|
277
|
+
status: String(artefact?.status ?? ""),
|
|
278
|
+
path,
|
|
279
|
+
label: type,
|
|
280
|
+
missingLabel: `${type} (${pack.artefacts.notYetCreated})`,
|
|
281
|
+
};
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function formatArtefactRefs(refs) {
|
|
286
|
+
return refs.map((ref) => ref.exists ? `[${ref.label}](${ref.path})` : ref.missingLabel).join(" · ");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function markdownCell(value) {
|
|
290
|
+
return String(value ?? "")
|
|
291
|
+
.replaceAll("\\", "\\\\")
|
|
292
|
+
.replaceAll("|", "\\|")
|
|
293
|
+
.replaceAll("&", "&")
|
|
294
|
+
.replaceAll("<", "<")
|
|
295
|
+
.replaceAll(">", ">")
|
|
296
|
+
.replace(/\r?\n/g, "<br>");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function operationalList(value, none) {
|
|
300
|
+
if (!Array.isArray(value) || value.length === 0) return none;
|
|
301
|
+
return value.map((item) => String(item ?? "").trim()).filter(Boolean).join("\n") || none;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function operationalBreadcrumb(value, labels) {
|
|
305
|
+
if (!Array.isArray(value) || value.length === 0) return "";
|
|
306
|
+
const marker = {
|
|
307
|
+
fulfilled: labels.breadcrumbFulfilled,
|
|
308
|
+
current: labels.breadcrumbCurrent,
|
|
309
|
+
open: labels.breadcrumbOpen,
|
|
310
|
+
};
|
|
311
|
+
return value.map((item) => `${marker[item?.status] ?? labels.breadcrumbOpen} ${String(item?.gate ?? "").trim()}`)
|
|
312
|
+
.filter((item) => item.trim())
|
|
313
|
+
.join(labels.breadcrumbSeparator);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function renderOperationalStatusCard(statusCard, {
|
|
317
|
+
registry,
|
|
318
|
+
humanPresentation,
|
|
319
|
+
revisionId = "",
|
|
320
|
+
} = {}) {
|
|
321
|
+
if (!plainObject(statusCard) || !plainObject(humanPresentation)) return null;
|
|
322
|
+
const runId = String(statusCard.run_id ?? "").trim();
|
|
323
|
+
const currentGate = String(statusCard.current_gate ?? "").trim();
|
|
324
|
+
const revision = String(revisionId ?? "").trim() || "unversioned";
|
|
325
|
+
const locale = resolvePresentationLocale(registry, statusCard.presentation_language);
|
|
326
|
+
if (!runId || !currentGate || !locale) return null;
|
|
327
|
+
|
|
328
|
+
const pack = localePack(registry, locale);
|
|
329
|
+
const labels = pack.statusCard;
|
|
330
|
+
const primary = pack.primary;
|
|
331
|
+
const none = primary.none;
|
|
332
|
+
const gateLabel = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, currentGate);
|
|
333
|
+
const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
|
|
334
|
+
const breadcrumb = operationalBreadcrumb(statusCard.breadcrumb, labels);
|
|
335
|
+
const rows = [
|
|
336
|
+
[labels.run, `${runTitle} · \`${runId}\``],
|
|
337
|
+
...(breadcrumb ? [[labels.breadcrumb, breadcrumb]] : []),
|
|
338
|
+
[labels.status, primary.status[statusCard.status] ?? statusCard.status],
|
|
339
|
+
[labels.gate, `${gateLabel} (\`${currentGate}\`)`],
|
|
340
|
+
[labels.allowed, operationalList(statusCard.allowed_now, none)],
|
|
341
|
+
[labels.forbidden, operationalList(statusCard.forbidden_now, none)],
|
|
342
|
+
[labels.blocked, statusCard.blocking_condition === "none" ? none : statusCard.blocking_condition],
|
|
343
|
+
[labels.missing, statusCard.missing_approval === "none" ? none : statusCard.missing_approval],
|
|
344
|
+
[labels.nextGate, statusCard.next_gate_after_approval === "none" ? none : statusCard.next_gate_after_approval],
|
|
345
|
+
[labels.allowedAfter, statusCard.allowed_after_approval === "none" ? none : statusCard.allowed_after_approval],
|
|
346
|
+
[labels.step, statusCard.next_step || none],
|
|
347
|
+
[labels.quality, statusCard.quality_outlook || none],
|
|
348
|
+
];
|
|
349
|
+
const markdown = [
|
|
350
|
+
`## ${labels.title}`,
|
|
351
|
+
"",
|
|
352
|
+
`| ${labels.field} | ${labels.value} |`,
|
|
353
|
+
"|---|---|",
|
|
354
|
+
...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
|
|
355
|
+
].join("\n");
|
|
356
|
+
|
|
357
|
+
return Object.freeze({
|
|
358
|
+
schema_version: "1",
|
|
359
|
+
semantic_block: "run_status_card",
|
|
360
|
+
run_id: runId,
|
|
361
|
+
revision_id: revision,
|
|
362
|
+
current_gate: currentGate,
|
|
363
|
+
presentation_language: locale,
|
|
364
|
+
markdown,
|
|
365
|
+
authorizes: false,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function renderTaskTargetOrientation(resolution, {
|
|
370
|
+
registry,
|
|
371
|
+
requestedLocale,
|
|
372
|
+
} = {}) {
|
|
373
|
+
if (!plainObject(resolution)) return null;
|
|
374
|
+
|
|
375
|
+
const resolutionState = String(resolution.resolution_state ?? "").trim();
|
|
376
|
+
const reasonCode = String(resolution.reason_code ?? "").trim();
|
|
377
|
+
const primaryTarget = String(resolution.primary_target ?? "").trim();
|
|
378
|
+
const governanceTarget = String(resolution.governance_target ?? "").trim();
|
|
379
|
+
const workingDirectory = String(resolution.working_directory ?? "").trim();
|
|
380
|
+
const nextAction = String(resolution.next_action ?? "").trim();
|
|
381
|
+
if (typeof resolution.target_changed !== "boolean") return null;
|
|
382
|
+
const targetChanged = resolution.target_changed === true;
|
|
383
|
+
const evidenceSources = Array.isArray(resolution.evidence_sources)
|
|
384
|
+
? resolution.evidence_sources.map((item) => String(item ?? "").trim()).filter(Boolean)
|
|
385
|
+
: null;
|
|
386
|
+
|
|
387
|
+
const resolvedReasons = new Set(["explicit_target", "continued_target"]);
|
|
388
|
+
const unresolvedReasons = new Set([
|
|
389
|
+
"multiple_plausible_targets",
|
|
390
|
+
"target_content_mismatch",
|
|
391
|
+
"target_unavailable",
|
|
392
|
+
"no_reliable_target",
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
if (!["resolved", "unresolved"].includes(resolutionState)) return null;
|
|
396
|
+
if (!workingDirectory || evidenceSources === null) return null;
|
|
397
|
+
if (resolutionState === "resolved") {
|
|
398
|
+
if (!resolvedReasons.has(reasonCode) || !primaryTarget || nextAction) return null;
|
|
399
|
+
} else if (!unresolvedReasons.has(reasonCode) || primaryTarget || governanceTarget || !nextAction || targetChanged) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let locale;
|
|
404
|
+
try {
|
|
405
|
+
locale = resolvePresentationLocale(registry, requestedLocale);
|
|
406
|
+
} catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
if (!locale) return null;
|
|
410
|
+
|
|
411
|
+
const pack = localePack(registry, locale);
|
|
412
|
+
const labels = pack?.taskTargetResolution;
|
|
413
|
+
if (!plainObject(labels) || !plainObject(labels.reasonCodes)) return null;
|
|
414
|
+
const reason = labels.reasonCodes[reasonCode];
|
|
415
|
+
if (!reason) return null;
|
|
416
|
+
|
|
417
|
+
const rows = resolutionState === "resolved"
|
|
418
|
+
? [
|
|
419
|
+
[labels.state, labels.resolved],
|
|
420
|
+
[labels.primaryTarget, primaryTarget],
|
|
421
|
+
[labels.governanceTarget, governanceTarget || labels.none],
|
|
422
|
+
[labels.evidenceSources, evidenceSources.join("; ") || labels.none],
|
|
423
|
+
[labels.workingDirectory, workingDirectory],
|
|
424
|
+
[labels.reason, reason],
|
|
425
|
+
...(targetChanged ? [[labels.targetChanged, labels.yes]] : []),
|
|
426
|
+
]
|
|
427
|
+
: [
|
|
428
|
+
[labels.state, labels.unresolved],
|
|
429
|
+
[labels.reason, reason],
|
|
430
|
+
[labels.evidenceSources, evidenceSources.join("; ") || labels.none],
|
|
431
|
+
[labels.workingDirectory, workingDirectory],
|
|
432
|
+
[labels.nextAction, nextAction],
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
const markdown = [
|
|
436
|
+
`## ${labels.title}`,
|
|
437
|
+
"",
|
|
438
|
+
`| ${labels.field} | ${labels.value} |`,
|
|
439
|
+
"|---|---|",
|
|
440
|
+
...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
|
|
441
|
+
].join("\n");
|
|
442
|
+
|
|
443
|
+
return Object.freeze({
|
|
444
|
+
schema_version: "1",
|
|
445
|
+
semantic_block: "task_target_orientation",
|
|
446
|
+
presentation_language: locale,
|
|
447
|
+
resolution_state: resolutionState,
|
|
448
|
+
reason_code: reasonCode,
|
|
449
|
+
markdown,
|
|
450
|
+
authorizes: false,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function renderScopeClassificationCard(classification, {
|
|
455
|
+
registry,
|
|
456
|
+
requestedLocale,
|
|
457
|
+
} = {}) {
|
|
458
|
+
if (!plainObject(classification)) return null;
|
|
459
|
+
const { outcome, mode, trivial_boundary: trivialBoundary } = classification;
|
|
460
|
+
if (outcome !== "ungated" || mode !== "quick_task") return null;
|
|
461
|
+
if (!["inside", "outside"].includes(trivialBoundary)) return null;
|
|
462
|
+
|
|
463
|
+
const urTriggerEvaluation = scopeClassificationText(classification.ur_trigger_evaluation);
|
|
464
|
+
const allowedSummary = scopeClassificationText(classification.allowed_summary);
|
|
465
|
+
const forbiddenSummary = scopeClassificationText(classification.forbidden_summary);
|
|
466
|
+
const challengePath = scopeClassificationText(classification.challenge_path);
|
|
467
|
+
if (!urTriggerEvaluation || !allowedSummary || !forbiddenSummary || !challengePath) return null;
|
|
468
|
+
|
|
469
|
+
if (!Array.isArray(classification.escalation_triggers)
|
|
470
|
+
|| classification.escalation_triggers.length < SCOPE_CLASSIFICATION_LIMITS.minEscalationTriggers
|
|
471
|
+
|| classification.escalation_triggers.length > SCOPE_CLASSIFICATION_LIMITS.maxEscalationTriggers) return null;
|
|
472
|
+
const escalationTriggers = classification.escalation_triggers.map(scopeClassificationText);
|
|
473
|
+
if (escalationTriggers.some((item) => !item)) return null;
|
|
474
|
+
if (new Set(escalationTriggers).size !== escalationTriggers.length) return null;
|
|
475
|
+
|
|
476
|
+
let locale;
|
|
477
|
+
try {
|
|
478
|
+
locale = resolvePresentationLocale(registry, requestedLocale);
|
|
479
|
+
} catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
if (!locale) return null;
|
|
483
|
+
|
|
484
|
+
const pack = localePack(registry, locale);
|
|
485
|
+
const labels = pack?.scopeClassification;
|
|
486
|
+
if (!plainObject(labels)) return null;
|
|
487
|
+
|
|
488
|
+
const modeLabel = labels.mode?.[mode] ?? mode;
|
|
489
|
+
const boundaryLabel = labels.boundary?.[trivialBoundary] ?? trivialBoundary;
|
|
490
|
+
|
|
491
|
+
const rows = [
|
|
492
|
+
[labels.classification, `${modeLabel} · ${boundaryLabel}`],
|
|
493
|
+
[labels.grounds, urTriggerEvaluation],
|
|
494
|
+
[labels.allowed, allowedSummary],
|
|
495
|
+
[labels.forbidden, forbiddenSummary],
|
|
496
|
+
[labels.escalation, escalationTriggers.join("; ")],
|
|
497
|
+
[labels.challenge, challengePath],
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
const markdown = [
|
|
501
|
+
`## ${labels.title ?? "AGDF scope classification"}`,
|
|
502
|
+
"",
|
|
503
|
+
`| ${labels.field ?? "Field"} | ${labels.value ?? "Value"} |`,
|
|
504
|
+
"|---|---|",
|
|
505
|
+
...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
|
|
506
|
+
].join("\n");
|
|
507
|
+
|
|
508
|
+
return Object.freeze({
|
|
509
|
+
schema_version: "1",
|
|
510
|
+
semantic_block: "scope_classification_card",
|
|
511
|
+
presentation_language: locale,
|
|
512
|
+
markdown,
|
|
513
|
+
authorizes: false,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function gateOptions(registry, requestedLocale, gate, { includeCancel = false } = {}) {
|
|
518
|
+
const pack = localePack(registry, requestedLocale);
|
|
519
|
+
const options = [
|
|
520
|
+
{ outcome: "approve", label: `Approval: ${gate}`, description: pack.interaction.approveDescription, value: `Approval: ${gate}`, authorizes: true },
|
|
521
|
+
{ outcome: "revise", label: pack.interaction.reviseLabel, description: pack.interaction.reviseDescription, value: "revise", authorizes: false },
|
|
522
|
+
{ outcome: "decline", label: pack.interaction.declineLabel, description: pack.interaction.declineDescription, value: "decline", authorizes: false },
|
|
523
|
+
];
|
|
524
|
+
if (includeCancel) options.push({ outcome: "cancel", label: pack.interaction.cancelLabel, description: pack.interaction.cancelDescription, value: "cancel", authorizes: false });
|
|
525
|
+
return options;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export function normalizeInteractionOutcome(value, { expectedApproval, stale = false, timedOut = false, noResponse = false } = {}) {
|
|
529
|
+
if (stale) return "stale";
|
|
530
|
+
if (timedOut) return "timeout";
|
|
531
|
+
if (noResponse || value === undefined || value === null) return "no_response";
|
|
532
|
+
if (typeof value !== "string") return "invalid";
|
|
533
|
+
const normalized = value.trim();
|
|
534
|
+
if (!normalized) return "empty";
|
|
535
|
+
if (normalized === expectedApproval) return "approve";
|
|
536
|
+
if (OUTCOMES.has(normalized) && normalized !== "approve") return normalized;
|
|
537
|
+
return "invalid";
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function buildApprovalOrientationSnapshot({
|
|
541
|
+
ready = false,
|
|
542
|
+
statusCard,
|
|
543
|
+
humanPresentation,
|
|
544
|
+
revisionId = "",
|
|
545
|
+
registry,
|
|
546
|
+
requestedLocale,
|
|
547
|
+
} = {}) {
|
|
548
|
+
if (!ready || !plainObject(statusCard) || !plainObject(humanPresentation)) return null;
|
|
549
|
+
const gate = String(statusCard.current_gate ?? "").trim();
|
|
550
|
+
const runId = String(statusCard.run_id ?? "").trim();
|
|
551
|
+
const expectedApproval = `Approval: ${gate}`;
|
|
552
|
+
if (!REQUIRED_GATES.includes(gate) || !/^[A-Za-z0-9._-]+$/.test(runId) || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
|
|
553
|
+
|
|
554
|
+
const locale = resolvePresentationLocale(registry, requestedLocale);
|
|
555
|
+
const pack = localePack(registry, locale);
|
|
556
|
+
const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
|
|
557
|
+
const currentGateTitle = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, gate);
|
|
558
|
+
const primaryHeading = String(pack.gateActionTitles?.[gate] ?? "").trim();
|
|
559
|
+
const artefactRefs = Object.freeze([...(humanPresentation.artefactRefs ?? [])].map((ref) => Object.freeze({ ...ref })));
|
|
560
|
+
const nextGate = String(statusCard.next_gate_after_approval ?? "none");
|
|
561
|
+
const nextUserGate = String(statusCard.next_user_gate ?? "").trim();
|
|
562
|
+
const userActionRequired = String(statusCard.user_action_required ?? "").trim();
|
|
563
|
+
const userActionSemanticsValid = (userActionRequired === "no" && nextUserGate === "none")
|
|
564
|
+
|| (userActionRequired === "yes" && REQUIRED_GATES.includes(nextUserGate) && nextGate === nextUserGate);
|
|
565
|
+
if (!userActionSemanticsValid) return null;
|
|
566
|
+
const narration = pack.primary.narration.gates[gate] ?? {};
|
|
567
|
+
const nextDecision = userActionRequired === "no"
|
|
568
|
+
? pack.primary.narration.noAction
|
|
569
|
+
: narration.userAction || `${pack.gateTitles[nextUserGate] ?? nextUserGate} ${pack.interaction.decisionFollows}`;
|
|
570
|
+
const statusFields = Object.freeze([
|
|
571
|
+
Object.freeze({ id: "selected_run", label: pack.statusCard.run, value: `${runTitle} · \`${runId}\`` }),
|
|
572
|
+
Object.freeze({ id: "readiness_status", label: pack.statusCard.title, value: pack.interaction.ready }),
|
|
573
|
+
Object.freeze({ id: "current_gate", label: pack.statusCard.gate, value: `${currentGateTitle} (\`${gate}\`)` }),
|
|
574
|
+
Object.freeze({ id: "required_decision", label: pack.statusCard.requiredDecision, value: pack.gateRequiredDecisions[gate] }),
|
|
575
|
+
Object.freeze({ id: "next_action", label: pack.statusCard.step, value: pack.interaction.decisionInstruction }),
|
|
576
|
+
]);
|
|
577
|
+
const options = Object.freeze(gateOptions(registry, locale, gate).map((option) => Object.freeze({ ...option })));
|
|
578
|
+
|
|
579
|
+
return Object.freeze({
|
|
580
|
+
schema_version: "1",
|
|
581
|
+
run_id: runId,
|
|
582
|
+
revision_id: String(revisionId ?? "").trim(),
|
|
583
|
+
current_gate: gate,
|
|
584
|
+
presentation_language: locale,
|
|
585
|
+
sequence: APPROVAL_SEQUENCE,
|
|
586
|
+
compact_status_card: Object.freeze({
|
|
587
|
+
semantic_block: "run_status_card",
|
|
588
|
+
title: primaryHeading,
|
|
589
|
+
primary_heading: primaryHeading,
|
|
590
|
+
primary_heading_level: 2,
|
|
591
|
+
fields: statusFields,
|
|
592
|
+
}),
|
|
593
|
+
gate_transition_card: Object.freeze({
|
|
594
|
+
semantic_block: "gate_transition_card",
|
|
595
|
+
title: `${currentGateTitle} · ${runTitle} · \`${runId}\``,
|
|
596
|
+
artefact_refs: artefactRefs,
|
|
597
|
+
ready: pack.interaction.ready,
|
|
598
|
+
approve_heading: pack.interaction.approveHeading,
|
|
599
|
+
exact_approval: expectedApproval,
|
|
600
|
+
approval_effect: pack.primary.afterApproval[gate] ?? pack.primary.actions[gate] ?? "",
|
|
601
|
+
next_heading: pack.interaction.nextHeading,
|
|
602
|
+
next_gate: nextGate,
|
|
603
|
+
next_transition: `${narration.agentNext ?? pack.primary.actions[gate] ?? ""}. ${nextDecision}.`,
|
|
604
|
+
}),
|
|
605
|
+
approval_interaction: Object.freeze({
|
|
606
|
+
semantic_block: "approval_interaction",
|
|
607
|
+
prompt: pack.interaction.decisionPrompt.replace("{gate}", currentGateTitle).replace("{run_id}", runId),
|
|
608
|
+
expected_approval: expectedApproval,
|
|
609
|
+
options,
|
|
610
|
+
exact_text_fallback: pack.interaction.exactTextRequest.replace("{approval}", expectedApproval),
|
|
611
|
+
authorizes: false,
|
|
612
|
+
}),
|
|
613
|
+
authorizes: false,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function validateApprovalOrientationSnapshot(snapshot, { registry, expectedIdentity } = {}) {
|
|
618
|
+
const errors = [];
|
|
619
|
+
if (!plainObject(snapshot)) return Object.freeze({ valid: false, errors: Object.freeze(["snapshot_missing"]) });
|
|
620
|
+
if (JSON.stringify(snapshot.sequence) !== JSON.stringify(APPROVAL_SEQUENCE)) errors.push("sequence");
|
|
621
|
+
if (snapshot.compact_status_card?.semantic_block !== "run_status_card") errors.push("run_status_card");
|
|
622
|
+
if (snapshot.gate_transition_card?.semantic_block !== "gate_transition_card") errors.push("gate_transition_card");
|
|
623
|
+
if (snapshot.approval_interaction?.semantic_block !== "approval_interaction") errors.push("approval_interaction");
|
|
624
|
+
const heading = String(snapshot.compact_status_card?.primary_heading ?? "").trim();
|
|
625
|
+
if (!heading || snapshot.compact_status_card?.primary_heading_level !== 2) errors.push("primary_heading");
|
|
626
|
+
if (String(snapshot.compact_status_card?.title ?? "").trim() !== heading) errors.push("primary_heading_owner");
|
|
627
|
+
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");
|
|
628
|
+
if (/^(?:approve|accept|authorize|freigeben|genehmigen|abnehmen)\b|(?:freigeben|genehmigen|abnehmen)$/i.test(heading)) errors.push("approval_biased_heading");
|
|
629
|
+
const fieldIds = snapshot.compact_status_card?.fields?.map((field) => field?.id);
|
|
630
|
+
if (JSON.stringify(fieldIds) !== JSON.stringify(["selected_run", "readiness_status", "current_gate", "required_decision", "next_action"])) errors.push("status_fields");
|
|
631
|
+
if (!String(snapshot.revision_id ?? "").trim()) errors.push("revision_identity");
|
|
632
|
+
const runIdentitySuffix = ` · \`${String(snapshot.run_id ?? "")}\``;
|
|
633
|
+
if (!String(snapshot.compact_status_card?.fields?.[0]?.value ?? "").endsWith(runIdentitySuffix)
|
|
634
|
+
|| !String(snapshot.gate_transition_card?.title ?? "").endsWith(runIdentitySuffix)) errors.push("identity_projection");
|
|
635
|
+
const expectedApproval = `Approval: ${String(snapshot.current_gate ?? "").trim()}`;
|
|
636
|
+
const inCardApprovalCount = JSON.stringify({
|
|
637
|
+
compact_status_card: snapshot.compact_status_card,
|
|
638
|
+
gate_transition_card: snapshot.gate_transition_card,
|
|
639
|
+
}).split(expectedApproval).length - 1;
|
|
640
|
+
if (inCardApprovalCount !== 1) errors.push("approval_card_occurrence");
|
|
641
|
+
const artefactOrder = snapshot.gate_transition_card?.artefact_refs?.map((ref) => ref?.type);
|
|
642
|
+
if (JSON.stringify(artefactOrder) !== JSON.stringify(ARTEFACT_ORDER)) errors.push("artefact_order");
|
|
643
|
+
if (snapshot.gate_transition_card?.artefact_refs?.some((ref) => ref?.exists && safeArtefactPath(ref.path) !== ref.path)) errors.push("unsafe_artefact_ref");
|
|
644
|
+
if (!String(snapshot.gate_transition_card?.approval_effect ?? "").trim() || !String(snapshot.gate_transition_card?.next_transition ?? "").trim()) errors.push("transition_content");
|
|
645
|
+
if (!String(snapshot.approval_interaction?.prompt ?? "").trim() || !String(snapshot.approval_interaction?.exact_text_fallback ?? "").includes(expectedApproval)) errors.push("approval_interaction_copy");
|
|
646
|
+
if (JSON.stringify(snapshot.approval_interaction?.options?.map((option) => option?.outcome)) !== JSON.stringify(["approve", "revise", "decline"])) errors.push("option_order");
|
|
647
|
+
if (!REQUIRED_GATES.includes(snapshot.current_gate)
|
|
648
|
+
|| snapshot.gate_transition_card?.exact_approval !== expectedApproval
|
|
649
|
+
|| snapshot.approval_interaction?.expected_approval !== expectedApproval
|
|
650
|
+
|| snapshot.approval_interaction?.options?.[0]?.value !== expectedApproval) errors.push("canonical_approval");
|
|
651
|
+
if (plainObject(expectedIdentity)) {
|
|
652
|
+
if (["run_id", "revision_id", "current_gate", "presentation_language"]
|
|
653
|
+
.some((key) => String(snapshot[key] ?? "") !== String(expectedIdentity[key] ?? ""))) errors.push("stale_identity");
|
|
654
|
+
}
|
|
655
|
+
if (plainObject(registry) && REQUIRED_GATES.includes(snapshot.current_gate)) {
|
|
656
|
+
const locale = String(snapshot.presentation_language ?? "");
|
|
657
|
+
const pack = localePack(registry, locale);
|
|
658
|
+
const gate = snapshot.current_gate;
|
|
659
|
+
const fields = snapshot.compact_status_card?.fields ?? [];
|
|
660
|
+
const localizedOptions = gateOptions(registry, locale, gate);
|
|
661
|
+
const localizedGateTitle = gateTitle(registry, locale, gate);
|
|
662
|
+
const nextGate = String(snapshot.gate_transition_card?.next_gate ?? "none");
|
|
663
|
+
const narration = pack.primary.narration.gates[gate] ?? {};
|
|
664
|
+
const nextDecision = REQUIRED_GATES.includes(nextGate)
|
|
665
|
+
? narration.userAction || `${pack.gateTitles[nextGate] ?? nextGate} ${pack.interaction.decisionFollows}`
|
|
666
|
+
: pack.primary.narration.noAction;
|
|
667
|
+
const nextTransition = `${narration.agentNext ?? pack.primary.actions[gate] ?? ""}. ${nextDecision}.`;
|
|
668
|
+
const localeConsistent = resolvePresentationLocale(registry, locale) === locale
|
|
669
|
+
&& heading === pack.gateActionTitles?.[gate]
|
|
670
|
+
&& fields[0]?.label === pack.statusCard.run
|
|
671
|
+
&& fields[1]?.label === pack.statusCard.title
|
|
672
|
+
&& fields[1]?.value === pack.interaction.ready
|
|
673
|
+
&& fields[2]?.label === pack.statusCard.gate
|
|
674
|
+
&& fields[2]?.value === `${localizedGateTitle} (\`${gate}\`)`
|
|
675
|
+
&& fields[3]?.label === pack.statusCard.requiredDecision
|
|
676
|
+
&& fields[3]?.value === pack.gateRequiredDecisions?.[gate]
|
|
677
|
+
&& fields[4]?.label === pack.statusCard.step
|
|
678
|
+
&& fields[4]?.value === pack.interaction.decisionInstruction
|
|
679
|
+
&& String(snapshot.gate_transition_card?.title ?? "").startsWith(`${localizedGateTitle} · `)
|
|
680
|
+
&& snapshot.gate_transition_card?.ready === pack.interaction.ready
|
|
681
|
+
&& snapshot.gate_transition_card?.approve_heading === pack.interaction.approveHeading
|
|
682
|
+
&& snapshot.gate_transition_card?.approval_effect === (pack.primary.afterApproval?.[gate] ?? pack.primary.actions?.[gate] ?? "")
|
|
683
|
+
&& snapshot.gate_transition_card?.next_heading === pack.interaction.nextHeading
|
|
684
|
+
&& snapshot.gate_transition_card?.next_transition === nextTransition
|
|
685
|
+
&& snapshot.approval_interaction?.prompt === pack.interaction.decisionPrompt.replace("{gate}", localizedGateTitle).replace("{run_id}", snapshot.run_id)
|
|
686
|
+
&& snapshot.approval_interaction?.exact_text_fallback === pack.interaction.exactTextRequest.replace("{approval}", expectedApproval)
|
|
687
|
+
&& JSON.stringify(snapshot.approval_interaction?.options) === JSON.stringify(localizedOptions);
|
|
688
|
+
if (!localeConsistent) errors.push("locale_consistency");
|
|
689
|
+
}
|
|
690
|
+
return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export function renderApprovalOrientationSnapshot(snapshot, validation = {}) {
|
|
694
|
+
if (!validateApprovalOrientationSnapshot(snapshot, validation).valid) return null;
|
|
695
|
+
const statusMarkdown = [
|
|
696
|
+
`## ${snapshot.compact_status_card.primary_heading}`,
|
|
697
|
+
...snapshot.compact_status_card.fields.map((field) => `${field.label}: ${field.value}`),
|
|
698
|
+
].join("\n");
|
|
699
|
+
const transition = snapshot.gate_transition_card;
|
|
700
|
+
const transitionMarkdown = [
|
|
701
|
+
transition.title,
|
|
702
|
+
formatArtefactRefs(transition.artefact_refs),
|
|
703
|
+
transition.ready,
|
|
704
|
+
"",
|
|
705
|
+
transition.approve_heading,
|
|
706
|
+
`\`${transition.exact_approval}\` ${transition.approval_effect}`,
|
|
707
|
+
"",
|
|
708
|
+
transition.next_heading,
|
|
709
|
+
transition.next_transition,
|
|
710
|
+
].join("\n");
|
|
711
|
+
return Object.freeze({
|
|
712
|
+
schema_version: "1",
|
|
713
|
+
run_id: snapshot.run_id,
|
|
714
|
+
revision_id: snapshot.revision_id,
|
|
715
|
+
current_gate: snapshot.current_gate,
|
|
716
|
+
presentation_language: snapshot.presentation_language,
|
|
717
|
+
sequence: snapshot.sequence,
|
|
718
|
+
blocks: Object.freeze({
|
|
719
|
+
run_status_card: Object.freeze({ markdown: statusMarkdown }),
|
|
720
|
+
gate_transition_card: Object.freeze({ markdown: transitionMarkdown }),
|
|
721
|
+
}),
|
|
722
|
+
approval_interaction: snapshot.approval_interaction,
|
|
723
|
+
authorizes: false,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function attachApprovalOrientationSnapshot(statusCard, options = {}) {
|
|
728
|
+
if (!plainObject(statusCard)) throw new Error("approval orientation status card missing");
|
|
729
|
+
const snapshot = buildApprovalOrientationSnapshot({ ...options, statusCard });
|
|
730
|
+
Object.defineProperty(statusCard, "approvalOrientation", {
|
|
731
|
+
value: snapshot,
|
|
732
|
+
enumerable: false,
|
|
733
|
+
});
|
|
734
|
+
return snapshot;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function qualityStatus(value) {
|
|
738
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
739
|
+
return Object.hasOwn(QUALITY_STATUS_RANK, normalized) ? normalized : "unknown";
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function buildQualityReadiness({
|
|
743
|
+
planCoverage,
|
|
744
|
+
solutionIntegrity,
|
|
745
|
+
codeQuality,
|
|
746
|
+
qaDecision,
|
|
747
|
+
decisiveReason = "",
|
|
748
|
+
nextAction = "",
|
|
749
|
+
} = {}) {
|
|
750
|
+
const values = [planCoverage, solutionIntegrity, codeQuality, qaDecision];
|
|
751
|
+
const rows = QUALITY_DIMENSIONS.map((dimension, index) => Object.freeze({
|
|
752
|
+
...dimension,
|
|
753
|
+
status: qualityStatus(values[index]),
|
|
754
|
+
}));
|
|
755
|
+
const known = rows.filter((row) => row.status !== "unknown");
|
|
756
|
+
if (known.length === 0) return null;
|
|
757
|
+
const status = rows.some((row) => row.status === "unknown")
|
|
758
|
+
? "revise"
|
|
759
|
+
: known.reduce((current, row) =>
|
|
760
|
+
QUALITY_STATUS_RANK[row.status] > QUALITY_STATUS_RANK[current] ? row.status : current,
|
|
761
|
+
"pass");
|
|
762
|
+
const decisiveRow = rows.find((row) => row.status === status && status !== "pass")
|
|
763
|
+
?? rows.find((row) => row.id === "qa_decision")
|
|
764
|
+
?? rows[0];
|
|
765
|
+
return Object.freeze({
|
|
766
|
+
status,
|
|
767
|
+
rows: Object.freeze(rows),
|
|
768
|
+
decisive_reason: String(decisiveReason ?? "").trim(),
|
|
769
|
+
decisive_dimension: decisiveRow.id,
|
|
770
|
+
next_action: String(nextAction ?? "").trim(),
|
|
771
|
+
decision_owner: "qa-gate",
|
|
772
|
+
authorizes: false,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export function collapseInternalState(
|
|
777
|
+
{ modeSliceDecision = "", verifiedChangeState = "", contextGraphRequiredAction = "", multiScopeState = "" } = {},
|
|
778
|
+
registry,
|
|
779
|
+
requestedLocale,
|
|
780
|
+
) {
|
|
781
|
+
const pack = localePack(registry, requestedLocale);
|
|
782
|
+
const labels = pack.internalStateLabels || {};
|
|
783
|
+
const result = {};
|
|
784
|
+
if (modeSliceDecision === "verified_change") {
|
|
785
|
+
if (verifiedChangeState === "escalated") {
|
|
786
|
+
result.verified_change = labels.verifiedChangeEscalated || "Escalated to structured delivery";
|
|
787
|
+
} else if (["missing", "draft", "invalid", "eligible", "executed"].includes(verifiedChangeState)) {
|
|
788
|
+
result.verified_change = labels.verifiedChange || "Compact change under review";
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (contextGraphRequiredAction === "open_gap") {
|
|
792
|
+
result.context_graph = labels.contextGraphOpenGap || "Graph gap open";
|
|
793
|
+
} else if (["link", "update", "create", "resolve_drift"].includes(contextGraphRequiredAction)) {
|
|
794
|
+
result.context_graph = labels.contextGraphMaintained || "Project memory maintained";
|
|
795
|
+
}
|
|
796
|
+
if (multiScopeState === "blocked") {
|
|
797
|
+
result.multi_scope = labels.multiScopeBlocked || "Ambiguous scope, clarification needed";
|
|
798
|
+
}
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export function buildBreadcrumb(breadcrumb, registry, requestedLocale) {
|
|
803
|
+
if (!Array.isArray(breadcrumb)) return "";
|
|
804
|
+
const pack = localePack(registry, requestedLocale);
|
|
805
|
+
const cardLabels = pack.statusCard || {};
|
|
806
|
+
const titles = pack.gateTitles || {};
|
|
807
|
+
const sep = cardLabels.breadcrumbSeparator || " \u00b7 ";
|
|
808
|
+
const symbols = {
|
|
809
|
+
fulfilled: cardLabels.breadcrumbFulfilled || "\u2713",
|
|
810
|
+
current: cardLabels.breadcrumbCurrent || "\u25cf",
|
|
811
|
+
open: cardLabels.breadcrumbOpen || "\u25cb",
|
|
812
|
+
};
|
|
813
|
+
return breadcrumb
|
|
814
|
+
.map((entry) => {
|
|
815
|
+
const title = titles[entry.gate] || entry.gate;
|
|
816
|
+
const symbol = symbols[entry.status] || symbols.open;
|
|
817
|
+
return `${title} ${symbol}`;
|
|
818
|
+
})
|
|
819
|
+
.join(sep);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export function buildTransitionNarration(gate, registry, requestedLocale) {
|
|
823
|
+
const pack = localePack(registry, requestedLocale);
|
|
824
|
+
const narration = pack.primary?.narration || {};
|
|
825
|
+
const gateTitles = pack.gateTitles || {};
|
|
826
|
+
const gateTitle = gateTitles[gate] || gate;
|
|
827
|
+
const gateConfig = narration.gates?.[gate] || {};
|
|
828
|
+
const agentNext = gateConfig.agentNext || "";
|
|
829
|
+
const userAction = gateConfig.userAction || narration.noAction || "no user action required now";
|
|
830
|
+
if (!agentNext) return "";
|
|
831
|
+
const gateSatisfiedTemplate = narration.gateSatisfied || "{gate} approved";
|
|
832
|
+
const gateSatisfied = gateSatisfiedTemplate.replace("{gate}", gateTitle);
|
|
833
|
+
return `${gateSatisfied} \u2192 ${agentNext} \u2192 ${userAction}`;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
export const interactionPresentationConstants = Object.freeze({
|
|
837
|
+
artefactOrder: Object.freeze([...ARTEFACT_ORDER]),
|
|
838
|
+
outcomes: Object.freeze([...OUTCOMES]),
|
|
839
|
+
attemptOutcomes: Object.freeze([...ATTEMPT_OUTCOMES]),
|
|
840
|
+
approvalSequence: APPROVAL_SEQUENCE,
|
|
841
|
+
qualityDimensions: QUALITY_DIMENSIONS,
|
|
842
|
+
});
|