create-agdf 0.31.0 → 0.32.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.
Files changed (121) hide show
  1. package/generated/.opencode/agdf-interaction-locales.json +148 -4
  2. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  3. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  4. package/generated/plugins/agdf/companion/events.mjs +5 -3
  5. package/generated/plugins/agdf/companion/figure.mjs +241 -110
  6. package/generated/plugins/agdf/companion/localization.mjs +5 -0
  7. package/generated/plugins/agdf/companion/notice.mjs +10 -2
  8. package/generated/plugins/agdf/companion/register.mjs +90 -32
  9. package/generated/plugins/agdf/companion/run-state.mjs +13 -1
  10. package/generated/plugins/agdf/companion/view.mjs +149 -19
  11. package/generated/plugins/agdf/mcp/server/package.json +2 -2
  12. package/generated/plugins/agdf/mcp/server/src/generation-runtime.js +6 -4
  13. package/generated/plugins/agdf/mcp/server/src/generation.js +8 -2
  14. package/generated/plugins/agdf/mcp/server/src/main.js +4 -1
  15. package/generated/plugins/agdf/mcp/server/src/server.js +19 -5
  16. package/generated/plugins/agdf/mcp/server/ui/cockpit.html +130 -33
  17. package/generated/plugins/agdf/mcp/server/ui/decision-manifest.json +3 -3
  18. package/generated/plugins/agdf/mcp/server/ui/decision.html +3 -3
  19. package/generated/plugins/agdf/mcp/server/ui/manifest.json +3 -3
  20. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  21. package/generated/plugins/agdf/meta/agdf-mcp-capability.json +1 -1
  22. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  23. package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
  24. package/generated/plugins/agdf/meta/distribution-profile-history.json +7 -0
  25. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  26. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +4 -1
  28. package/generated/plugins/agdf/runtime/create-agdf/lib/mcp-worker-runtime.js +77 -77
  29. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  30. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/events.js +4 -1
  31. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/figure-state.js +6 -4
  32. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/journal.js +6 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/companion/run-view.js +54 -11
  34. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  35. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  36. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-list.js +10 -8
  37. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-session.js +11 -3
  38. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit-work.js +4 -2
  39. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/cockpit.js +43 -13
  40. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/work-situation.js +25 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-read/cockpit-worker.js +1 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-create-command.js +6 -1
  43. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  44. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory.js +12 -27
  45. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-title.js +43 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/interaction-catalog-validation.js +2 -1
  48. package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +5 -9
  49. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  50. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  51. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  52. package/generated/plugins/agdf/submission/openai/release-notes.md +5 -8
  53. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  54. package/generated/plugins/agdf-planning/.codex-plugin/plugin.json +1 -1
  55. package/generated/plugins/agdf-planning/plugin.json +1 -1
  56. package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +76 -58
  57. package/generated/plugins/copilot/agdf/mcp/server/package.json +2 -2
  58. package/generated/plugins/copilot/agdf/mcp/server/src/generation-runtime.js +6 -4
  59. package/generated/plugins/copilot/agdf/mcp/server/src/generation.js +8 -2
  60. package/generated/plugins/copilot/agdf/mcp/server/src/main.js +4 -1
  61. package/generated/plugins/copilot/agdf/mcp/server/src/server.js +19 -5
  62. package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
  63. package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +7 -0
  64. package/generated/plugins/copilot/agdf/plugin.json +1 -1
  65. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +148 -4
  66. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  67. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +4 -1
  68. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/mcp-worker-runtime.js +62 -62
  69. package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
  70. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/events.js +4 -1
  71. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/figure-state.js +6 -4
  72. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/journal.js +6 -0
  73. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/companion/run-view.js +54 -11
  74. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  75. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  76. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/work-situation.js +25 -0
  77. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-create-command.js +6 -1
  78. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  79. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-inventory.js +12 -27
  80. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  81. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-title.js +43 -0
  82. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/interaction-catalog-validation.js +2 -1
  83. package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +5 -9
  84. package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
  85. package/generated/plugins/copilot/agdf/skills/agdf-interaction-locales.json +1 -1
  86. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  87. package/generated/submissions/openai/agdf/meta/agdf-interaction-locales.json +148 -4
  88. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  89. package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
  90. package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +7 -0
  91. package/generated/submissions/openai/agdf/plugin.json +1 -1
  92. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  93. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  94. package/generated/submissions/openai/agdf/submission/openai/inventory.json +13 -13
  95. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  96. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  97. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  98. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +5 -8
  99. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  100. package/lib/mcp-dispatch-runtime.js +4 -1
  101. package/lib/mcp-worker-runtime.js +77 -77
  102. package/package.json +1 -1
  103. package/runtime/core/lib/companion/events.js +4 -1
  104. package/runtime/core/lib/companion/figure-state.js +6 -4
  105. package/runtime/core/lib/companion/journal.js +6 -0
  106. package/runtime/core/lib/companion/run-view.js +54 -11
  107. package/runtime/core/lib/control-evaluation/decision-content-placeholder.js +4 -1
  108. package/runtime/core/lib/control-evaluation/gate-check.js +5 -1
  109. package/runtime/core/lib/control-inspect/cockpit-list.js +10 -8
  110. package/runtime/core/lib/control-inspect/cockpit-session.js +11 -3
  111. package/runtime/core/lib/control-inspect/cockpit-work.js +4 -2
  112. package/runtime/core/lib/control-inspect/cockpit.js +43 -13
  113. package/runtime/core/lib/control-inspect/work-situation.js +25 -0
  114. package/runtime/core/lib/control-read/cockpit-worker.js +1 -0
  115. package/runtime/core/lib/control-state/run-create-command.js +6 -1
  116. package/runtime/core/lib/control-state/run-inventory-reader.js +50 -3
  117. package/runtime/core/lib/control-state/run-inventory.js +12 -27
  118. package/runtime/core/lib/control-state/run-presentation-render.js +29 -0
  119. package/runtime/core/lib/control-state/run-title.js +43 -0
  120. package/runtime/core/lib/interaction-catalog-validation.js +2 -1
  121. package/runtime/core/lib/interaction-presentation.js +5 -9
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agdf/local-validator-runtime",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "imports": {
@@ -70,6 +70,8 @@ export function commandGate(command = {}) {
70
70
  const payload = command.payload ?? {};
71
71
  if (command.kind === 'closeout.record') return 'OR';
72
72
  if (command.kind === 'step.record') return payload.internal_step ?? RUN_STEP_SLOTS[payload.step] ?? null;
73
+ if (command.kind === 'verified_change.record') return 'Verified Change';
74
+ if (command.kind === 'ur_draft.replace') return 'UR';
73
75
  if (command.kind === 'operation.recover') return null;
74
76
  return typeof payload.gate === 'string' ? payload.gate : null;
75
77
  }
