create-agdf 0.8.2 → 0.8.4
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/bin/create-agdf.js +70 -2
- package/generated/.github/skills/agdf-gate-check/SKILL.md +24 -14
- package/generated/.github/skills/agdf-interaction-locales.json +58 -0
- package/generated/.github/skills/agdf-qa-gate/SKILL.md +7 -0
- package/generated/.github/skills/agdf-runtime-contract.md +66 -16
- package/generated/.opencode/AGDF.md +4 -4
- package/generated/.opencode/agdf-interaction-locales.json +58 -0
- package/generated/.opencode/agdf-runtime-contract.md +66 -16
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +24 -14
- package/generated/.opencode/skills/agdf-qa-gate/SKILL.md +7 -0
- package/generated/AGENTS.md +4 -4
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/meta/agdf-agent-router.md +4 -4
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +58 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +8 -8
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +66 -16
- package/generated/plugins/agdf/skills/delivery-path-search/help.md +21 -3
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +24 -14
- package/generated/plugins/agdf/skills/qa-gate/SKILL.md +7 -0
- package/lib/interaction-presentation.js +163 -1
- package/package.json +1 -1
|
@@ -62,16 +62,24 @@ For a ready `gate_approval`:
|
|
|
62
62
|
|
|
63
63
|
1. Resolve exactly one selected run and evaluate the current gate.
|
|
64
64
|
2. Confirm the required durable artefact is present and ready before presenting a question.
|
|
65
|
-
3.
|
|
66
|
-
4.
|
|
67
|
-
5.
|
|
68
|
-
6.
|
|
69
|
-
7.
|
|
70
|
-
8.
|
|
71
|
-
9.
|
|
72
|
-
10.
|
|
73
|
-
11.
|
|
74
|
-
12.
|
|
65
|
+
3. Build one immutable, non-authorizing presentation snapshot from the selected evaluated run and current revision identity.
|
|
66
|
+
4. Compose one Approval Orientation Envelope as one immediately preceding assistant message containing two distinct blocks. Render the compact localized approval-time Run Status Card first. It contains exactly selected run, readiness status, current gate, missing exact approval, one next action and quality outlook; omit raw keys, machine statuses, evidence, diagnostics and allowed/forbidden inventories.
|
|
67
|
+
5. In the same assistant message, render a separate localized Gate Transition Card second, immediately before any native question or exact-text fallback. It includes the deterministic human run title and `UR · PRD · SD · TP` links from the selected run. Never put either card only into the question, button description or hidden tool context. Do not invoke the native question tool until the complete two-card envelope is visible.
|
|
68
|
+
6. Compose the transition card as one human-readable gate title with `run_id`, one ready-for-decision line, one short approval-effect block and one short next-transition block. It must answer only: where am I, what does this decision do, and what happens next. Run and gate identity may anchor both cards; do not duplicate status rows, action inventories or the native question.
|
|
69
|
+
7. Distinguish internal process steps from user decisions in natural language. For example, after `Approval: TP`, say that pre-implementation Brownfield Analysis runs next and that no further user action is required now; do not expose `next_user_gate: none` or `user_action_required: no`, and do not ask for a second approval for Brownfield Analysis.
|
|
70
|
+
8. Ask exactly one question that names the selected `run_id` and `current_gate`.
|
|
71
|
+
9. Offer options in stable order: exact `Approval: <GateName>`, localized revise and localized decline. Add explicit cancel only where supported after decline; otherwise host dismissal maps to cancel. Never preselect, skip, auto-submit or reorder an option.
|
|
72
|
+
10. Use the surface adapter declared in the canonical plugin definition only when it can wait for deliberate input without auto-resolution; otherwise request the same exact approval in concise text.
|
|
73
|
+
11. Render both cards once in that single complete assistant message. If the native attempt is unavailable or not applied, continue to exact text without repeating either card.
|
|
74
|
+
12. Re-run gate evaluation for the same run and expected gate after the response and immediately before persistence.
|
|
75
|
+
13. Reject a missing artefact, ambiguous or wrong run, wrong gate, stale expected gate, timeout/default, hook-supplied answer, agent message, technical permission outcome or plan approval.
|
|
76
|
+
14. Persist accepted input only through the existing control-state workflow.
|
|
77
|
+
|
|
78
|
+
The ready-gate projection must expose `native_attempt_required: true`. Treat this as a mandatory
|
|
79
|
+
orchestration instruction: after rendering the two cards, invoke the configured native question
|
|
80
|
+
adapter exactly once before using the exact-text fallback. A hook, permission result or session
|
|
81
|
+
context message cannot replace that attempt or supply its answer. For every non-ready interaction,
|
|
82
|
+
the signal is absent or false and no approval control may be shown.
|
|
75
83
|
|
|
76
84
|
Before composing the question, resolve the configured chat locale from `.agdf/control/config.json` through `plugin/meta/agdf-interaction-locales.json`: exact complete pack, language subtag, then English fallback. German and English are initial packs, not a closed language list. Keep `Approval: <GateName>` exactly unchanged in every language, and do not mix presentation languages within one interaction. Labels and descriptions use the same resolved locale; host-owned UI chrome remains host-owned and may use a different language.
|
|
77
85
|
|
|
@@ -191,10 +199,12 @@ Keep the result short and operational. Render the Runtime Contract's compact hum
|
|
|
191
199
|
|
|
192
200
|
When an approval is missing, also include `Next gate after approval` and `Allowed after approval` exactly as constrained by the Runtime Contract. Keep forbidden outputs, evidence and next-skill detail in the concise surrounding text when relevant; they are not extra status-card rows.
|
|
193
201
|
|
|
194
|
-
This operational status table
|
|
195
|
-
immediately requests a ready gate approval,
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
This complete operational status table remains the status-reporting and detail
|
|
203
|
+
surface. When the same response immediately requests a ready gate approval,
|
|
204
|
+
render its six-field compact approval-time projection first, then the Gate
|
|
205
|
+
Transition Card in the same immediately preceding assistant message, then
|
|
206
|
+
invoke exactly one native question or exact-text fallback. Both cards derive
|
|
207
|
+
from the same snapshot and are shown exactly once.
|
|
198
208
|
|
|
199
209
|
If this skill creates or updates control artefacts, do not paste full file bodies into the chat.
|
|
200
210
|
List paths, summarize the decision, name the blocker or approval needed, and keep the durable content in the files.
|
|
@@ -28,6 +28,13 @@ QA-specific `decision` is exactly `pass | revise | block`.
|
|
|
28
28
|
`pass` is allowed only when TP coverage, Brownfield fit, solution integrity, and relevant documentation/Context Graph impact are sufficiently evidenced.
|
|
29
29
|
`sot_drift` must not pass silently as a warning.
|
|
30
30
|
|
|
31
|
+
When post-CD+Tests review evidence exists, present the Runtime Contract's derived Quality
|
|
32
|
+
Readiness projection before the detailed QA report: Plan coverage, Solution integrity, Code
|
|
33
|
+
quality and QA decision in that order. Name `qa-gate` as the sole decision owner, show one
|
|
34
|
+
decisive reason and one permissible next action, and keep detailed reports as evidence links or
|
|
35
|
+
on-demand detail. The projection is non-authorizing and must not replace the Run Status Card,
|
|
36
|
+
Gate Transition Card or the durable QA report.
|
|
37
|
+
|
|
31
38
|
## Rules
|
|
32
39
|
1. No QA pass without strong evidence.
|
|
33
40
|
2. TP is the reference, not merely working code.
|
|
@@ -2,6 +2,14 @@ const REQUIRED_GATES = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
|
|
|
2
2
|
const ARTEFACT_ORDER = ["UR", "PRD", "SD", "TP"];
|
|
3
3
|
const OUTCOMES = new Set(["approve", "revise", "decline", "cancel", "no_response", "timeout", "empty", "invalid", "stale"]);
|
|
4
4
|
const ATTEMPT_OUTCOMES = new Set(["presented", "unavailable_before_invocation", "attempted_not_applied", "unsafe_to_wait"]);
|
|
5
|
+
const APPROVAL_SEQUENCE = Object.freeze(["run_status_card", "gate_transition_card", "approval_interaction"]);
|
|
6
|
+
const QUALITY_STATUS_RANK = { pass: 0, warn: 1, revise: 2, block: 3 };
|
|
7
|
+
const QUALITY_DIMENSIONS = Object.freeze([
|
|
8
|
+
Object.freeze({ id: "plan_coverage", owner: "task-plan-review" }),
|
|
9
|
+
Object.freeze({ id: "solution_integrity", owner: "clean-implementation-review" }),
|
|
10
|
+
Object.freeze({ id: "code_quality", owner: "code-review" }),
|
|
11
|
+
Object.freeze({ id: "qa_decision", owner: "qa-gate" }),
|
|
12
|
+
]);
|
|
5
13
|
|
|
6
14
|
function plainObject(value) {
|
|
7
15
|
return value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -55,7 +63,7 @@ export function validateLocaleRegistry(registry) {
|
|
|
55
63
|
for (const [key, value] of visibleStrings(pack)) {
|
|
56
64
|
if (!value.trim()) errors.push(`empty_copy:${locale}:${key}`);
|
|
57
65
|
const budget = key.startsWith("gateTitles.") ? budgets.title
|
|
58
|
-
: key.includes("Description") || key.startsWith("primary.actions.") || key.startsWith("primary.afterApproval.") || key === "primary.quality"
|
|
66
|
+
: key.includes("Description") || key.includes("fallbackReasons") || key.startsWith("primary.actions.") || key.startsWith("primary.afterApproval.") || key === "primary.quality"
|
|
59
67
|
? budgets.description
|
|
60
68
|
: budgets.label;
|
|
61
69
|
if (Number.isInteger(budget) && value.length > budget) errors.push(`length_budget:${locale}:${key}`);
|
|
@@ -139,6 +147,57 @@ export function buildRunCandidates(runs) {
|
|
|
139
147
|
.sort((left, right) => left.display_title.localeCompare(right.display_title) || left.run_id.localeCompare(right.run_id));
|
|
140
148
|
}
|
|
141
149
|
|
|
150
|
+
export function normalizeReconciliationText(value) {
|
|
151
|
+
return String(value ?? "")
|
|
152
|
+
.normalize("NFKC")
|
|
153
|
+
.toLowerCase()
|
|
154
|
+
.replace(/[._:/\\-]+/g, " ")
|
|
155
|
+
.replace(/[^\p{L}\p{N}\s]+/gu, " ")
|
|
156
|
+
.replace(/\s+/g, " ")
|
|
157
|
+
.trim();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function reconcileRunScope({ requestText = "", scopeKey = "", runs = [] } = {}) {
|
|
161
|
+
const requested = normalizeReconciliationText(scopeKey || requestText);
|
|
162
|
+
if (!requested) return Object.freeze({ outcome: "match_uncertain", matches: [], reason: "scope_input_missing" });
|
|
163
|
+
const matches = [...(runs ?? [])]
|
|
164
|
+
.filter((run) => run?.valid && ["active", "completed"].includes(run?.meta?.lifecycle) && typeof run.run_id === "string")
|
|
165
|
+
.filter((run) => {
|
|
166
|
+
const keys = [run.scope_key, run.display_title, run.current_artefact_heading, run.ur_heading]
|
|
167
|
+
.map(normalizeReconciliationText)
|
|
168
|
+
.filter(Boolean);
|
|
169
|
+
return keys.includes(requested);
|
|
170
|
+
})
|
|
171
|
+
.map((run) => Object.freeze({
|
|
172
|
+
run_id: run.run_id,
|
|
173
|
+
lifecycle: run.meta.lifecycle,
|
|
174
|
+
display_title: displaySafeTitle(run.display_title || run.ur_heading || run.run_id, normalizedRunTitle(run.run_id)),
|
|
175
|
+
current_gate: run.control_state?.current_gate || run.meta?.current_gate || "unknown",
|
|
176
|
+
}))
|
|
177
|
+
.sort((left, right) => left.run_id.localeCompare(right.run_id));
|
|
178
|
+
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" });
|
|
179
|
+
return Object.freeze({ outcome: matches[0].lifecycle === "active" ? "active_match" : "completed_match", matches, reason: "exact_match" });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function executeNativeApprovalAttempt({ ready = false, invokeNative, fallback } = {}) {
|
|
183
|
+
if (!ready) return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "gate_not_ready", authorizes: false });
|
|
184
|
+
if (typeof invokeNative !== "function") return Object.freeze({ attempted: false, outcome: "unavailable_before_invocation", reason: "adapter_unavailable", authorizes: false });
|
|
185
|
+
let adapterResult;
|
|
186
|
+
try {
|
|
187
|
+
adapterResult = invokeNative();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
adapterResult = { outcome: "attempted_not_applied", reason: String(error?.message || "adapter_error") };
|
|
190
|
+
}
|
|
191
|
+
const outcome = ATTEMPT_OUTCOMES.has(adapterResult?.outcome)
|
|
192
|
+
? adapterResult.outcome
|
|
193
|
+
: adapterResult?.presented === true || adapterResult?.applied === true
|
|
194
|
+
? "presented"
|
|
195
|
+
: "attempted_not_applied";
|
|
196
|
+
const reason = String(adapterResult?.reason || "").trim();
|
|
197
|
+
if (outcome !== "presented" && typeof fallback === "function") fallback({ outcome, reason });
|
|
198
|
+
return Object.freeze({ attempted: true, outcome, reason, authorizes: false });
|
|
199
|
+
}
|
|
200
|
+
|
|
142
201
|
export function buildInteractionAttempt({ interactionId, runId, currentGate, surface, attemptOutcome, expectedApproval, fallbackReason = "" }) {
|
|
143
202
|
if (!ATTEMPT_OUTCOMES.has(attemptOutcome)) throw new Error("invalid interaction attempt outcome");
|
|
144
203
|
if (!String(interactionId ?? "").trim() || !String(runId ?? "").trim() || !String(currentGate ?? "").trim())
|
|
@@ -203,8 +262,111 @@ export function normalizeInteractionOutcome(value, { expectedApproval, stale = f
|
|
|
203
262
|
return "invalid";
|
|
204
263
|
}
|
|
205
264
|
|
|
265
|
+
export function buildApprovalOrientationSnapshot({
|
|
266
|
+
ready = false,
|
|
267
|
+
statusCard,
|
|
268
|
+
humanPresentation,
|
|
269
|
+
revisionId = "",
|
|
270
|
+
registry,
|
|
271
|
+
requestedLocale,
|
|
272
|
+
} = {}) {
|
|
273
|
+
if (!ready || !plainObject(statusCard) || !plainObject(humanPresentation)) return null;
|
|
274
|
+
const gate = String(statusCard.current_gate ?? "").trim();
|
|
275
|
+
const runId = String(statusCard.run_id ?? "").trim();
|
|
276
|
+
const expectedApproval = `Approval: ${gate}`;
|
|
277
|
+
if (!REQUIRED_GATES.includes(gate) || !runId || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
|
|
278
|
+
|
|
279
|
+
const locale = resolvePresentationLocale(registry, requestedLocale);
|
|
280
|
+
const pack = localePack(registry, locale);
|
|
281
|
+
const runTitle = String(humanPresentation.runTitle ?? "").trim() || normalizedRunTitle(runId);
|
|
282
|
+
const currentGateTitle = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, gate);
|
|
283
|
+
const artefactRefs = Object.freeze([...(humanPresentation.artefactRefs ?? [])].map((ref) => Object.freeze({ ...ref })));
|
|
284
|
+
const statusFields = Object.freeze([
|
|
285
|
+
Object.freeze({ id: "selected_run", label: pack.statusCard.run, value: `${runTitle} · ${runId}` }),
|
|
286
|
+
Object.freeze({ id: "readiness_status", label: pack.statusCard.title, value: pack.interaction.ready }),
|
|
287
|
+
Object.freeze({ id: "current_gate", label: pack.statusCard.gate, value: `${gate} — ${currentGateTitle}` }),
|
|
288
|
+
Object.freeze({ id: "missing_approval", label: pack.statusCard.missing, value: expectedApproval }),
|
|
289
|
+
Object.freeze({ id: "next_action", label: pack.statusCard.step, value: pack.primary.actions[gate] ?? String(statusCard.next_step ?? "") }),
|
|
290
|
+
Object.freeze({ id: "quality_outlook", label: pack.statusCard.quality, value: pack.primary.quality }),
|
|
291
|
+
]);
|
|
292
|
+
const options = Object.freeze(gateOptions(registry, locale, gate).map((option) => Object.freeze({ ...option })));
|
|
293
|
+
|
|
294
|
+
return Object.freeze({
|
|
295
|
+
schema_version: "1",
|
|
296
|
+
run_id: runId,
|
|
297
|
+
revision_id: String(revisionId ?? "").trim(),
|
|
298
|
+
current_gate: gate,
|
|
299
|
+
presentation_language: locale,
|
|
300
|
+
sequence: APPROVAL_SEQUENCE,
|
|
301
|
+
compact_status_card: Object.freeze({ title: pack.statusCard.title, fields: statusFields }),
|
|
302
|
+
gate_transition_card: Object.freeze({
|
|
303
|
+
title: `${currentGateTitle} · ${runTitle} · ${runId}`,
|
|
304
|
+
artefact_refs: artefactRefs,
|
|
305
|
+
ready: pack.interaction.ready,
|
|
306
|
+
approve_heading: pack.interaction.approveHeading,
|
|
307
|
+
exact_approval: expectedApproval,
|
|
308
|
+
approval_effect: pack.primary.afterApproval[gate] ?? pack.primary.actions[gate] ?? "",
|
|
309
|
+
next_heading: pack.interaction.nextHeading,
|
|
310
|
+
next_gate: String(statusCard.next_gate_after_approval ?? "none"),
|
|
311
|
+
}),
|
|
312
|
+
approval_interaction: Object.freeze({ expected_approval: expectedApproval, options, authorizes: false }),
|
|
313
|
+
authorizes: false,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function attachApprovalOrientationSnapshot(statusCard, options = {}) {
|
|
318
|
+
if (!plainObject(statusCard)) throw new Error("approval orientation status card missing");
|
|
319
|
+
const snapshot = buildApprovalOrientationSnapshot({ ...options, statusCard });
|
|
320
|
+
Object.defineProperty(statusCard, "approvalOrientation", {
|
|
321
|
+
value: snapshot,
|
|
322
|
+
enumerable: false,
|
|
323
|
+
});
|
|
324
|
+
return snapshot;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function qualityStatus(value) {
|
|
328
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
329
|
+
return Object.hasOwn(QUALITY_STATUS_RANK, normalized) ? normalized : "unknown";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function buildQualityReadiness({
|
|
333
|
+
planCoverage,
|
|
334
|
+
solutionIntegrity,
|
|
335
|
+
codeQuality,
|
|
336
|
+
qaDecision,
|
|
337
|
+
decisiveReason = "",
|
|
338
|
+
nextAction = "",
|
|
339
|
+
} = {}) {
|
|
340
|
+
const values = [planCoverage, solutionIntegrity, codeQuality, qaDecision];
|
|
341
|
+
const rows = QUALITY_DIMENSIONS.map((dimension, index) => Object.freeze({
|
|
342
|
+
...dimension,
|
|
343
|
+
status: qualityStatus(values[index]),
|
|
344
|
+
}));
|
|
345
|
+
const known = rows.filter((row) => row.status !== "unknown");
|
|
346
|
+
if (known.length === 0) return null;
|
|
347
|
+
const status = rows.some((row) => row.status === "unknown")
|
|
348
|
+
? "revise"
|
|
349
|
+
: known.reduce((current, row) =>
|
|
350
|
+
QUALITY_STATUS_RANK[row.status] > QUALITY_STATUS_RANK[current] ? row.status : current,
|
|
351
|
+
"pass");
|
|
352
|
+
const decisiveRow = rows.find((row) => row.status === status && status !== "pass")
|
|
353
|
+
?? rows.find((row) => row.id === "qa_decision")
|
|
354
|
+
?? rows[0];
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
status,
|
|
357
|
+
rows: Object.freeze(rows),
|
|
358
|
+
decisive_reason: String(decisiveReason ?? "").trim(),
|
|
359
|
+
decisive_dimension: decisiveRow.id,
|
|
360
|
+
next_action: String(nextAction ?? "").trim(),
|
|
361
|
+
decision_owner: "qa-gate",
|
|
362
|
+
authorizes: false,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
206
366
|
export const interactionPresentationConstants = Object.freeze({
|
|
207
367
|
artefactOrder: Object.freeze([...ARTEFACT_ORDER]),
|
|
208
368
|
outcomes: Object.freeze([...OUTCOMES]),
|
|
209
369
|
attemptOutcomes: Object.freeze([...ATTEMPT_OUTCOMES]),
|
|
370
|
+
approvalSequence: APPROVAL_SEQUENCE,
|
|
371
|
+
qualityDimensions: QUALITY_DIMENSIONS,
|
|
210
372
|
});
|
package/package.json
CHANGED