hadara 0.3.3 → 0.4.0-rc.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 (80) hide show
  1. package/README.md +26 -18
  2. package/dist/cli/dashboard.js +28 -9
  3. package/dist/cli/docs.js +50 -0
  4. package/dist/cli/evidence.js +113 -0
  5. package/dist/cli/handoff.js +13 -0
  6. package/dist/cli/help.js +4 -3
  7. package/dist/cli/init.js +615 -111
  8. package/dist/cli/legacy-boundary.js +77 -0
  9. package/dist/cli/main.js +31 -0
  10. package/dist/cli/package-smoke.js +34 -1
  11. package/dist/cli/release-artifact.js +9 -0
  12. package/dist/cli/release-closeout.js +22 -0
  13. package/dist/cli/release-publish.js +9 -0
  14. package/dist/cli/task.js +85 -21
  15. package/dist/cli/validation.js +71 -0
  16. package/dist/context/context-pack.js +204 -26
  17. package/dist/context/session-start.js +85 -6
  18. package/dist/context/task-extractors.js +3 -2
  19. package/dist/core/schema.js +22 -0
  20. package/dist/evidence/evidence.js +131 -9
  21. package/dist/evidence/semantics.js +3 -1
  22. package/dist/handoff/handoff-stale-problems.js +155 -0
  23. package/dist/handoff/handoff-suggestion.js +6 -1
  24. package/dist/harness/validate.js +316 -58
  25. package/dist/schemas/close-source.schema.json +33 -0
  26. package/dist/schemas/context-pack.schema.json +23 -0
  27. package/dist/schemas/docs-inbox.schema.json +50 -0
  28. package/dist/schemas/docs-read-map.schema.json +83 -0
  29. package/dist/schemas/docs-register.schema.json +70 -0
  30. package/dist/schemas/evidence-projection.schema.json +35 -0
  31. package/dist/schemas/evidence-summary.schema.json +73 -0
  32. package/dist/schemas/handoff-stale-problems.schema.json +64 -0
  33. package/dist/schemas/package-recycle.schema.json +150 -0
  34. package/dist/schemas/release-closeout.schema.json +39 -0
  35. package/dist/schemas/schema-index.json +79 -2
  36. package/dist/schemas/session-start.schema.json +30 -0
  37. package/dist/schemas/smoke-evidence-summary.schema.json +1 -1
  38. package/dist/schemas/task-audit-close.schema.json +7 -0
  39. package/dist/schemas/task-close.schema.json +4 -0
  40. package/dist/schemas/task-finalize.schema.json +40 -1
  41. package/dist/schemas/task-status.schema.json +117 -0
  42. package/dist/schemas/task-workbench.schema.json +97 -2
  43. package/dist/schemas/validation-run.schema.json +103 -0
  44. package/dist/services/capability-registry.js +326 -47
  45. package/dist/services/ci-gate.js +4 -2
  46. package/dist/services/dashboard-bootstrap.js +6 -6
  47. package/dist/services/dashboard-cache.js +2 -0
  48. package/dist/services/dashboard-task-detail.js +42 -13
  49. package/dist/services/dashboard-timeline.js +2 -2
  50. package/dist/services/docs-registry.js +485 -30
  51. package/dist/services/evidence-lint.js +9 -3
  52. package/dist/services/evidence-summary.js +66 -0
  53. package/dist/services/lifecycle-guide.js +11 -12
  54. package/dist/services/operational-debt.js +8 -14
  55. package/dist/services/operations-status-service.js +7 -1
  56. package/dist/services/package-recycle.js +762 -0
  57. package/dist/services/protocol-consistency.js +11 -5
  58. package/dist/services/protocol-migration.js +1 -1
  59. package/dist/services/release-closeout.js +136 -0
  60. package/dist/services/state-projection.js +31 -16
  61. package/dist/services/task-read-model.js +3 -7
  62. package/dist/services/task-workbench.js +582 -29
  63. package/dist/services/validation-run.js +325 -0
  64. package/dist/services/workbench-next-actions.js +31 -20
  65. package/dist/services/write-preflight.js +0 -7
  66. package/dist/task/authoring-guidance.js +74 -0
  67. package/dist/task/lifecycle-next-actions.js +0 -1
  68. package/dist/task/task-capsule.js +6 -11
  69. package/dist/task/task-close-repair-plan.js +3 -2
  70. package/dist/task/task-close.js +319 -39
  71. package/dist/task/task-complete-flow.js +4 -3
  72. package/dist/task/task-finalize.js +184 -20
  73. package/dist/task/task-finish.js +36 -8
  74. package/dist/task/task-lifecycle.js +14 -9
  75. package/dist/task/task-templates.js +5 -24
  76. package/dist/task/task-upgrade-scaffold.js +9 -60
  77. package/dist/tui/constants.js +1 -7
  78. package/dist/tui/read-model.js +4 -4
  79. package/dist/tui/snapshot.js +14 -2
  80. package/package.json +1 -1
@@ -10,6 +10,7 @@ const node_path_1 = __importDefault(require("node:path"));
10
10
  const context_graph_builder_1 = require("./context-graph-builder");
11
11
  const context_slice_boundary_1 = require("./context-slice-boundary");
12
12
  const extractor_contract_1 = require("./extractor-contract");
13
+ const docs_registry_1 = require("../services/docs-registry");
13
14
  exports.CONTEXT_PACK_SCHEMA_ID = 'hadara.contextPack.v1';
14
15
  exports.CONTEXT_PACK_COMMAND = 'context.pack';
15
16
  exports.CONTEXT_PACK_DEFAULT_BUDGET = {
@@ -62,31 +63,43 @@ function buildContextPackReport(input) {
62
63
  });
63
64
  }
64
65
  const connectedIds = taskId ? connectedNodeIds(graphReport.edges, (0, extractor_contract_1.createTaskNodeId)(taskId)) : new Set();