@@ -83,7 +85,8 @@ function successKind(command, result) {
83
85
  if (kind === 'approval.record') return 'approval_recorded';
84
86
  if (kind === 'closeout.record') return 'run_completed';
85
87
  if (kind === 'material.stage' && command.payload?.action === 'seal') return 'material_staged';
86
- if (kind === 'step.record' || kind === 'artefact.record') return 'document_recorded';
88
+ // Every document recording of run-steps.recordRunStep (run-command-service.js).
89
+ if (['step.record', 'artefact.record', 'verified_change.record', 'ur_draft.replace'].includes(kind)) return 'document_recorded';
87
90
  return null;
88
91
  }
89
92
 
@@ -21,16 +21,18 @@ export function deriveFigureState(entry) {
21
21
  if (!entry) return { mood: 'bereit', reason: { key: 'idle' } };
22
22
  if (entry.valid === false) return { mood: 'blockiert', reason: { key: 'unreadable', reason: entry.unreadable_reason ?? 'run_state_invalid' } };
23
23
  if (entry.evaluation_error) return { mood: 'blockiert', reason: { key: 'unreadable', reason: entry.evaluation_error } };
24
- if (entry.lifecycle === 'completed') return { mood: 'fertig', reason: { key: 'done' } };
24
+ // A completed run whose evaluation is blocked is not done: the block is shown like any other.
25
+ const blocked = entry.evaluated === true && (entry.status === 'blocked' || Boolean(entry.blocking_reason && entry.blocking_reason !== 'none'));
26
+ // A completed run without confirmed completion is still shown, but claims nothing: its own reason.
27
+ if (entry.lifecycle === 'completed' && !blocked) return entry.completion_confirmed === true
28
+ ? { mood: 'fertig', reason: { key: 'done' } } : { mood: 'bereit', reason: { key: 'completionUnconfirmed' } };
25
29
  if (ENDED_LIFECYCLES.includes(entry.lifecycle)) return { mood: 'bereit', reason: { key: 'ended' } };
26
30
  if (!entry.evaluated) return { mood: 'bereit', reason: { key: 'idle' } };
27
31
  const gate = entry.current_gate;
28
- if (entry.status === 'blocked' || (entry.blocking_reason && entry.blocking_reason !== 'none'))
29
- return { mood: 'blockiert', reason: { key: 'blocked', gate, decision: entry.blocking_reason } };
32
+ if (blocked) return { mood: 'blockiert', reason: { key: 'blocked', gate, decision: entry.blocking_reason } };
30
33
  const approval = pendingApproval(entry.missing_approval);
31
34
  if (approval) return { mood: 'wartet', reason: { key: 'waitingApproval', gate: approval } };
32
35
  if (entry.user_action_required === 'yes') return { mood: 'wartet', reason: { key: 'waitingDecision', gate } };
33
- if (entry.status === 'pass') return { mood: 'fertig', reason: { key: 'done' } };
34
36
  if (STRUCTURED_MODES.includes(entry.mode) && BUILD_GATES.includes(gate)) return { mood: 'baut', reason: { key: 'building', gate } };
35
37
  return { mood: 'laeuft', reason: { key: 'working', gate } };
36
38
  }
