create-agdf 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,8 +63,8 @@ export function validateLocaleRegistry(registry) {
63
63
  if (JSON.stringify(keys) !== JSON.stringify(baseline)) errors.push(`incomplete_locale:${locale}`);
64
64
  for (const [key, value] of visibleStrings(pack)) {
65
65
  if (!value.trim()) errors.push(`empty_copy:${locale}:${key}`);
66
- const budget = key.startsWith("gateTitles.") ? budgets.title
67
- : key.includes("Description") || key.includes("fallbackReasons") || key.startsWith("primary.actions.") || key.startsWith("primary.afterApproval.") || key.startsWith("primary.narration.") || key.startsWith("gateRationale.") || key.startsWith("interaction.why.") || key === "primary.quality"
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
68
  ? budgets.description
69
69
  : budgets.label;
70
70
  if (Number.isInteger(budget) && value.length > budget) errors.push(`length_budget:${locale}:${key}`);
@@ -72,6 +72,7 @@ export function validateLocaleRegistry(registry) {
72
72
  for (const key of REQUIRED_GATES) {
73
73
  if (typeof pack.gateTitles?.[key] !== "string" || !pack.gateTitles[key].trim()) errors.push(`missing_gate_title:${locale}:${key}`);
74
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}`);
75
76
  }
76
77
  }
77
78
  if (JSON.stringify(registry?.optionOrder) !== JSON.stringify(["approve", "revise", "decline", "cancel"])) errors.push("option_order");
@@ -303,21 +304,24 @@ export function buildApprovalOrientationSnapshot({
303
304
  const gate = String(statusCard.current_gate ?? "").trim();
304
305
  const runId = String(statusCard.run_id ?? "").trim();
305
306
  const expectedApproval = `Approval: ${gate}`;
306
- if (!REQUIRED_GATES.includes(gate) || !runId || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
307
+ if (!REQUIRED_GATES.includes(gate) || !/^[A-Za-z0-9._-]+$/.test(runId) || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
307
308
 
308
309
  const locale = resolvePresentationLocale(registry, requestedLocale);
309
310
  const pack = localePack(registry, locale);
310
- const runTitle = String(humanPresentation.runTitle ?? "").trim() || normalizedRunTitle(runId);
311
+ const runTitle = displaySafeTitle(humanPresentation.runTitle, normalizedRunTitle(runId));
311
312
  const currentGateTitle = String(humanPresentation.gateTitle ?? "").trim() || gateTitle(registry, locale, gate);
312
313
  const primaryHeading = String(pack.gateActionTitles?.[gate] ?? "").trim();
313
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}`);
314
319
  const statusFields = Object.freeze([
315
- Object.freeze({ id: "selected_run", label: pack.statusCard.run, value: `${runTitle} · ${runId}` }),
320
+ Object.freeze({ id: "selected_run", label: pack.statusCard.run, value: `${runTitle} · \`${runId}\`` }),
316
321
  Object.freeze({ id: "readiness_status", label: pack.statusCard.title, value: pack.interaction.ready }),
317
- Object.freeze({ id: "current_gate", label: pack.statusCard.gate, value: `${gate} — ${currentGateTitle}` }),
318
- Object.freeze({ id: "missing_approval", label: pack.statusCard.missing, value: expectedApproval }),
319
- Object.freeze({ id: "next_action", label: pack.statusCard.step, value: pack.primary.actions[gate] ?? String(statusCard.next_step ?? "") }),
320
- Object.freeze({ id: "quality_outlook", label: pack.statusCard.quality, value: pack.primary.quality }),
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 }),
321
325
  ]);
322
326
  const options = Object.freeze(gateOptions(registry, locale, gate).map((option) => Object.freeze({ ...option })));
323
327
 
@@ -337,21 +341,29 @@ export function buildApprovalOrientationSnapshot({
337
341
  }),
338
342
  gate_transition_card: Object.freeze({
339
343
  semantic_block: "gate_transition_card",
340
- title: `${currentGateTitle} · ${runTitle} · ${runId}`,
344
+ title: `${currentGateTitle} · ${runTitle} · \`${runId}\``,
341
345
  artefact_refs: artefactRefs,
342
346
  ready: pack.interaction.ready,
343
347
  approve_heading: pack.interaction.approveHeading,
344
348
  exact_approval: expectedApproval,
345
349
  approval_effect: pack.primary.afterApproval[gate] ?? pack.primary.actions[gate] ?? "",
346
350
  next_heading: pack.interaction.nextHeading,
347
- next_gate: String(statusCard.next_gate_after_approval ?? "none"),
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,
348
361
  }),
349
- approval_interaction: Object.freeze({ semantic_block: "approval_interaction", expected_approval: expectedApproval, options, authorizes: false }),
350
362
  authorizes: false,
351
363
  });
352
364
  }
353
365
 
354
- export function validateApprovalOrientationSnapshot(snapshot) {
366
+ export function validateApprovalOrientationSnapshot(snapshot, { registry, expectedIdentity } = {}) {
355
367
  const errors = [];
356
368
  if (!plainObject(snapshot)) return Object.freeze({ valid: false, errors: Object.freeze(["snapshot_missing"]) });
357
369
  if (JSON.stringify(snapshot.sequence) !== JSON.stringify(APPROVAL_SEQUENCE)) errors.push("sequence");
@@ -362,16 +374,104 @@ export function validateApprovalOrientationSnapshot(snapshot) {
362
374
  if (!heading || snapshot.compact_status_card?.primary_heading_level !== 2) errors.push("primary_heading");
363
375
  if (String(snapshot.compact_status_card?.title ?? "").trim() !== heading) errors.push("primary_heading_owner");
364
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");
365
378
  const fieldIds = snapshot.compact_status_card?.fields?.map((field) => field?.id);
366
- if (JSON.stringify(fieldIds) !== JSON.stringify(["selected_run", "readiness_status", "current_gate", "missing_approval", "next_action", "quality_outlook"])) errors.push("status_fields");
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");
367
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");
368
396
  if (!REQUIRED_GATES.includes(snapshot.current_gate)
369
397
  || snapshot.gate_transition_card?.exact_approval !== expectedApproval
370
398
  || snapshot.approval_interaction?.expected_approval !== expectedApproval
371
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
+ }
372
438
  return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
373
439
  }
374
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
+
375
475
  export function attachApprovalOrientationSnapshot(statusCard, options = {}) {
376
476
  if (!plainObject(statusCard)) throw new Error("approval orientation status card missing");
377
477
  const snapshot = buildApprovalOrientationSnapshot({ ...options, statusCard });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agdf",
3
- "version": "0.9.8",
3
+ "version": "0.10.0",
4
4
  "description": "Bootstrap AGDF repository-local plugin files, Copilot/OpenCode instructions, skills, control files and machine-readable checks.",
5
5
  "type": "module",
6
6
  "files": [