66
+ const docsReadMap = resolveDocsReadMap(input, taskId);
67
+ const readMapExcludedPaths = new Set((docsReadMap?.doNotReadByDefault ?? []).map((entry) => entry.path));
65
68
  const rankedNodes = rankContextPackNodes(graphReport.nodes, graphReport.edges, connectedIds, taskNode);
66
- const readFirstRanked = rankedNodes.filter((ranked) => isReadFirstAllowed(ranked.node));
67
- const readFirst = readFirstRanked
68
- .slice(0, budget.maxReadFirstItems)
69
- .map((ranked) => itemFromRankedNode(ranked, true, input.projectRoot));
70
- if (readFirstRanked.length > readFirst.length) {
69
+ const readFirstRanked = rankedNodes
70
+ .filter((ranked) => isReadFirstAllowed(ranked.node))
71
+ .filter((ranked) => !isExcludedByReadMap(ranked.node, readMapExcludedPaths));
72
+ const graphReadFirst = readFirstRanked.map((ranked) => itemFromRankedNode(ranked, true, input.projectRoot));
73
+ const readFirstCandidates = mergeContextPackItems([
74
+ ...graphReadFirst.filter((item) => item.id === `task:${taskId}`),
75
+ ...readMapItems(docsReadMap?.readFirst ?? [], input.projectRoot, true),
76
+ ...graphReadFirst.filter((item) => item.id !== `task:${taskId}`)
77
+ ]);
78
+ const readFirst = readFirstCandidates.slice(0, budget.maxReadFirstItems);
79
+ if (readFirstCandidates.length > readFirst.length) {
71
80
  issues.push({
72
81
  severity: 'warning',
73
82
  code: 'CONTEXT_PACK_BUDGET_TRUNCATED',
74
- message: `Context pack readFirst items were truncated from ${readFirstRanked.length} to ${readFirst.length}.`
83
+ message: `Context pack readFirst items were truncated from ${readFirstCandidates.length} to ${readFirst.length}.`
75
84
  });
76
85
  }
77
86
  const selectedIds = new Set(readFirst.map((item) => item.id));
87
+ const selectedPaths = new Set(readFirst.map((item) => item.path).filter((value) => Boolean(value)));
78
88
  const maxReadIfNeeded = Math.max(0, (budget.maxItems ?? 30) - readFirst.length);
79
89
  const readIfNeededRanked = rankedNodes
80
- .filter((ranked) => !selectedIds.has(ranked.node.id))
90
+ .filter((ranked) => !selectedIds.has(ranked.node.id) && (!ranked.node.path || !selectedPaths.has(ranked.node.path)))
91
+ .filter((ranked) => !isExcludedByReadMap(ranked.node, readMapExcludedPaths))
81
92
  .filter((ranked) => !isDoNotReadByDefault(ranked.node));
82
- const readIfNeeded = readIfNeededRanked
83
- .slice(0, maxReadIfNeeded)
84
- .map((ranked) => itemFromRankedNode(ranked, false, input.projectRoot));
85
- if (readIfNeededRanked.length > readIfNeeded.length) {
93
+ const readIfNeededCandidates = mergeContextPackItems([
94
+ ...readMapItems(docsReadMap?.readIfNeeded ?? [], input.projectRoot, false),
95
+ ...readIfNeededRanked.map((ranked) => itemFromRankedNode(ranked, false, input.projectRoot))
96
+ ]).filter((item) => !selectedIds.has(item.id) && (!item.path || !selectedPaths.has(item.path)));
97
+ const readIfNeeded = readIfNeededCandidates.slice(0, maxReadIfNeeded);
98
+ if (readIfNeededCandidates.length > readIfNeeded.length) {
86
99
  issues.push({
87
100
  severity: 'warning',
88
101
  code: 'CONTEXT_PACK_BUDGET_TRUNCATED',
89
- message: `Context pack readIfNeeded items were truncated from ${readIfNeededRanked.length} to ${readIfNeeded.length}.`
102
+ message: `Context pack readIfNeeded items were truncated from ${readIfNeededCandidates.length} to ${readIfNeeded.length}.`
90
103
  });
91
104
  }
92
105
  const doNotReadByDefault = graphReport.nodes
@@ -108,6 +121,10 @@ function buildContextPackReport(input) {
108
121
  score: 0
109
122
  }, false, input.projectRoot))
110
123
  .sort(compareItems);
124
+ const validateWith = validationSuggestionsForTask(taskId, graphReport);
125
+ const writeBoundaries = writeBoundariesForItems([...readFirst, ...readIfNeeded], graphReport.nodes);
126
+ const sliceCandidates = sliceCandidatesForItems([...readFirst, ...readIfNeeded], graphReport.nodes);
127
+ const agentActions = agentActionsForContextPack(readFirst, sliceCandidates, validateWith, taskId);
111
128
  const cache = input.cache ?? graphReport.cache ?? { used: false, hit: false };