@@ -160,6 +160,12 @@ export function createCompanionJournal({ dataRoot = process.env[COMPANION_DATA_R
160
160
  return Object.freeze({
161
161
  enabled,
162
162
  path: enabled ? journalPath(dataRoot) : null,
163
+ // Whether `observe` writes an event for this result, without writing: a facade asks this for a
164
+ // result its generation produced, and so already journaled.
165
+ observes(toolName, input, result) {
166
+ if (!enabled) return false;
167
+ try { return Boolean(eventKind(toolName, input, result)); } catch { return false; }
168
+ },
163
169
  observe(toolName, input, result) {
164
170
  if (!enabled) return false;
165
171
  try {
@@ -4,8 +4,12 @@
4
4
  // view sees exactly what the status card shows. Nothing here writes or authorizes.
5
5
  import { existsSync, realpathSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
- import { readFileSync } from '../control-read/fs.js';
7
+ import { readFileSync, statSync } from '../control-read/fs.js';
8
8
  import { captureControlScope } from '../control-read/snapshot.js';
9
+ import { containedRegularFile, hasSymlinkComponent, isSafeControlRelativePath } from '../control-state/contained-file.js';
10
+ import { parseControlState } from '../control-state/run-state-parser.js';
11
+ import { runIdentity } from '../control-state/run-title.js';
12
+ import { localizedDecisionFields } from '../control-evaluation/decision-content-readiness.js';
9
13
  import { evaluateGateCheck } from '../control-evaluation/gate-check.js';
10
14
  import { readRunInventory } from '../control-state/run-inventory-reader.js';
11
15
  import { runFolderEntries } from '../control-state/run-state-reader.js';
@@ -15,7 +19,7 @@ import { runRelativePath } from '../control-state/run-identity.js';
15
19
  import { artefactFileDigest } from '../control-state/run-seal-compute.js';
16
20
  import { boundedCode } from '../control-inspect/run-classification.js';
17
21
  import { localizeBlockingCondition, localizeOperationalValue } from '../interaction-presentation.js';
18
- import { interactionLocales } from '../resources/context.js';
22
+ import { interactionLocales, resolveArtifactPresentationLanguages } from '../resources/context.js';
19
23
  import { deriveFigureState } from './figure-state.js';
20
24
 
21
25
  export const RUN_VIEW_SCHEMA = 'run-view/1';
@@ -43,13 +47,50 @@ export function projectDiagnosis(entries) {
43
47
  return { unreadable, counts: { unreadable: unreadable.length, readable: entries.length - unreadable.length, by_reason } };
44
48
  }
45
49
 
50
+ const NAMING_TEXT_LIMIT = 500;
51
+ const UR_READ_LIMIT = 131072;
52
+ const bounded = value => (typeof value === 'string' && value.length > NAMING_TEXT_LIMIT ? `${value.slice(0, NAMING_TEXT_LIMIT - 1)}…` : value);
53
+
54
+ // How a run names itself to a person, read inside the run's own snapshot: the stable title of the
55
+ // shared Run title rule (registered UR heading, then the authored Objective; a bare Run ID is no
56
+ // title) and the goal in the display language. Where display and artefact language differ, the goal
57
+ // is the one the UR's localized approval summary states. An unreadable UR leaves both to the Run document.
58
+ export function readRunNaming(root, runId, language = 'en') {
59
+ let content;
60
+ try { content = readFileSync(join(root, runRelativePath(runId, '', 'RUN_STATE.md')), 'utf8'); } catch { return null; }
61
+ let urHeading = '', localizedGoal = null;
62
+ try {
63
+ const path = String(parseControlState(content).artefacts.get('UR')?.path ?? '').replace(/^`|`$/gu, '').trim();
64
+ if (isSafeControlRelativePath(path) && path.startsWith('.agdf/control/') && !hasSymlinkComponent(root, path)) {
65
+ const file = containedRegularFile(root, path);
66
+ if (file.status === 'valid' && statSync(file.path).size <= UR_READ_LIMIT) {
67
+ const text = readFileSync(file.path, 'utf8');
68
+ urHeading = (text.match(/^#\s+(.+)$/mu)?.[1] ?? '').replace(/^UR:\s*/iu, '').trim();
69
+ const sourceLanguage = resolveArtifactPresentationLanguages(root, language).artifact_language;
70
+ localizedGoal = localizedDecisionFields('UR', text, { locale: String(language).split('-')[0], sourceLanguage, path }).fields.goal ?? null;
71
+ }
72
+ }
73
+ } catch (error) { if (error?.code === 'source_changed') throw error; }
74
+ const identity = runIdentity({ runId, runContent: content, urHeading });
75
+ return { run_title: identity.title_source === 'run_id' ? null : bounded(identity.title),
76
+ run_title_source: identity.title_source, goal: bounded(localizedGoal ?? identity.objective) };
77
+ }
78
+
46
79
  // One run entry: inventory facts plus, when evaluated, the status card fields of its report.
47
- export function projectRunEntry(inventoryEntry, report, { registry = interactionLocales, language = 'en' } = {}) {
80
+ // `naming` is readRunNaming's result for an evaluated run; without it the inventory names the run.
81
+ export function projectRunEntry(inventoryEntry, report, { registry = interactionLocales, language = 'en', naming = null } = {}) {
82
+ const authored = inventoryEntry.title_source === 'objective' ? inventoryEntry.title ?? null : null;
48
83
  const base = {
49
84
  run_id: inventoryEntry.run_id,
50
85
  title: inventoryEntry.title ?? null,
51
- // The authored objective only; a template objective or a run-id title is no goal (CR finding C2).
52
- objective: inventoryEntry.title_source === 'objective' ? inventoryEntry.title ?? null : null,
86
+ // The stable human title, the same at every gate; null while only the Run ID could name the run.
87
+ run_title: naming ? naming.run_title : authored,
88
+ run_title_source: naming?.run_title_source ?? (authored ? 'objective' : 'run_id'),
89
+ // The goal in the display language where the UR states one, otherwise the authored objective.
90
+ goal: naming ? naming.goal : inventoryEntry.objective ?? null,
91
+ // The authored objective only, from the shared Run title rule; a template objective or a
92
+ // bare Run ID is no goal (CR finding C2).
93
+ objective: inventoryEntry.objective ?? null,
53
94
  lifecycle: inventoryEntry.lifecycle ?? null,
54
95
  valid: inventoryEntry.valid !== false,
55
96
  revision: inventoryEntry.revision ?? null,
@@ -66,6 +107,7 @@ export function projectRunEntry(inventoryEntry, report, { registry = interaction
66
107
  ...base,
67
108
  title: card.humanPresentation?.runTitle ?? base.title,
68
109
  evaluated: true,
110
+ completion_confirmed: base.lifecycle === 'completed' && report.completion?.fulfilled === true,
69
111
  status: card.status,
70
112
  current_gate: card.current_gate,
71
113
  path: card.breadcrumb.map(({ gate, status }) => ({ gate, status })),
@@ -122,9 +164,10 @@ export function readRunDrift(root, runId) {
122
164
  return drift;
123
165
  }
124
166
 
125
- // Active and unreadable runs are evaluated; terminal runs are summarized unless requested by id,
126
- // so a large history does not cost one evaluation per run.
127
- const needsEvaluation = (entry, requested) => requested.has(entry.run_id) || !entry.valid || entry.lifecycle === 'active';
167
+ // Active runs are evaluated; terminal and unreadable runs are summarized unless requested by id, so
168
+ // neither a large history nor many unreadable runs cost one evaluation per run. An unreadable run's
169
+ // reason and figure come from the inventory findings.
170
+ const needsEvaluation = (entry, requested) => requested.has(entry.run_id) || (entry.valid && entry.lifecycle === 'active');
128
171
 
129
172
  // A write that lands during a capture invalidates it (source_changed); the read is repeated a bounded
130
173
  // number of times before the change is reported (CR delta review RV-1).
@@ -152,12 +195,12 @@ export function readRunView(root, { language = 'en', runIds = [] } = {}) {
152
195
  try {
153
196
  return captured(readRoot, base => {
154
197
  const report = evaluateGateCheck(base, { runId, ignoreRunIdEnv: true, presentationLanguage: language });
155
- return { report, drift: report?.status_card ? readRunDrift(base, runId) : null };
198
+ return { report, drift: report?.status_card ? readRunDrift(base, runId) : null, naming: readRunNaming(base, runId, language) };
156
199
  });
157
- } catch (error) { return { report: { evaluation_error: boundedCode(error) }, drift: null }; }
200
+ } catch (error) { return { report: { evaluation_error: boundedCode(error) }, drift: null, naming: null }; }
158
201
  };
159
202
  const entryOf = (inventoryEntry, evaluated) => {
160
- const entry = projectRunEntry(inventoryEntry, evaluated?.report ?? null, { language });
203
+ const entry = projectRunEntry(inventoryEntry, evaluated?.report ?? null, { language, naming: evaluated?.naming ?? null });
161
204
  return entry.evaluated && entry.valid !== false && evaluated.drift ? { ...entry, drift: evaluated.drift } : entry;
162
205
  };
163
206
  let inventory = null, store;
@@ -2,8 +2,11 @@
2
2
  // run inventory and other `node:`-free readers share the same rule.
3
3
  const prompts = new Set(['describe the trustworthy outcome.', 'what outcome should become possible?',
4
4
  'which tp tasks are fully done, partially done or not done?', 'what remains unverified?', 'which risks remain and what is their impact?']);
5
+ // A bare generated Run ID (`run-<operation uuid>`) names the Run but states no outcome.
6
+ const generatedRunId = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
5
7
  export function isDecisionContentPlaceholder(value) {
6
8
  if (typeof value !== 'string') return true;
7
9
  const text = value.trim().replace(/^`([^`]+)`$/u, '$1').replace(/\s+/gu, ' ').toLowerCase();
8
- return !text || prompts.has(text) || /^(?:todo|tbd|<[^>]+>|(?:draft|done|ready|blocked|pass|revise|block)(?:\s*\|\s*(?:draft|done|ready|blocked|pass|revise|block))+)$/.test(text);
10
+ return !text || prompts.has(text) || generatedRunId.test(text)
11
+ || /^(?:todo|tbd|<[^>]+>|(?:draft|done|ready|blocked|pass|revise|block)(?:\s*\|\s*(?:draft|done|ready|blocked|pass|revise|block))+)$/.test(text);
9
12
  }
@@ -159,7 +159,7 @@ export function isReadyUserGateApproval({ status, currentGate, missingApproval }
159
159
 
160
160
  function buildBreadcrumbPath(modeSliceDecision, currentGate, runState) {
161
161
  const gates = BREADCRUMB_PATH_TEMPLATES[modeSliceDecision] ?? ["UR"];
162
- return gates.map((gate) => {
162
+ return gates.map((gate, index) => {
163
163
  if (BREADCRUMB_STANDARD_GATES.includes(gate)) {
164
164
  if (gateApprovalStatus(runState, gate) === "approved") return { gate, status: "fulfilled" };
165
165
  if (gate === currentGate) return { gate, status: "current" };
@@ -170,6 +170,10 @@ function buildBreadcrumbPath(modeSliceDecision, currentGate, runState) {
170
170
  if (gate === currentGate) return { gate, status: "current" };
171
171
  if ((gate === "Verified Change" || gate === "Compact Delivery") && currentGate === "OR")
172
172
  return { gate, status: "fulfilled" };
173
+ // A path has one current entry: while an earlier gate is still the current one (a stored route
174
+ // with an unapproved UR), the route entry after it is open, not current by its route.
175
+ if (gates.slice(0, index).some((earlier) => earlier === currentGate && gateApprovalStatus(runState, earlier) !== "approved"))
176
+ return { gate, status: "open" };
173
177
  if (gate === "Verified Change" && modeSliceDecision === "verified_change")
174
178
  return { gate, status: "current" };
175
179
  if (gate === "Compact Delivery" && modeSliceDecision === "quick_task")
@@ -0,0 +1,25 @@
1
+ // Presentation of one canonical control assessment. This module never evaluates or authorizes work.
2
+ export function projectWorkSituation({ assessment, gate, blockingReason, missingApproval, closureOutcome, persistedGate, persistedNext, nextAllowedAction } = {}) {
3
+ if (persistedGate !== undefined && (persistedGate !== gate || !(assessment === 'ended' && closureOutcome) && persistedNext !== nextAllowedAction)) assessment = 'unconfirmed';
4
+ let kind = 'unconfirmed';
5
+ if (assessment === 'open') kind = gate === 'CD+Tests' ? 'in_progress' : 'open';
6
+ else if (assessment === 'blocked') {
7
+ kind = blockingReason === 'presentation_version_decided' ? 'revision_required'
8
+ : blockingReason === 'none' && missingApproval === `Approval: ${gate}` ? 'approval_required'
9
+ : gate === 'UR' && blockingReason === 'AGDF_UR_REQUIREMENTS_INCOMPLETE' ? 'clarification_required' : 'blocked';
10
+ } else if (assessment === 'completed') kind = 'completed';
11
+ else if (assessment === 'ended' && closureOutcome) kind = 'ended';
12
+ const values = {
13
+ revision_required: ['clarification_required', 'Revision required', 'Überarbeitung nötig'],
14
+ approval_required: ['decision_required', 'Approval required', 'Freigabe erforderlich'],
15
+ clarification_required: ['clarification_required', 'Clarification required', 'Klärung erforderlich'],
16
+ in_progress: ['in_progress', 'In implementation', 'In Umsetzung'],
17
+ open: ['in_progress', 'Work may continue', 'Weiterarbeit offen'],
18
+ blocked: ['blocked', 'Blocked', 'Blockiert'],
19
+ completed: ['none', 'Completed', 'Vorhaben abgeschlossen'],
20
+ ended: ['none', 'Ended', 'Vorhaben beendet'],
21
+ unconfirmed: ['unclear', 'Current conditions unconfirmed', 'Aktuelle Voraussetzungen nicht bestätigt'],
22
+ };
23
+ const [code, en, de] = values[kind];
24
+ return { kind, code, en, de };
25
+ }
@@ -12,6 +12,7 @@ import { runSealState } from './run-seal.js';
12
12
  import { containedRegularFile, hasSymlinkComponent } from './contained-file.js';
13
13
  import { COOPERATIVE_ASSURANCE } from './approval-command-contract.js';
14
14
  import { withControlMutation } from './control-layout.js';
15
+ import { isDecisionContentPlaceholder } from '../control-evaluation/decision-content-placeholder.js';
15
16
 
16
17
  import { creationRunId } from './run-identity.js';
17
18
  export { creationRunId };
@@ -50,7 +51,11 @@ export function createRunCommand(root, input, { requestDigest, packageVersion, c
50
51
  created_at: new Date().toISOString(), producer: { contract: 'run-command/1', package_version: packageVersion },
51
52
  assurance: COOPERATIVE_ASSURANCE, effect: { previous_revision_id: null,
52
53
  resulting_revision_id: parsed.meta.revision_id, revision: 1, record: { title: input.command.payload.title } } });
53
- return appendRunOperation(content.replace('Describe the trustworthy outcome.', () => input.command.payload.title), receipt);
54
+ // Only a semantic title orients the Objective. A placeholder title such as the bare Run ID
55
+ // keeps the template prompt, so recording the UR projects its Goal there.
56
+ const title = input.command.payload.title;
57
+ const oriented = isDecisionContentPlaceholder(title) ? content : content.replace('Describe the trustworthy outcome.', () => title);
58
+ return appendRunOperation(oriented, receipt);
54
59
  },
55
60
  beforePublish: () => checkpoint('creation_before_publish'),
56
61
  });
@@ -1,16 +1,30 @@
1
1
  import { readControlLayout } from './control-layout.js';
2
- import { prepareRunContentState } from '../control-evaluation/run-state.js';
2
+ import { prepareRunContentState, readArtefactHeading } from '../control-evaluation/run-state.js';
3
3
  import { transitionDecisionForRunState } from '../control-evaluation/gate-policy.js';
4
+ import { evaluateGateCheck } from '../control-evaluation/gate-check.js';
5
+ import { projectCockpitAssessment } from '../control-inspect/run-classification.js';
6
+ import { projectWorkSituation } from '../control-inspect/work-situation.js';
7
+ import { runClosure } from './run-operations.js';
4
8
  import { existsSync } from "../control-read/fs.js";
5
9
  import { join } from "node:path";
6
10
  import { discoverRuns } from "./run-state-reader.js";
7
11
  import { buildRunInventory } from "./run-inventory.js";
12
+ import { parseControlState } from './run-state-parser.js';
13
+ import { runSealState, listedArtefactPaths } from './run-seal.js';
14
+ import { isSafeControlRelativePath } from './contained-file.js';
15
+
16
+ const READ_BOUNDARY_CODES = ['source_changed', 'resource_limit', 'resource_denied', 'timeout'];
17
+ const inControlScope = path => isSafeControlRelativePath(path) && path.startsWith('.agdf/control/');
18
+ // The detail reader's boundary: a run registering sources outside .agdf/control cannot be proven here.
19
+ const registersOnlyControlSources = content => listedArtefactPaths(content).every(inControlScope);
8
20
 
9
21
  // Feeds the pure inventory from the run discovery of the current read view: each run's parsed text
10
22
  // and, when the full parser rejected it, its findings. No run state is read a second time. `observe: false`
11
23
  // skips the per-run gate observation, which reads registered documents, for a caller that evaluates the
12
24
  // runs itself (the companion run view, which reads each run in its own captured snapshot).
13
- export function readRunInventory(root, { language = "en", observe = true } = {}) {
25
+ // `cockpitAssessment` evaluates active runs only, like the companion run view: terminal runs stay
26
+ // summarized history, so a long history costs no gate evaluation per run and per refresh.
27
+ export function readRunInventory(root, { language = "en", observe = true, includePresentation = false, cockpitAssessment = false } = {}) {
14
28
  if (!existsSync(join(root, '.agdf/control'))) return buildRunInventory(null, { language });
15
29
  readControlLayout(root);
16
30
  if (!existsSync(join(root, '.agdf/control/runs'))) return buildRunInventory([], { language,
@@ -20,12 +34,45 @@ export function readRunInventory(root, { language = "en", observe = true } = {})
20
34
  const store = discovered.filter((run) => run.run_id === "<repository>");
21
35
  const sources = discovered.filter(run => run.run_id !== '<repository>').map(run => {
22
36
  const source = { run_id: run.run_id, ...(typeof run.content === 'string' ? { text: run.content } : {}), findings: run.valid ? [] : run.findings };
37
+ if (includePresentation && typeof run.content === 'string') {
38
+ let name = '', seal = 'unconfirmed';
39
+ try {
40
+ const ur = parseControlState(run.content).artefacts.get('UR');
41
+ const path = String(ur?.path ?? '').replace(/^`|`$/g, '').trim();
42
+ if (inControlScope(path)) name = readArtefactHeading(root, ur).replace(/^UR:\s*/i, '').trim();
43
+ } catch { /* An unreadable title must not hide the inventory. */ }
44
+ try {
45
+ if (registersOnlyControlSources(run.content)) {
46
+ const result = runSealState(root, run.content).status;
47
+ seal = result === 'valid' ? 'valid' : result === 'unsealed' ? 'unsealed'
48
+ : ['invalid', 'content_changed', 'approvals_changed'].includes(result) ? 'invalid' : 'unconfirmed';
49
+ }
50
+ } catch { /* Unavailable evidence cannot prove a seal state. */ }
51
+ source.presentation = { name: name || null, seal };
52
+ }
23
53
  if (run.valid && observe) {
24
54
  try {
25
55
  const state = prepareRunContentState(root, run.content, run.path), decision = transitionDecisionForRunState(state);
26
56
  source.observation = { current_gate: decision.current_gate, decision: decision.status, missing: decision.missing_approval,
27
57
  next_action: decision.next_allowed_action, presentation_decision: Boolean(decision.presentation_decision?.decided) };
28
- } catch { source.findings = [{ code: 'AGDF_RUN_OBSERVATION_UNAVAILABLE' }]; }
58
+ if (cockpitAssessment && run.meta?.lifecycle === 'active') {
59
+ let report = null;
60
+ if (registersOnlyControlSources(run.content)) { try { report = evaluateGateCheck(root, { runId: run.run_id, ignoreRunIdEnv: true }); } catch (error) {
61
+ if (READ_BOUNDARY_CODES.includes(error.code)) throw error;
62
+ // Preserve the Run but do not claim a verified assessment.
63
+ } }
64
+ const assessed = report ? projectCockpitAssessment(report, run.meta?.lifecycle) : { state: 'unconfirmed' };
65
+ source.observation.work_situation = projectWorkSituation({ assessment: assessed.state,
66
+ gate: report?.current_gate, blockingReason: report?.blocking_reason, missingApproval: report?.missing_approval,
67
+ persistedGate: report ? state.current_gate : undefined, persistedNext: state.next_allowed_action, nextAllowedAction: report?.next_allowed_action,
68
+ closureOutcome: runClosure(run.content)?.outcome });
69
+ if (report) Object.assign(source.observation, { current_gate: report.current_gate, decision: report.status,
70
+ missing: report.missing_approval, next_action: report.next_allowed_action });
71
+ }
72
+ } catch (error) {
73
+ if (cockpitAssessment && READ_BOUNDARY_CODES.includes(error.code)) throw error;
74
+ source.findings = [{ code: 'AGDF_RUN_OBSERVATION_UNAVAILABLE' }];
75
+ }
29
76
  }
30
77
  return source;
31
78
  });
@@ -3,7 +3,9 @@
3
3
  // Callers pass validity findings from the full parser; this module never validates receipts.
4
4
  import { scalarFields, sectionTableRows, projectRunFacts } from "./run-state-document.js";
5
5
  import { RUN_ID_PATTERN } from "./run-identity.js";
6
- import { isDecisionContentPlaceholder } from "../control-evaluation/decision-content-placeholder.js";
6
+ import { normalizedRunTitle, runIdentity } from "./run-title.js";
7
+
8
+ export { normalizedRunTitle };
7
9
 
8
10
  export const RUN_INVENTORY_SCHEMA_VERSION = "1";
9
11
  export const RUN_INVENTORY_LIFECYCLES = Object.freeze(["active", "completed", "superseded", "abandoned"]);
@@ -42,29 +44,10 @@ const ISO_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u;
42
44
  const plain = value => String(value ?? "").replace(/`/gu, "").trim();
43
45
  const clip = text => (text.length > TITLE_LIMIT ? `${text.slice(0, TITLE_LIMIT - 1)}…` : text);
44
46
 
45
- // The run-ID title every reader shows when no authored title exists.
46
- export function normalizedRunTitle(runId) {
47
- const words = String(runId ?? "").replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
48
- return words ? words.replace(/\b\p{L}/gu, (letter) => letter.toUpperCase()) : "AGDF run";
49
- }
50
-
51
- // Lines of one level-two section up to the next heading; an empty section stays empty.
52
- function sectionLines(text, heading) {
53
- const lines = text.split("\n"), start = lines.findIndex(line => line.trimEnd() === `## ${heading}`);
54
- if (start < 0) return [];
55
- const rest = lines.slice(start + 1), end = rest.findIndex(line => /^#{1,2} /u.test(line));
56
- return end < 0 ? rest : rest.slice(0, end);
57
- }
58
-
59
- function objectiveTitle(text, runId) {
60
- // The first paragraph, so hard-wrapped objectives stay whole.
61
- const lines = sectionLines(text, "Objective").map(value => value.trim()), first = lines.findIndex(Boolean);
62
- const paragraph = first < 0 ? [] : lines.slice(first), end = paragraph.indexOf("");
63
- const line = (end < 0 ? paragraph : paragraph.slice(0, end)).join(" ");
64
- const stripped = line.replace(/^(?:[-*>]|#+|\d+\.)\s+/u, "").replace(/\*\*|__|`/gu, "").replace(/\s+/gu, " ").trim();
65
- return isDecisionContentPlaceholder(stripped)
66
- ? { title: normalizedRunTitle(runId), title_source: "run_id" }
67
- : { title: clip(stripped), title_source: "objective" };
47
+ // The inventory reads no UR document, so its title source is the Objective or the Run ID.
48
+ function titleFacts(text, runId) {
49
+ const { title, title_source, objective } = runIdentity({ runId, runContent: text });
50
+ return { title: clip(title), title_source, objective: objective === null ? null : clip(objective) };
68
51
  }
69
52
 
70
53
  // Base64url receipt cells decoded for display facts only; undecodable cells are skipped.
@@ -126,17 +109,19 @@ function entryOf(source, language) {
126
109
  const gateResult = facts?.artefacts.get(gate === 'Quick Task Execution' ? 'CR' : gate)?.status ?? null;
127
110
  const presentationDecision = source.observation?.presentation_decision === true;
128
111
  const missing = source.observation?.missing === 'none' ? '' : source.observation?.missing ?? '';
129
- const attention = runAttention({ lifecycle, valid, decision, gateResult, presentationDecision, missing });
112
+ const situation = source.observation?.work_situation;
113
+ const attention = situation?.code ?? runAttention({ lifecycle, valid, decision, gateResult, presentationDecision, missing });
130
114
  const revision = /^[1-9]\d*$/u.test(meta.get("revision") ?? "") ? Number(meta.get("revision")) : null;
131
115
  const index = languageIndex(language);
132
116
  return {
133
117
  run_id: runId,
134
- ...(text ? objectiveTitle(text, runId) : { title: normalizedRunTitle(runId), title_source: "run_id" }),
118
+ ...titleFacts(text, runId),
119
+ ...(source.presentation ? { presentation: source.presentation } : {}),
135
120
  valid,
136
121
  lifecycle: valid ? lifecycle : null,
137
122
  current_gate: gate,
138
123
  decision,
139
- attention: { code: attention, label: LABELS[attention][index] },
124
+ attention: { code: attention, label: situation ? (index === 1 ? situation.de : situation.en) : LABELS[attention][index] },
140
125
  basis: revision === null ? TEXT.basisUnknown[index]() : TEXT.basis[index](revision),
141
126
  last_activity: text ? lastActivity(decoded) : { at: null, source: "none", precision: null },
142
127
  revision,
@@ -355,6 +355,35 @@ function artifactSummary(gate, markdown, { runId, revisionId, language, sourceLa
355
355
  return { markdown: summary, digest: hash(summary) };
356
356
  }
357
357
 
358
+ // Cockpit presentation reuses the same language and summary owner as authoring checks.
359
+ // The structured rows are derived from that summary; no second content authority is created.
360
+ export function projectCockpitSummary(gate, markdown, languages, context = {}) {
361
+ if (!['UR', 'PRD', 'SD', 'TP'].includes(gate)) return null;
362
+ try {
363
+ const readiness = evaluateApprovalSummaryReadiness(gate, markdown, languages, { path: context.artefactPath });
364
+ if (!readiness.ready) return null;
365
+ const summary = artifactSummary(gate, markdown, { ...context, language: languages.presentation_language, sourceLanguage: languages.artifact_language });
366
+ const priorities = {
367
+ UR: [/^(?:Ziel|Goal)$/iu, /^(?:Umfang|Scope)$/iu, /^(?:Abnahme|Abnahmesignal|Acceptance)$/iu],
368
+ PRD: [/^(?:Ziel|Nutzerziel|Goal|User intent)$/iu, /^(?:Umfang|Produktumfang|Scope|Product scope)$/iu, /^(?:AC-[A-Z0-9-]+)$/iu],
369
+ SD: [/^(?:Lösung|Solution)$/iu, /^(?:Verantwortung|Ownership)$/iu, /^(?:Entscheidungen|Design decisions)$/iu],
370
+ TP: [/^(?:Aufgaben|Tasks)$/iu, /^(?:Tests)$/iu, /^(?:Risiken|Risks)$/iu],
371
+ };
372
+ const rows = summary.markdown.split(/\r?\n/u).flatMap(line => {
373
+ const match = line.match(/^[-*]\s+([^:\n]{1,80}):\s*(\S.*)$/u);
374
+ return match && match[2].length <= 1200 ? [{ label: match[1].trim(), text: match[2].trim() }] : [];
375
+ });
376
+ const items = priorities[gate].flatMap(pattern => {
377
+ const matches = rows.filter(row => pattern.test(row.label));
378
+ return matches.length === 1 ? matches : [];
379
+ });
380
+ return { state: 'available', gate, items, markdown: summary.markdown, authorizes: false };
381
+ } catch (error) {
382
+ if (String(error.message).startsWith('approval_summary_')) return null;
383
+ throw error;
384
+ }
385
+ }
386
+
358
387
  // A compact decision brief is a projection of the already validated, source-
359
388
  // authored summary. Missing authored fields stay absent; no agent/UI paraphrase.
360
389
  export function decisionBriefFromSummary(summary, language) {
@@ -0,0 +1,43 @@
1
+ // One owner for a Run's human title and orienting Objective. Pure: no `node:` import, so the run
2
+ // inventory, the cockpit, the companion run view and the status card share exactly this rule.
3
+ // Precedence: registered UR heading, then the authored Objective, then the normalized Run ID.
4
+ import { isDecisionContentPlaceholder } from "../control-evaluation/decision-content-placeholder.js";
5
+
6
+ // The run-ID title every reader shows when no authored title exists.
7
+ export function normalizedRunTitle(runId) {
8
+ const words = String(runId ?? "").replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
9
+ return words ? words.replace(/\b\p{L}/gu, (letter) => letter.toUpperCase()) : "AGDF run";
10
+ }
11
+
12
+ // A heading as display text: no leading hashes and no inline Markdown marks.
13
+ export function plainHeading(value) {
14
+ return String(value ?? "").replace(/^#+\s*/u, "").replace(/[`*_]/gu, "").replace(/\s+/gu, " ").trim();
15
+ }
16
+
17
+ // Lines of one level-two section up to the next heading; an empty section stays empty.
18
+ function sectionLines(text, heading) {
19
+ const lines = text.split("\n"), start = lines.findIndex(line => line.trimEnd() === `## ${heading}`);
20
+ if (start < 0) return [];
21
+ const rest = lines.slice(start + 1), end = rest.findIndex(line => /^#{1,2} /u.test(line));
22
+ return end < 0 ? rest : rest.slice(0, end);
23
+ }
24
+
25
+ // The first paragraph of the Objective, so hard-wrapped objectives stay whole. A template prompt,
26
+ // a status word or a bare generated Run ID is no orientation and yields null.
27
+ export function authoredObjective(runContent) {
28
+ const text = String(runContent ?? "").replace(/^/u, "").replace(/\r\n?/gu, "\n");
29
+ const lines = sectionLines(text, "Objective").map(value => value.trim()), first = lines.findIndex(Boolean);
30
+ if (first < 0) return null;
31
+ const paragraph = lines.slice(first), end = paragraph.indexOf("");
32
+ const value = (end < 0 ? paragraph : paragraph.slice(0, end)).join(" ")
33
+ .replace(/^(?:[-*>]|#+|\d+\.)\s+/u, "").replace(/\*\*|__|`/gu, "").replace(/\s+/gu, " ").trim();
34
+ return isDecisionContentPlaceholder(value) ? null : value;
35
+ }
36
+
37
+ // `urHeading` is the registered UR document heading without its "UR:" prefix, when the caller read it.
38
+ export function runIdentity({ runId, runContent, urHeading } = {}) {
39
+ const objective = authoredObjective(runContent), ur = plainHeading(urHeading);
40
+ if (ur) return { title: ur, title_source: "ur", objective };
41
+ if (objective) return { title: objective, title_source: "objective", objective };
42
+ return { title: normalizedRunTitle(runId), title_source: "run_id", objective: null };
43
+ }
@@ -20,7 +20,8 @@ export function validateInteractionCatalog(registry) {
20
20
  check(`${language}.decisionCard`, pack.decisionCard, DECISION_CARD_LABEL_KEYS);
21
21
  check(`${language}.flowEvidence`, pack.flowEvidence, FLOW_EVIDENCE_LABEL_KEYS);
22
22
  check(`${language}.companion.states`, pack.companion?.states, COMPANION_MOODS);
23
- check(`${language}.companion.statusStates`, pack.companion?.statusStates, [...COMPANION_MOODS, "unreadable"]);
23
+ // Beside the moods the status line names a run view that cannot be read and a project without AGDF.
24
+ check(`${language}.companion.statusStates`, pack.companion?.statusStates, [...COMPANION_MOODS, "unreadable", "notSetUp"]);
24
25
  const unreadable = pack.companion?.unreadable;
25
26
  check(`${language}.companion.unreadable`, unreadable && Object.fromEntries(COMPANION_UNREADABLE_REASONS.map(key => [key, unreadable[key]])), COMPANION_UNREADABLE_REASONS);
26
27
  }
@@ -1,5 +1,6 @@
1
1
  import { RUN_ID_PATTERN } from "./control-state/run-identity.js";
2
- import { normalizedRunTitle, runInventoryHistoryLine } from "./control-state/run-inventory.js";
2
+ import { runInventoryHistoryLine } from "./control-state/run-inventory.js";
3
+ import { authoredObjective, normalizedRunTitle, plainHeading, runIdentity } from "./control-state/run-title.js";
3
4
  import { DISPATCH_RECOVERY_CODES, DECISION_CARD_COPY_BUDGETS } from "./interaction-catalog.js";
4
5
  import { TASK_TARGET_SOURCES, TASK_TARGET_UNRESOLVED_REASONS } from "./task-target-resolution.js";
5
6
 
@@ -229,16 +230,11 @@ function stripMarkdown(value) {
229
230
  .trim();
230
231
  }
231
232
 
232
- function section(content, heading) {
233
- const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
234
- return content.match(new RegExp(`(?:^|\\n)## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`))?.[1]?.trim() ?? "";
235
- }
236
-
237
233
  export { normalizedRunTitle };
238
234
 
235
+ // The current artefact heading names the card's document; otherwise the shared Run title rule applies.
239
236
  export function resolveHumanRunTitle({ currentArtefactHeading, urHeading, runContent, runId }) {
240
- const objective = section(String(runContent ?? ""), "Objective").split(/\r?\n/).map(stripMarkdown).find(Boolean) ?? "";
241
- return [currentArtefactHeading, urHeading, objective].map(stripMarkdown).find(Boolean) || normalizedRunTitle(runId);
237
+ return plainHeading(currentArtefactHeading) || runIdentity({ runId, runContent, urHeading }).title;
242
238
  }
243
239
 
244
240
  function displaySafeTitle(value, fallback) {
@@ -271,7 +267,7 @@ export function buildRunCandidates(runs) {
271
267
  return {
272
268
  run_id: run.run_id,
273
269
  display_title: displaySafeTitle(title, normalizedRunTitle(run.run_id)),
274
- objective: displaySafeObjective(section(String(run.content ?? ""), "Objective"), normalizedRunTitle(run.run_id)),
270
+ objective: displaySafeObjective(authoredObjective(run.content), normalizedRunTitle(run.run_id)),
275
271
  current_gate: currentGate,
276
272
  decision: String(run.control_state?.decision || "unknown").replaceAll("`", "").trim() || "unknown",
277
273
  next_allowed_action: String(run.control_state?.next_allowed_action ?? "").trim(),
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1",
3
3
  "generated": true,
4
4
  "source": "create-agdf/cli",
5
- "version": "0.31.0",
5
+ "version": "0.32.0",
6
6
  "entrypoint": "create-agdf/bin/agdf-validator.js",
7
- "digest": "b38eba8c15f4c412c909e2667187cd47a7c9fcd41fc09d8289d557a4df5e23a2"
7
+ "digest": "6adaa20191c11cc43e70669bb4be3b24eca02dfeedfa88be30bfb92abd5b6de3"
8
8
  }