112
129
  return {
113
130
  schemaVersion: exports.CONTEXT_PACK_SCHEMA_ID,
@@ -120,9 +137,10 @@ function buildContextPackReport(input) {
120
137
  readFirst,
121
138
  readIfNeeded,
122
139
  doNotReadByDefault,
123
- validateWith: validationSuggestionsForTask(taskId, graphReport),
124
- writeBoundaries: writeBoundariesForItems([...readFirst, ...readIfNeeded], graphReport.nodes),
125
- sliceCandidates: sliceCandidatesForItems([...readFirst, ...readIfNeeded], graphReport.nodes),
140
+ validateWith,
141
+ writeBoundaries,
142
+ sliceCandidates,
143
+ agentActions,
126
144
  knownProblems,
127
145
  stateProjection: {
128
146
  ...graphReport.stateProjection.summary,
@@ -132,16 +150,86 @@ function buildContextPackReport(input) {
132
150
  graphAvailable: true,
133
151
  codeIndexAvailable: codeIndexAvailable(graphReport),
134
152
  stateProjectionAvailable: true,
135
- docsRegistryAvailable: graphReport.stateProjection.sources.some((source) => source.kind === 'docs-registry'),
153
+ docsRegistryAvailable: Boolean(docsReadMap?.source.registryPresent) || graphReport.stateProjection.sources.some((source) => source.kind === 'docs-registry'),
136
154
  commandRegistryAvailable: graphReport.nodes.some((node) => node.type === 'Command'),
137
155
  degraded: graphReport.summary.degraded || issues.some((issue) => issue.severity !== 'info'),
138
156
  graphSourceHash: graphReport.sourceHash,
139
- sourcesRead: graphReport.summary.sourcesRead
157
+ sourcesRead: graphReport.summary.sourcesRead,
158
+ docsReadMapAvailable: Boolean(docsReadMap),
159
+ docsReadMapReadFirstCount: docsReadMap?.readFirst.length ?? 0,
160
+ docsReadMapDoNotReadByDefaultCount: docsReadMap?.doNotReadByDefault.length ?? 0
140
161
  },
141
162
  cache,
142
163
  issues
143
164
  };
144
165
  }
166
+ function resolveDocsReadMap(input, taskId) {
167
+ if (input.docsReadMap)
168
+ return input.docsReadMap;
169
+ if (input.graphReport || !taskId)
170
+ return undefined;
171
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(input.projectRoot, '.hadara', 'docs-registry.json')))
172
+ return undefined;
173
+ return (0, docs_registry_1.createDocsReadMapReport)(input.projectRoot, taskId);
174
+ }
175
+ function readMapItems(entries, projectRoot, required) {
176
+ return [...entries].sort(compareReadMapEntriesForContextPack).map((entry) => {
177
+ const sourceHash = sourceHashForPath(projectRoot, entry.path);
178
+ return {
179
+ id: `doc:${entry.path}`,
180
+ type: 'Document',
181
+ path: entry.path,
182
+ title: entry.title,
183
+ reason: `Docs read-map ${entry.readTier}: ${entry.reason}`,
184
+ confidence: entry.readTier === 'active-task' || entry.readTier === 'active-spec' ? 'explicit' : 'derived',
185
+ ...(sourceHash ? { sourceHash } : {}),
186
+ estimatedTokens: estimateTokensForPath(entry.path, entry.title),
187
+ required,
188
+ sourceAccess: sourceAccessForPath(entry.path)
189
+ };
190
+ });
191
+ }
192
+ function compareReadMapEntriesForContextPack(a, b) {
193
+ return readMapTierPriority(a.readTier) - readMapTierPriority(b.readTier) || a.path.localeCompare(b.path);
194
+ }
195
+ function readMapTierPriority(tier) {
196
+ if (tier === 'active-task')
197
+ return 0;
198
+ if (tier === 'active-spec')
199
+ return 1;
200
+ if (tier === 'current-state')
201
+ return 2;
202
+ if (tier === 'workflow-reference')
203
+ return 3;
204
+ if (tier === 'conditional-reference')
205
+ return 4;
206
+ if (tier === 'implemented-reference')
207
+ return 5;
208
+ if (tier === 'drift-review')
209
+ return 6;
210
+ if (tier === 'historical')
211
+ return 7;
212
+ return 8;
213
+ }
214
+ function mergeContextPackItems(items) {
215
+ const seenIds = new Set();
216
+ const seenPaths = new Set();
217
+ const merged = [];
218
+ for (const item of items) {
219
+ if (seenIds.has(item.id))
220
+ continue;
221
+ if (item.path && seenPaths.has(item.path))
222
+ continue;
223
+ seenIds.add(item.id);
224
+ if (item.path)
225
+ seenPaths.add(item.path);
226
+ merged.push(item);
227
+ }
228
+ return merged;
229
+ }
230
+ function isExcludedByReadMap(node, excludedPaths) {
231
+ return Boolean(node.path && excludedPaths.has(node.path));
232
+ }
145
233
  function normalizeContextBudget(input = {}) {
146
234
  const maxReadFirstItems = positiveInteger(input.maxReadFirstItems) ?? exports.CONTEXT_PACK_DEFAULT_BUDGET.maxReadFirstItems;
147
235
  return {
@@ -159,7 +247,7 @@ function rankContextPackNodes(nodes, edges, connectedIds, taskNode) {
159
247
  if (taskNode) {
160
248
  ranked.set(taskNode.id, {
161
249
  node: taskNode,
162
- reason: 'Active task capsule is the first read for task-scoped context routing.',
250
+ reason: 'Read the active task capsule first; it defines scope, acceptance, test expectations, and close handoff for this task.',
163
251
  confidence: 'explicit',
164
252
  score: 1000
165
253
  });
@@ -174,7 +262,7 @@ function rankContextPackNodes(nodes, edges, connectedIds, taskNode) {
174
262
  continue;
175
263
  upsertRankedNode(ranked, {
176
264
  node,
177
- reason: edge.reason,
265
+ reason: concreteRankReason(node, edge.reason),
178
266
  confidence: edge.confidence,
179
267
  score: nodeScore(node, edge.confidence, connectedIds.has(node.id))
180
268
  });
@@ -186,7 +274,7 @@ function rankContextPackNodes(nodes, edges, connectedIds, taskNode) {
186
274
  if (metadataBoolean(node, 'requiredReading')) {
187
275
  upsertRankedNode(ranked, {
188
276
  node,
189
- reason: 'Document registry marks this document as required reading.',
277
+ reason: 'Required current-state document from the docs registry; read after task-local context if the task needs this surface.',
190
278
  confidence: 'explicit',
191
279
  score: nodeScore(node, 'explicit', false) - 25
192
280
  });
@@ -203,6 +291,8 @@ function upsertRankedNode(ranked, candidate) {
203
291
  function nodeScore(node, confidence, connected) {
204
292
  const confidenceScore = confidence === 'explicit' ? 80 : confidence === 'derived' ? 50 : 20;
205
293
  const connectedScore = connected ? 25 : 0;
294
+ const taskLocalScore = node.path?.startsWith('tasks/') ? 140 : 0;
295
+ const sourceScore = ['SourceFile', 'TestFile', 'Symbol'].includes(node.type) ? 40 : 0;
206
296
  const typeScore = {
207
297
  Task: 1000,
208
298
  Document: 700,
@@ -218,7 +308,28 @@ function nodeScore(node, confidence, connected) {
218
308
  FixtureFile: 380,
219
309
  ConfigFile: 360
220
310
  };
221
- return typeScore[node.type] + confidenceScore + connectedScore;
311
+ return typeScore[node.type] + confidenceScore + connectedScore + taskLocalScore + sourceScore;
312
+ }
313
+ function concreteRankReason(node, sourceReason) {
314
+ if (node.path?.startsWith('tasks/')) {
315
+ return `Task-local file connected to the active task; read it before broad project history. ${sourceReason}`;
316
+ }
317
+ if (node.type === 'SourceFile') {
318
+ return `Implementation file connected to the active task; inspect it when changing code. ${sourceReason}`;
319
+ }
320
+ if (node.type === 'TestFile') {
321
+ return `Test file connected to the active task; inspect it when planning or verifying changes. ${sourceReason}`;
322
+ }
323
+ if (node.type === 'Symbol') {
324
+ return `Specific symbol connected to the active task; prefer this bounded location over opening the full file. ${sourceReason}`;
325
+ }
326
+ if (node.type === 'KnownProblem') {
327
+ return `Current handoff problem relevant to this task; check whether it changes implementation or validation. ${sourceReason}`;
328
+ }
329
+ if (node.type === 'Command') {
330
+ return `Command surface connected to this task; use it to choose dry-run/read-only validation before mutation. ${sourceReason}`;
331
+ }
332
+ return sourceReason;
222
333
  }
223
334
  function isReadFirstAllowed(node) {
224
335
  if (isDoNotReadByDefault(node))
@@ -256,19 +367,24 @@ function itemFromRankedNode(ranked, required, projectRoot) {
256
367
  function sourceHashForItem(projectRoot, node) {
257
368
  if (!node.path || !(0, context_slice_boundary_1.isContextSliceProjectRelativePath)(node.path))
258
369
  return node.source.hash;
259
- const normalized = (0, context_slice_boundary_1.normalizeContextSliceInputPath)(node.path);
370
+ return sourceHashForPath(projectRoot, node.path) ?? node.source.hash;
371
+ }
372
+ function sourceHashForPath(projectRoot, relativePath) {
373
+ if (!(0, context_slice_boundary_1.isContextSliceProjectRelativePath)(relativePath))
374
+ return undefined;
375
+ const normalized = (0, context_slice_boundary_1.normalizeContextSliceInputPath)(relativePath);
260
376
  const root = node_path_1.default.resolve(projectRoot);
261
377
  const absolutePath = node_path_1.default.resolve(root, normalized);
262
378
  if (absolutePath !== root && !absolutePath.startsWith(`${root}${node_path_1.default.sep}`))
263
- return node.source.hash;
379
+ return undefined;
264
380
  try {
265
381
  const stat = node_fs_1.default.statSync(absolutePath);
266
382
  if (!stat.isFile())
267
- return node.source.hash;
383
+ return undefined;
268
384
  return (0, extractor_contract_1.hashContextGraphText)(node_fs_1.default.readFileSync(absolutePath, 'utf8'));
269
385
  }
270
386
  catch {
271
- return node.source.hash;
387
+ return undefined;
272
388
  }
273
389
  }
274
390
  function sourceAccessForNode(node) {
@@ -278,7 +394,10 @@ function sourceAccessForNode(node) {
278
394
  reason: 'This context item has no project file path for raw context slicing.'
279
395
  };
280
396
  }
281
- if ((0, context_slice_boundary_1.isContextSliceProjectRelativePath)(node.path)) {
397
+ return sourceAccessForPath(node.path);
398
+ }
399
+ function sourceAccessForPath(relativePath) {
400
+ if ((0, context_slice_boundary_1.isContextSliceProjectRelativePath)(relativePath)) {
282
401
  return {
283
402
  rawSlice: 'sliceable',
284
403
  reason: 'This item path is inside the raw context-slice read boundary.'
@@ -294,6 +413,9 @@ function estimateTokens(node) {
294
413
  const pathCost = node.path ? Math.ceil(node.path.length / 4) : 0;
295
414
  return Math.max(24, labelCost + pathCost + 16);
296
415
  }
416
+ function estimateTokensForPath(relativePath, title) {
417
+ return Math.max(24, Math.ceil(title.length / 4) + Math.ceil(relativePath.length / 4) + 16);
418
+ }
297
419
  function validationSuggestionsForTask(taskId, graphReport) {
298
420
  const suggestions = new Map();
299
421
  if (taskId) {
@@ -388,6 +510,62 @@ function sliceCandidatesForItems(items, nodes) {
388
510
  };
389
511
  });
390
512
  }
513
+ function agentActionsForContextPack(readFirst, sliceCandidates, validateWith, taskId) {
514
+ const actions = [];
515
+ const seenCommands = new Set();
516
+ const firstSliceableItem = readFirst.find((item) => item.path && item.sourceAccess?.rawSlice === 'sliceable');
517
+ if (firstSliceableItem?.path) {
518
+ const args = suggestedSliceCommandArgs(firstSliceableItem.path, 'explicit-range', undefined, {
519
+ lineStart: firstSliceableItem.lineStart ?? 1,
520
+ lineEnd: firstSliceableItem.lineEnd ?? Math.max(firstSliceableItem.lineStart ?? 1, (firstSliceableItem.lineStart ?? 1) + 80)
521
+ });
522
+ pushAgentAction(actions, seenCommands, {
523
+ id: 'agent-action:read-first:1',
524
+ kind: 'read-first',
525
+ priority: 100,
526
+ sourceItemId: firstSliceableItem.id,
527
+ path: firstSliceableItem.path,
528
+ reason: `Read the top context item ${firstSliceableItem.id} as a bounded raw slice before opening broader files.`,
529
+ command: suggestedSliceCommand(args),
530
+ commandArgs: args,
531
+ writeBoundary: 'read-only'
532
+ });
533
+ }
534
+ for (const [index, candidate] of sliceCandidates.slice(0, 3).entries()) {
535
+ pushAgentAction(actions, seenCommands, {
536
+ id: `agent-action:slice:${index + 1}`,
537
+ kind: 'slice',
538
+ priority: 90 - index,
539
+ sliceCandidateId: candidate.id,
540
+ path: candidate.path,
541
+ reason: `Use this bounded slice candidate before reading the full file: ${candidate.reason}`,
542
+ command: candidate.suggestedCommand,
543
+ commandArgs: candidate.suggestedCommandArgs,
544
+ writeBoundary: 'read-only'
545
+ });
546
+ }
547
+ const requiredValidation = validateWith.find((suggestion) => suggestion.requiredForClose);
548
+ if (requiredValidation) {
549
+ pushAgentAction(actions, seenCommands, {
550
+ id: 'agent-action:validate:required-close',
551
+ kind: 'validate',
552
+ priority: 50,
553
+ reason: taskId
554
+ ? `Run this read-only readiness check before planning close for ${taskId}.`
555
+ : 'Run this read-only readiness check before planning close.',
556
+ command: requiredValidation.command,
557
+ writeBoundary: 'read-only'
558
+ });
559
+ }
560
+ return actions.sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));
561
+ }
562
+ function pushAgentAction(actions, seenCommands, action) {
563
+ const key = action.commandArgs ? action.commandArgs.join('\0') : action.command;
564
+ if (seenCommands.has(key))
565
+ return;
566
+ seenCommands.add(key);
567
+ actions.push(action);
568
+ }
391
569
  function explicitRangeForCandidate(item, node) {
392
570
  const lineStart = item.lineStart ?? numberMetadata(node, 'startLine') ?? node?.source.line ?? 1;
393
571
  const metadataEnd = numberMetadata(node, 'endLine');
@@ -7,6 +7,8 @@ const context_graph_builder_1 = require("./context-graph-builder");
7
7
  const context_cache_store_1 = require("./context-cache-store");
8
8
  const source_manifest_1 = require("./source-manifest");
9
9
  const code_graph_extractor_1 = require("./code-graph-extractor");
10
+ const validate_1 = require("../harness/validate");
11
+ const docs_registry_1 = require("../services/docs-registry");
10
12
  exports.SESSION_START_SCHEMA_ID = 'hadara.sessionStart.v1';
11
13
  exports.SESSION_START_COMMAND = 'session.start';
12
14
  function buildSessionStartReport(input) {
@@ -41,6 +43,7 @@ function buildSessionStartReport(input) {
41
43
  lifecycle,
42
44
  allowLiveContextPack: Boolean(input.allowLiveContextPack)
43
45
  });
46
+ const docsReadMap = taskId ? createSessionStartDocsReadMap(input.projectRoot, taskId, input.budget?.maxReadFirstItems ?? 7) : undefined;
44
47
  return {
45
48
  schemaVersion: exports.SESSION_START_SCHEMA_ID,
46
49
  command: exports.SESSION_START_COMMAND,
@@ -56,6 +59,7 @@ function buildSessionStartReport(input) {
56
59
  contextPack,
57
60
  lifecycle,
58
61
  guidance,
62
+ ...(docsReadMap ? { docsReadMap } : {}),
59
63
  knownProblems: contextPack.knownProblems,
60
64
  sourceSummary: contextPack.sourceSummary,
61
65
  cache: contextPack.cache,
@@ -70,6 +74,43 @@ function buildSessionStartReport(input) {
70
74
  issues
71
75
  };
72
76
  }
77
+ function createSessionStartDocsReadMap(projectRoot, taskId, maxReadFirst) {
78
+ const readMap = (0, docs_registry_1.createDocsReadMapReport)(projectRoot, taskId);
79
+ const sourceValidation = (0, validate_1.validateTaskCapsule)(projectRoot, taskId, { level: 'done' });
80
+ const sourceDocumentDrift = sourceValidation.issues
81
+ .filter((issue) => issue.code === 'TASK_SOURCE_DOCUMENT_CHANGED' || issue.code === 'TASK_SOURCE_DOCUMENT_MISSING_HASH')
82
+ .map((issue) => ({
83
+ code: issue.code,
84
+ message: issue.message,
85
+ ...(issue.path ? { path: issue.path } : {}),
86
+ ...(issue.heading ? { heading: issue.heading } : {})
87
+ }));
88
+ return {
89
+ taskId: readMap.taskId,
90
+ command: `node dist/cli/main.js docs read-map --task ${taskId} --json`,
91
+ source: readMap.source,
92
+ task: readMap.task,
93
+ readFirstCount: readMap.readFirst.length,
94
+ readIfNeededCount: readMap.readIfNeeded.length,
95
+ doNotReadByDefaultCount: readMap.doNotReadByDefault.length,
96
+ driftWarningCount: readMap.driftWarnings.length,
97
+ sourceDocumentDriftCount: sourceDocumentDrift.length,
98
+ readFirst: readMap.readFirst.slice(0, maxReadFirst).map((entry) => ({
99
+ path: entry.path,
100
+ readTier: entry.readTier,
101
+ authority: entry.authority,
102
+ reason: entry.reason
103
+ })),
104
+ driftWarnings: readMap.driftWarnings.slice(0, 10),
105
+ sourceDocumentDrift,
106
+ issues: readMap.issues.map((issue) => ({
107
+ severity: issue.severity,
108
+ code: issue.code,
109
+ message: issue.message,
110
+ ...(issue.path ? { path: issue.path } : {})
111
+ }))
112
+ };
113
+ }
73
114
  function buildWarmCachedContextPackReport(input) {
74
115
  const cachedManifest = (0, context_cache_store_1.readContextSourceManifestCache)(input.projectRoot);
75
116
  if (cachedManifest.status !== 'valid' || !cachedManifest.manifest)
@@ -137,7 +178,7 @@ function lifecycleForSessionStart(taskId, contextPack) {
137
178
  `node dist/cli/main.js task status --task ${taskId} --json`,
138
179
  `node dist/cli/main.js context pack --task ${taskId} --json`
139
180
  ]
140
- : ['node dist/cli/main.js task next --json'];
181
+ : ['node dist/cli/main.js task status --json'];
141
182
  for (const suggestion of contextPack.validateWith) {
142
183
  if (suggestion.requiredForClose && !primaryNextCommands.includes(suggestion.command)) {
143
184
  primaryNextCommands.push(suggestion.command);
@@ -175,20 +216,39 @@ function guidanceForSessionStart(input) {
175
216
  ? 'Session Start used explicit live context-pack discovery because --live was supplied.'
176
217
  : 'Session Start used the bounded no-live packet and avoided broad live graph discovery.';
177
218
  const commands = [];
219
+ let primaryAction;
178
220
  if (!taskId) {
221
+ primaryAction = {
222
+ id: 'task-status',
223
+ label: 'Select the next task',
224
+ command: 'node dist/cli/main.js task status --json',
225
+ args: ['task', 'status', '--json'],
226
+ reason: 'No task id is available, so the next useful step is to select a concrete task before reading task-scoped context.',
227
+ writeBoundary: 'read-only',
228
+ recommendedActorRole: 'agent-worker'
229
+ };
179
230
  commands.push({
180
- id: 'task-next',
181
- command: 'node dist/cli/main.js task next --json',
182
- args: ['task', 'next', '--json'],
231
+ id: 'task-status',
232
+ command: 'node dist/cli/main.js task status --json',
233
+ args: ['task', 'status', '--json'],
183
234
  reason: 'Choose the next task before requesting task-scoped context.'
184
235
  });
185
236
  }
186
237
  else {
238
+ primaryAction = {
239
+ id: 'task-status',
240
+ label: 'Inspect task loop phase',
241
+ command: `node dist/cli/main.js task status --task ${taskId} --json`,
242
+ args: ['task', 'status', '--task', taskId, '--json'],
243
+ reason: 'A task id is available, so the fastest safe first step is to inspect loop phase, blockers, and the primary next action before editing files.',
244
+ writeBoundary: 'read-only',
245
+ recommendedActorRole: 'agent-worker'
246
+ };
187
247
  commands.push({
188
248
  id: 'task-status',
189
249
  command: `node dist/cli/main.js task status --task ${taskId} --json`,
190
250
  args: ['task', 'status', '--task', taskId, '--json'],
191
- reason: 'Inspect task readiness, evidence, and lifecycle state.'
251
+ reason: 'Inspect task readiness, evidence, loop phase, and the primary next action.'
192
252
  });
193
253
  commands.push({
194
254
  id: 'context-pack',
@@ -196,6 +256,12 @@ function guidanceForSessionStart(input) {
196
256
  args: ['context', 'pack', '--task', taskId, '--json'],
197
257
  reason: 'Inspect the bounded task read plan without slicing raw source text.'
198
258
  });
259
+ commands.push({
260
+ id: 'docs-read-map',
261
+ command: `node dist/cli/main.js docs read-map --task ${taskId} --json`,
262
+ args: ['docs', 'read-map', '--task', taskId, '--json'],
263
+ reason: 'Inspect registry-backed task reading guidance and drift warnings before broad manual reads.'
264
+ });
199
265
  }
200
266
  commands.push({
201
267
  id: 'cache-warm',
@@ -216,6 +282,18 @@ function guidanceForSessionStart(input) {
216
282
  return {
217
283
  mode,
218
284
  primaryNextAction,
285
+ primaryAction,
286
+ whyThisNow: primaryAction.reason,
287
+ avoidForNow: taskId
288
+ ? [
289
+ 'Do not run task finalize before reviewing lifecycle blockers and required reads.',
290
+ 'Do not opt into --live context reads unless the bounded or warm packet is insufficient.'
291
+ ]
292
+ : [
293
+ 'Do not infer a task id from broad project files.',
294
+ 'Do not run live context discovery before selecting a task.'
295
+ ],
296
+ nextCommandArgs: primaryAction.args,
219
297
  reason,
220
298
  taskRequired,
221
299
  liveContextPackAvailable: true,
@@ -240,7 +318,7 @@ function buildBoundedContextPackReport(input) {
240
318
  severity: 'warning',
241
319
  code: 'CONTEXT_PACK_TASK_NOT_FOUND',
242
320
  message: 'No task id was supplied. Bounded session start returned task-selection guidance without running live project discovery.',
243
- fixHint: 'Run hadara task next --json, then rerun hadara session start --task <task-id> --json.'
321
+ fixHint: 'Run hadara task status --json, then rerun hadara session start --task <task-id> --json.'
244
322
  }];
245
323
  const readFirst = input.taskId
246
324
  ? [{
@@ -273,6 +351,7 @@ function buildBoundedContextPackReport(input) {
273
351
  : [],
274
352
  writeBoundaries: [],
275
353
  sliceCandidates: [],
354
+ agentActions: [],
276
355
  knownProblems: [],
277
356
  stateProjection: {
278
357
  ...(input.taskId ? { activeTask: input.taskId } : {}),
@@ -138,8 +138,9 @@ function readTaskStatus(content) {
138
138
  const sectionStatus = (0, markdown_table_1.readMarkdownSection)(content, '## Status').trim().split(/\r?\n/)[0]?.trim();
139
139
  if (sectionStatus)
140
140
  return sectionStatus;
141
- const rows = (0, markdown_table_1.parseMarkdownRowsUnderHeading)(content, '## Metadata');
142
- return (0, markdown_table_1.findMarkdownRowByCell)(rows, 0, 'Status')?.[1] ?? null;
141
+ const identityRows = (0, markdown_table_1.parseMarkdownRowsUnderHeading)(content, '## Identity');
142
+ const metadataRows = (0, markdown_table_1.parseMarkdownRowsUnderHeading)(content, '## Metadata');
143
+ return (0, markdown_table_1.findMarkdownRowByCell)(identityRows, 0, 'Status')?.[1] ?? (0, markdown_table_1.findMarkdownRowByCell)(metadataRows, 0, 'Status')?.[1] ?? null;
143
144
  }
144
145
  function readTaskHandoffStatus(content) {
145
146
  const rows = (0, markdown_table_1.parseMarkdownRowsUnderHeading)(content, '## Current State');
@@ -12,6 +12,7 @@ const actor_context_schema_json_1 = __importDefault(require("../schemas/actor-co
12
12
  const active_run_projection_schema_json_1 = __importDefault(require("../schemas/active-run-projection.schema.json"));
13
13
  const active_run_resume_schema_json_1 = __importDefault(require("../schemas/active-run-resume.schema.json"));
14
14
  const clean_checkout_smoke_schema_json_1 = __importDefault(require("../schemas/clean-checkout-smoke.schema.json"));
15
+ const close_source_schema_json_1 = __importDefault(require("../schemas/close-source.schema.json"));
15
16
  const code_index_schema_json_1 = __importDefault(require("../schemas/code-index.schema.json"));
16
17
  const context_cache_record_schema_json_1 = __importDefault(require("../schemas/context-cache-record.schema.json"));
17
18
  const context_cache_status_schema_json_1 = __importDefault(require("../schemas/context-cache-status.schema.json"));
@@ -29,19 +30,26 @@ const dev_docker_check_schema_json_1 = __importDefault(require("../schemas/dev-d
29
30
  const docs_archive_plan_schema_json_1 = __importDefault(require("../schemas/docs-archive-plan.schema.json"));
30
31
  const docs_doctor_schema_json_1 = __importDefault(require("../schemas/docs-doctor.schema.json"));
31
32
  const docs_explain_schema_json_1 = __importDefault(require("../schemas/docs-explain.schema.json"));
33
+ const docs_inbox_schema_json_1 = __importDefault(require("../schemas/docs-inbox.schema.json"));
32
34
  const docs_list_schema_json_1 = __importDefault(require("../schemas/docs-list.schema.json"));
33
35
  const docs_mark_schema_json_1 = __importDefault(require("../schemas/docs-mark.schema.json"));
34
36
  const docs_patch_plan_schema_json_1 = __importDefault(require("../schemas/docs-patch-plan.schema.json"));
37
+ const docs_read_map_schema_json_1 = __importDefault(require("../schemas/docs-read-map.schema.json"));
38
+ const docs_register_schema_json_1 = __importDefault(require("../schemas/docs-register.schema.json"));
35
39
  const docs_required_reading_schema_json_1 = __importDefault(require("../schemas/docs-required-reading.schema.json"));
36
40
  const evidence_lint_schema_json_1 = __importDefault(require("../schemas/evidence-lint.schema.json"));
37
41
  const evidence_list_schema_json_1 = __importDefault(require("../schemas/evidence-list.schema.json"));
38
42
  const evidence_migration_preview_schema_json_1 = __importDefault(require("../schemas/evidence-migration-preview.schema.json"));
43
+ const evidence_projection_schema_json_1 = __importDefault(require("../schemas/evidence-projection.schema.json"));
44
+ const evidence_summary_schema_json_1 = __importDefault(require("../schemas/evidence-summary.schema.json"));
39
45
  const event_schema_json_1 = __importDefault(require("../schemas/event.schema.json"));
40
46
  const feature_smoke_schema_json_1 = __importDefault(require("../schemas/feature-smoke.schema.json"));
41
47
  const harness_validate_schema_json_1 = __importDefault(require("../schemas/harness-validate.schema.json"));
48
+ const handoff_stale_problems_schema_json_1 = __importDefault(require("../schemas/handoff-stale-problems.schema.json"));
42
49
  const handoff_suggestion_schema_json_1 = __importDefault(require("../schemas/handoff-suggestion.schema.json"));
43
50
  const install_plan_schema_json_1 = __importDefault(require("../schemas/install-plan.schema.json"));
44
51
  const next_action_schema_json_1 = __importDefault(require("../schemas/next-action.schema.json"));
52
+ const package_recycle_schema_json_1 = __importDefault(require("../schemas/package-recycle.schema.json"));
45
53
  const package_smoke_schema_json_1 = __importDefault(require("../schemas/package-smoke.schema.json"));
46
54
  const plan_context_schema_json_1 = __importDefault(require("../schemas/plan-context.schema.json"));
47
55
  const private_evidence_schema_json_1 = __importDefault(require("../schemas/private-evidence.schema.json"));
@@ -52,6 +60,7 @@ const provider_call_schema_json_1 = __importDefault(require("../schemas/provider
52
60
  const provider_config_schema_json_1 = __importDefault(require("../schemas/provider-config.schema.json"));
53
61
  const release_artifact_manifest_schema_json_1 = __importDefault(require("../schemas/release-artifact-manifest.schema.json"));
54
62
  const release_artifact_schema_json_1 = __importDefault(require("../schemas/release-artifact.schema.json"));
63
+ const release_closeout_schema_json_1 = __importDefault(require("../schemas/release-closeout.schema.json"));
55
64
  const release_dry_run_schema_json_1 = __importDefault(require("../schemas/release-dry-run.schema.json"));
56
65
  const release_gate_schema_json_1 = __importDefault(require("../schemas/release-gate.schema.json"));
57
66
  const release_publish_schema_json_1 = __importDefault(require("../schemas/release-publish.schema.json"));
@@ -70,9 +79,11 @@ const task_finish_schema_json_1 = __importDefault(require("../schemas/task-finis
70
79
  const task_lifecycle_schema_json_1 = __importDefault(require("../schemas/task-lifecycle.schema.json"));
71
80
  const task_next_schema_json_1 = __importDefault(require("../schemas/task-next.schema.json"));
72
81
  const task_ready_schema_json_1 = __importDefault(require("../schemas/task-ready.schema.json"));
82
+ const task_status_schema_json_1 = __importDefault(require("../schemas/task-status.schema.json"));
73
83
  const task_upgrade_scaffold_schema_json_1 = __importDefault(require("../schemas/task-upgrade-scaffold.schema.json"));
74
84
  const task_workbench_schema_json_1 = __importDefault(require("../schemas/task-workbench.schema.json"));
75
85
  const tools_list_schema_json_1 = __importDefault(require("../schemas/tools-list.schema.json"));
86
+ const validation_run_schema_json_1 = __importDefault(require("../schemas/validation-run.schema.json"));
76
87
  const write_preflight_schema_json_1 = __importDefault(require("../schemas/write-preflight.schema.json"));
77
88
  class SchemaValidationError extends Error {
78
89
  schemaId;
@@ -92,6 +103,7 @@ const registeredSchemas = {
92
103
  'hadara.active_run.projection.v1': active_run_projection_schema_json_1.default,
93
104
  'hadara.active_run.resume.v1': active_run_resume_schema_json_1.default,
94
105
  'hadara.cleanCheckoutSmoke.v1': clean_checkout_smoke_schema_json_1.default,
106
+ 'hadara.closeSource.v1': close_source_schema_json_1.default,
95
107
  'hadara.codeIndex.v1': code_index_schema_json_1.default,
96
108
  'hadara.context.cacheRecord.v1': context_cache_record_schema_json_1.default,
97
109
  'hadara.context.cacheStatus.v1': context_cache_status_schema_json_1.default,
@@ -109,19 +121,26 @@ const registeredSchemas = {
109
121
  'hadara.docs.archivePlan.v1': docs_archive_plan_schema_json_1.default,
110
122
  'hadara.docs.doctor.v1': docs_doctor_schema_json_1.default,
111
123
  'hadara.docs.explain.v1': docs_explain_schema_json_1.default,
124
+ 'hadara.docs.inbox.v1': docs_inbox_schema_json_1.default,
112
125
  'hadara.docs.list.v1': docs_list_schema_json_1.default,
113
126
  'hadara.docs.mark.v1': docs_mark_schema_json_1.default,
114
127
  'hadara.docs.patchPlan.v1': docs_patch_plan_schema_json_1.default,
128
+ 'hadara.docs.readMap.v1': docs_read_map_schema_json_1.default,
129
+ 'hadara.docs.register.v1': docs_register_schema_json_1.default,
115
130
  'hadara.docs.requiredReading.v1': docs_required_reading_schema_json_1.default,
116
131
  'hadara.evidence.lint.v1': evidence_lint_schema_json_1.default,
117
132
  'hadara.evidence.list.v1': evidence_list_schema_json_1.default,
118
133
  'hadara.evidence.migration_preview.v1': evidence_migration_preview_schema_json_1.default,
134
+ 'hadara.evidence.projection.v1': evidence_projection_schema_json_1.default,
135
+ 'hadara.evidence.summary.v1': evidence_summary_schema_json_1.default,
119
136
  'hadara.event.v1': event_schema_json_1.default,
120
137
  'hadara.featureSmoke.v1': feature_smoke_schema_json_1.default,
121
138
  'hadara.harness.validate.v1': harness_validate_schema_json_1.default,
139
+ 'hadara.handoff.staleProblems.v1': handoff_stale_problems_schema_json_1.default,
122
140
  'hadara.handoff.suggestion.v1': handoff_suggestion_schema_json_1.default,
123
141
  'hadara.install.plan.v1': install_plan_schema_json_1.default,
124
142
  'hadara.next_action.v1': next_action_schema_json_1.default,
143
+ 'hadara.packageRecycle.v1': package_recycle_schema_json_1.default,
125
144
  'hadara.packageSmoke.v1': package_smoke_schema_json_1.default,
126
145
  'hadara.plan_context.v1': plan_context_schema_json_1.default,
127
146
  'hadara.privateEvidence.v1': private_evidence_schema_json_1.default,
@@ -132,6 +151,7 @@ const registeredSchemas = {
132
151
  'hadara.provider.config.v1': provider_config_schema_json_1.default,
133
152
  'hadara.releaseArtifact.manifest.v1': release_artifact_manifest_schema_json_1.default,
134
153
  'hadara.releaseArtifact.v1': release_artifact_schema_json_1.default,
154
+ 'hadara.releaseCloseout.v1': release_closeout_schema_json_1.default,
135
155
  'hadara.releaseDryRun.v1': release_dry_run_schema_json_1.default,
136
156
  'hadara.releaseGate.v1': release_gate_schema_json_1.default,
137
157
  'hadara.releasePublish.v1': release_publish_schema_json_1.default,
@@ -150,9 +170,11 @@ const registeredSchemas = {
150
170
  'hadara.task.lifecycle.v1': task_lifecycle_schema_json_1.default,
151
171
  'hadara.task.next.v1': task_next_schema_json_1.default,
152
172
  'hadara.task.ready.v1': task_ready_schema_json_1.default,
173
+ 'hadara.task.status.v1': task_status_schema_json_1.default,
153
174
  'hadara.task.upgrade_scaffold.v1': task_upgrade_scaffold_schema_json_1.default,
154
175
  'hadara.task.workbench.v1': task_workbench_schema_json_1.default,
155
176
  'hadara.tools.list.v1': tools_list_schema_json_1.default,
177
+ 'hadara.validation.run.v1': validation_run_schema_json_1.default,
156
178
  'hadara.write.preflight.v1': write_preflight_schema_json_1.default
157
179
  };
158
180
  function loadSchema(schemaId) {