hadara 0.3.2 → 0.3.3-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 (51) hide show
  1. package/README.md +38 -23
  2. package/dist/cli/context.js +110 -0
  3. package/dist/cli/help.js +6 -7
  4. package/dist/cli/init.js +56 -58
  5. package/dist/cli/main.js +12 -0
  6. package/dist/cli/session.js +37 -0
  7. package/dist/cli/task.js +52 -0
  8. package/dist/context/code-graph-extractor.js +123 -0
  9. package/dist/context/code-index.js +1154 -0
  10. package/dist/context/context-cache-store.js +925 -0
  11. package/dist/context/context-graph-builder.js +341 -0
  12. package/dist/context/context-graph.js +42 -0
  13. package/dist/context/context-pack.js +457 -0
  14. package/dist/context/context-slice-boundary.js +37 -0
  15. package/dist/context/context-slice.js +487 -0
  16. package/dist/context/document-extractors.js +343 -0
  17. package/dist/context/evidence-extractors.js +179 -0
  18. package/dist/context/extractor-contract.js +166 -0
  19. package/dist/context/registry-extractors.js +177 -0
  20. package/dist/context/release-extractors.js +175 -0
  21. package/dist/context/session-start.js +297 -0
  22. package/dist/context/source-manifest.js +566 -0
  23. package/dist/context/state-projection.js +196 -0
  24. package/dist/context/task-extractors.js +168 -0
  25. package/dist/core/schema.js +26 -0
  26. package/dist/harness/validate.js +7 -8
  27. package/dist/schemas/code-index.schema.json +173 -0
  28. package/dist/schemas/context-cache-record.schema.json +56 -0
  29. package/dist/schemas/context-cache-status.schema.json +167 -0
  30. package/dist/schemas/context-cache-warm.schema.json +222 -0
  31. package/dist/schemas/context-graph.schema.json +286 -0
  32. package/dist/schemas/context-pack.schema.json +246 -0
  33. package/dist/schemas/context-slice.schema.json +94 -0
  34. package/dist/schemas/context-source-manifest.schema.json +147 -0
  35. package/dist/schemas/schema-index.json +91 -0
  36. package/dist/schemas/session-start.schema.json +158 -0
  37. package/dist/schemas/task-close-repair-plan.schema.json +67 -0
  38. package/dist/schemas/task-context.schema.json +125 -0
  39. package/dist/schemas/task-finalize.schema.json +98 -0
  40. package/dist/schemas/task-lifecycle.schema.json +84 -0
  41. package/dist/services/capability-registry.js +266 -9
  42. package/dist/services/lifecycle-guide.js +23 -29
  43. package/dist/services/protocol-consistency.js +6 -8
  44. package/dist/task/acceptance.js +171 -0
  45. package/dist/task/task-close-repair-plan.js +190 -0
  46. package/dist/task/task-close.js +34 -35
  47. package/dist/task/task-finalize.js +377 -0
  48. package/dist/task/task-lifecycle.js +210 -0
  49. package/dist/task/task-next.js +10 -1
  50. package/dist/task/task-ready.js +4 -0
  51. package/package.json +1 -1
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTaskFinalizeReport = createTaskFinalizeReport;
7
+ exports.formatTaskFinalizeReport = formatTaskFinalizeReport;
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const task_close_1 = require("./task-close");
10
+ const task_finish_1 = require("./task-finish");
11
+ const lifecycle_next_actions_1 = require("./lifecycle-next-actions");
12
+ const task_ready_1 = require("./task-ready");
13
+ function createTaskFinalizeReport(projectRoot, taskId, options = {}) {
14
+ const actor = options.actor ?? (0, lifecycle_next_actions_1.defaultTaskLifecycleActor)();
15
+ const reports = createFinalizeReports(projectRoot, taskId, actor);
16
+ const steps = createSteps(taskId, reports);
17
+ const issues = collectIssues(reports);
18
+ const planHash = hashPlan(taskId, steps);
19
+ if (options.executeRequested)
20
+ return executeFinalizePlan(projectRoot, taskId, actor, reports, steps, issues, planHash, options.planHash);
21
+ return createFinalizeReport(taskId, actor, 'dry-run', true, steps, issues, planHash, undefined, reports);
22
+ }
23
+ function formatTaskFinalizeReport(report) {
24
+ const lines = [`[HADARA] task finalize ${report.taskId}: ${report.mode}`];
25
+ lines.push(`readOnly=${report.readOnly} ok=${report.ok} planHash=${report.planHash ?? 'none'}`);
26
+ if (report.primaryNextAction)
27
+ lines.push(`next=${report.primaryNextAction.command ?? report.primaryNextAction.summary ?? report.primaryNextAction.id}`);
28
+ for (const step of report.steps)
29
+ lines.push(`${step.status.toUpperCase()}\t${step.id}\t${step.command}`);
30
+ for (const issue of report.issues)
31
+ lines.push(`[${issue.severity}] ${issue.code}: ${issue.message}`);
32
+ return lines.join('\n');
33
+ }
34
+ function executeFinalizePlan(projectRoot, taskId, actor, initialReports, initialSteps, initialIssues, currentPlanHash, requestedPlanHash) {
35
+ if (!requestedPlanHash)
36
+ return createExecuteRefusal(taskId, actor, 'TASK_FINALIZE_PLAN_HASH_REQUIRED', 'task finalize --execute requires a reviewed --plan-hash from a dry-run report.', currentPlanHash, initialSteps);
37
+ if (requestedPlanHash !== currentPlanHash) {
38
+ return createExecuteRefusal(taskId, actor, 'TASK_FINALIZE_PLAN_HASH_MISMATCH', 'task finalize --execute refused because --plan-hash does not match the current dry-run plan.', currentPlanHash, initialSteps, {
39
+ requestedPlanHash,
40
+ currentPlanHash,
41
+ planHashMatched: false,
42
+ executedSteps: []
43
+ });
44
+ }
45
+ const executedSteps = [];
46
+ const initialBlocker = initialSteps.find((step) => step.status === 'blocked');
47
+ if (initialBlocker) {
48
+ return createFinalizeReport(taskId, actor, 'execute', false, initialSteps, initialIssues, currentPlanHash, {
49
+ requestedPlanHash,
50
+ currentPlanHash,
51
+ planHashMatched: true,
52
+ executedSteps: [createExecutedStep(initialBlocker, false, initialReports[reportKeyForStep(initialBlocker.id)], 'blocked')],
53
+ stoppedAt: initialBlocker.id
54
+ }, initialReports);
55
+ }
56
+ let reports = initialReports;
57
+ let steps = initialSteps;
58
+ let finishStep = steps.find((step) => step.id === 'finish');
59
+ if (finishStep?.status === 'required') {
60
+ const finishReport = (0, task_finish_1.createTaskFinishReport)(projectRoot, taskId, 'execute', { actor });
61
+ executedSteps.push(createExecutedStep(finishStep, finishReport.ok, finishReport, finishReport.ok ? 'executed' : 'blocked'));
62
+ if (!finishReport.ok)
63
+ return createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, currentPlanHash, executedSteps, 'finish');
64
+ reports = createFinalizeReports(projectRoot, taskId, actor);
65
+ steps = createSteps(taskId, reports);
66
+ }
67
+ else if (finishStep?.status === 'satisfied') {
68
+ executedSteps.push(createExecutedStep(finishStep, true, reports.finish, 'satisfied'));
69
+ }
70
+ const readyStep = steps.find((step) => step.id === 'ready');
71
+ if (readyStep?.status !== 'satisfied') {
72
+ const readyReport = reports.ready;
73
+ executedSteps.push(createExecutedStep(readyStep ?? fallbackStep(taskId, 'ready'), readyReport?.ok ?? false, readyReport, readyReport?.ok ? 'satisfied' : 'blocked'));
74
+ return createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, currentPlanHash, executedSteps, 'ready');
75
+ }
76
+ executedSteps.push(createExecutedStep(readyStep, true, reports.ready, 'satisfied'));
77
+ const closeStep = steps.find((step) => step.id === 'close');
78
+ if (closeStep?.status === 'required') {
79
+ const closeReport = (0, task_close_1.createTaskCloseReport)(projectRoot, taskId, 'execute', { actor });
80
+ if (closeReport.ok)
81
+ (0, task_close_1.executeTaskCloseEvidence)(projectRoot, closeReport);
82
+ executedSteps.push(createExecutedStep(closeStep, closeReport.ok, closeReport, closeReport.ok ? 'executed' : 'blocked'));
83
+ if (!closeReport.ok)
84
+ return createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, currentPlanHash, executedSteps, 'close');
85
+ reports = createFinalizeReports(projectRoot, taskId, actor);
86
+ steps = createSteps(taskId, reports);
87
+ }
88
+ else if (closeStep?.status === 'satisfied') {
89
+ executedSteps.push(createExecutedStep(closeStep, true, reports.close, 'satisfied'));
90
+ }
91
+ else {
92
+ executedSteps.push(createExecutedStep(closeStep ?? fallbackStep(taskId, 'close'), false, reports.close, 'blocked'));
93
+ return createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, currentPlanHash, executedSteps, 'close');
94
+ }
95
+ const auditStep = steps.find((step) => step.id === 'audit-close');
96
+ const auditReport = reports.audit;
97
+ executedSteps.push(createExecutedStep(auditStep ?? fallbackStep(taskId, 'audit-close'), auditReport?.ok ?? false, auditReport, auditReport?.ok ? 'satisfied' : 'blocked'));
98
+ return createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, currentPlanHash, executedSteps, auditReport?.ok ? undefined : 'audit-close');
99
+ }
100
+ function createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, reviewedPlanHash, executedSteps, stoppedAt) {
101
+ const reports = createFinalizeReports(projectRoot, taskId, actor);
102
+ const steps = createSteps(taskId, reports);
103
+ const issues = collectIssues(reports);
104
+ const nextAction = createPrimaryNextAction(taskId, steps, issues);
105
+ const finalAudit = reports.audit?.auditVerdict.verdict === 'closed-valid';
106
+ const execution = {
107
+ requestedPlanHash,
108
+ currentPlanHash: reviewedPlanHash,
109
+ planHashMatched: true,
110
+ executedSteps,
111
+ ...(stoppedAt ? { stoppedAt } : {})
112
+ };
113
+ return {
114
+ schemaVersion: 'hadara.task.finalize.v1',
115
+ command: 'task.finalize',
116
+ ok: finalAudit && issues.every((issue) => issue.severity !== 'error') && steps.every((step) => step.status === 'satisfied'),
117
+ readOnly: false,
118
+ mode: 'execute',
119
+ taskId,
120
+ generatedAt: new Date().toISOString(),
121
+ actor,
122
+ planHash: reviewedPlanHash,
123
+ summary: summarizeSteps(steps, reports),
124
+ steps,
125
+ execution,
126
+ ...(nextAction ? { primaryNextAction: nextAction } : {}),
127
+ nextActions: nextAction ? [nextAction] : [],
128
+ issues
129
+ };
130
+ }
131
+ function createFinalizeReport(taskId, actor, mode, readOnly, steps, issues, planHash, execution, reports) {
132
+ const nextAction = createPrimaryNextAction(taskId, steps, issues);
133
+ return {
134
+ schemaVersion: 'hadara.task.finalize.v1',
135
+ command: 'task.finalize',
136
+ ok: mode === 'execute' ? false : issues.every((issue) => issue.severity !== 'error') && steps.every((step) => step.status === 'satisfied'),
137
+ readOnly,
138
+ mode,
139
+ taskId,
140
+ generatedAt: new Date().toISOString(),
141
+ actor,
142
+ ...(planHash ? { planHash } : {}),
143
+ summary: summarizeSteps(steps, reports),
144
+ steps,
145
+ ...(execution ? { execution } : {}),
146
+ ...(nextAction ? { primaryNextAction: nextAction } : {}),
147
+ nextActions: nextAction ? [nextAction] : [],
148
+ issues
149
+ };
150
+ }
151
+ function createExecuteRefusal(taskId, actor, code, message, currentPlanHash, steps, execution) {
152
+ return {
153
+ ...createFinalizeReport(taskId, actor, 'execute-refused', true, steps, [{ severity: 'error', code, message }], currentPlanHash, execution),
154
+ ok: false
155
+ };
156
+ }
157
+ function createFinalizeReports(projectRoot, taskId, actor) {
158
+ const finish = (0, task_finish_1.createTaskFinishReport)(projectRoot, taskId, 'dry-run', { actor });
159
+ const finishStatus = getFinishStatus(finish);
160
+ if (finishStatus === 'blocked')
161
+ return { finish };
162
+ if (finishStatus === 'required' && finish.status.taskStatus !== 'Done')
163
+ return { finish };
164
+ const close = (0, task_close_1.createTaskCloseReport)(projectRoot, taskId, 'dry-run', { actor });
165
+ const ready = (0, task_ready_1.createTaskReadyReportFromClosePlan)(projectRoot, taskId, 'done', close, actor);
166
+ if (!ready.ok)
167
+ return { finish, ready, close };
168
+ const audit = (0, task_close_1.createTaskAuditCloseReport)(projectRoot, taskId, { actor });
169
+ return { finish, ready, close, audit };
170
+ }
171
+ function createSteps(taskId, reports) {
172
+ const finishStatus = getFinishStatus(reports.finish);
173
+ const readyStatus = finishStatus === 'satisfied' ? (reports.ready ? (reports.ready.ok ? 'satisfied' : 'required') : 'pending') : 'pending';
174
+ const closeStatus = readyStatus === 'satisfied'
175
+ ? reports.audit?.auditVerdict.closeEvidenceFound
176
+ ? 'satisfied'
177
+ : reports.close?.ok
178
+ ? 'required'
179
+ : 'blocked'
180
+ : 'pending';
181
+ const auditStatus = reports.audit ? (reports.audit.auditVerdict.closeEvidenceFound ? (reports.audit.ok ? 'satisfied' : 'required') : 'pending') : 'pending';
182
+ return [
183
+ {
184
+ id: 'finish',
185
+ status: finishStatus,
186
+ summary: finishStatus === 'required' ? 'Apply bounded finish bookkeeping.' : finishStatus === 'satisfied' ? 'Finish bookkeeping is current.' : 'Finish blockers must be resolved.',
187
+ command: finishStatus === 'required' ? `hadara task finish --task ${taskId} --execute --json` : `hadara task finish --task ${taskId} --json`,
188
+ mode: finishStatus === 'required' ? 'execute' : 'dry-run',
189
+ writeBoundary: finishStatus === 'required' ? 'task-local' : 'read-only',
190
+ expectedWritePaths: reports.finish.writes.map((write) => write.path),
191
+ alreadySatisfied: finishStatus === 'satisfied',
192
+ sourceReport: 'hadara.task.finish.v1'
193
+ },
194
+ {
195
+ id: 'ready',
196
+ status: readyStatus,
197
+ summary: readyStatus === 'satisfied' ? 'Done-level readiness passed.' : readyStatus === 'pending' ? 'Ready waits for finish.' : 'Run readiness and resolve blockers.',
198
+ command: `hadara task ready --task ${taskId} --level done --json`,
199
+ mode: 'read-only',
200
+ writeBoundary: 'read-only',
201
+ expectedWritePaths: [],
202
+ alreadySatisfied: readyStatus === 'satisfied',
203
+ sourceReport: 'hadara.task.ready.v1'
204
+ },
205
+ {
206
+ id: 'close',
207
+ status: closeStatus,
208
+ summary: closeStatus === 'required' ? 'Append close evidence after reviewing close dry-run.' : closeStatus === 'satisfied' ? 'Close evidence exists.' : closeStatus === 'pending' ? 'Close waits for readiness.' : 'Close preconditions have blockers.',
209
+ command: closeStatus === 'required' ? `hadara task close --task ${taskId} --execute --json` : `hadara task close --task ${taskId} --json`,
210
+ mode: closeStatus === 'required' ? 'execute' : 'dry-run',
211
+ writeBoundary: closeStatus === 'required' ? 'evidence-append' : 'read-only',
212
+ expectedWritePaths: closeStatus === 'required' && reports.finish.task ? [`${reports.finish.task.capsule}/evidence.jsonl`] : [],
213
+ alreadySatisfied: closeStatus === 'satisfied',
214
+ sourceReport: 'hadara.task.close.v1'
215
+ },
216
+ {
217
+ id: 'audit-close',
218
+ status: auditStatus,
219
+ summary: auditStatus === 'satisfied' ? 'Close audit passed.' : auditStatus === 'pending' ? 'Audit waits for close evidence.' : 'Run close audit and repair any drift.',
220
+ command: `hadara task audit-close --task ${taskId} --json`,
221
+ mode: 'read-only',
222
+ writeBoundary: 'read-only',
223
+ expectedWritePaths: [],
224
+ alreadySatisfied: auditStatus === 'satisfied',
225
+ sourceReport: 'hadara.task.audit_close.v1'
226
+ }
227
+ ];
228
+ }
229
+ function collectIssues(reports) {
230
+ const seen = new Set();
231
+ const issues = [];
232
+ const reportIssues = [...reports.finish.issues, ...(reports.ready?.issues ?? []), ...(reports.close?.issues ?? []), ...(reports.audit?.issues ?? [])];
233
+ for (const issue of reportIssues) {
234
+ const key = `${issue.severity}:${issue.code}:${issue.path ?? ''}:${issue.message}`;
235
+ if (seen.has(key))
236
+ continue;
237
+ seen.add(key);
238
+ issues.push({
239
+ severity: issue.severity,
240
+ code: issue.code,
241
+ message: issue.message,
242
+ ...(issue.path ? { path: issue.path } : {}),
243
+ ...('fixHint' in issue && issue.fixHint ? { fixHint: issue.fixHint } : {}),
244
+ ...('example' in issue && issue.example ? { example: issue.example } : {})
245
+ });
246
+ }
247
+ const evidenceQualityIssue = issues.find(isEvidenceQualityIssue);
248
+ if (evidenceQualityIssue) {
249
+ issues.push({
250
+ severity: 'info',
251
+ code: 'TASK_FINALIZE_EVIDENCE_QUALITY_HINT',
252
+ message: 'Done-level evidence is missing substantive passed proof. Record validation evidence with --result passed and --category validation after a meaningful check succeeds.',
253
+ path: evidenceQualityIssue.path,
254
+ fixHint: 'Use evidence add-command with an explicit passed result/category for real validation output; do not rewrite existing unknown/failed evidence.',
255
+ example: 'hadara evidence add-command --task T-XXXX --summary "Focused validation passed." --result passed --category validation --json'
256
+ });
257
+ }
258
+ return issues;
259
+ }
260
+ function createPrimaryNextAction(taskId, steps, issues) {
261
+ const nextStep = steps.find((step) => step.status === 'required' || step.status === 'blocked');
262
+ if (!nextStep)
263
+ return undefined;
264
+ if (nextStep.id === 'ready' && issues.some(isEvidenceQualityIssue)) {
265
+ return (0, lifecycle_next_actions_1.createTaskLifecycleNextAction)({
266
+ id: 'finalize-record-passed-evidence',
267
+ kind: 'command',
268
+ required: true,
269
+ command: `hadara evidence add-command --task ${taskId} --summary "Focused validation passed." --result passed --category validation --json`,
270
+ message: 'Record substantive passed validation evidence before rerunning readiness.',
271
+ writeBoundary: 'evidence-append',
272
+ recommendedActorRole: 'worker',
273
+ requiresBeforeHash: false,
274
+ stalePlanRisk: 'low'
275
+ });
276
+ }
277
+ return (0, lifecycle_next_actions_1.createTaskLifecycleNextAction)({
278
+ id: `finalize-${nextStep.id}`,
279
+ kind: nextStep.status === 'blocked' ? 'review' : 'command',
280
+ required: true,
281
+ command: nextStep.status === 'blocked' ? undefined : nextStep.command,
282
+ message: nextStep.summary,
283
+ writeBoundary: nextStep.writeBoundary,
284
+ recommendedActorRole: nextStep.writeBoundary === 'read-only' ? 'reviewer' : 'worker',
285
+ requiresBeforeHash: false,
286
+ stalePlanRisk: nextStep.writeBoundary === 'read-only' ? 'none' : 'low'
287
+ });
288
+ }
289
+ function summarizeSteps(steps, reports) {
290
+ const evaluatedReports = evaluatedReportNames(steps, reports);
291
+ const skippedReports = ['finish', 'ready', 'close', 'audit-close'].filter((name) => !evaluatedReports.includes(name));
292
+ return {
293
+ steps: steps.length,
294
+ required: steps.filter((step) => step.status === 'required').length,
295
+ blocked: steps.filter((step) => step.status === 'blocked').length,
296
+ satisfied: steps.filter((step) => step.status === 'satisfied').length,
297
+ executeSupported: true,
298
+ evaluatedReports,
299
+ skippedReports
300
+ };
301
+ }
302
+ function createExecutedStep(step, ok, report, status) {
303
+ return {
304
+ id: step.id,
305
+ status,
306
+ command: step.command,
307
+ ok,
308
+ reportHash: hashReport(report),
309
+ summary: step.summary,
310
+ writeBoundary: step.writeBoundary
311
+ };
312
+ }
313
+ function hashReport(report) {
314
+ return `sha256:${node_crypto_1.default.createHash('sha256').update(JSON.stringify(report)).digest('hex')}`;
315
+ }
316
+ function reportKeyForStep(stepId) {
317
+ if (stepId === 'finish')
318
+ return 'finish';
319
+ if (stepId === 'ready')
320
+ return 'ready';
321
+ if (stepId === 'close')
322
+ return 'close';
323
+ return 'audit';
324
+ }
325
+ function getFinishStatus(finish) {
326
+ return finish.ok ? (finish.summary.plannedWrites > 0 ? 'required' : 'satisfied') : 'blocked';
327
+ }
328
+ function evaluatedReportNames(steps, reports) {
329
+ if (reports)
330
+ return ['finish', ...(reports.ready ? ['ready'] : []), ...(reports.close ? ['close'] : []), ...(reports.audit ? ['audit-close'] : [])];
331
+ const evaluated = new Set(['finish']);
332
+ for (const step of steps) {
333
+ if (step.id === 'ready' && step.status !== 'pending')
334
+ evaluated.add('ready');
335
+ if (step.id === 'close' && step.status !== 'pending')
336
+ evaluated.add('close');
337
+ if (step.id === 'audit-close' && step.status !== 'pending')
338
+ evaluated.add('audit-close');
339
+ }
340
+ return [...evaluated];
341
+ }
342
+ function isEvidenceQualityIssue(issue) {
343
+ return (issue.code.includes('TASK_DONE_WITHOUT_SUBSTANTIVE_EVIDENCE') ||
344
+ issue.code.includes('TASK_DONE_WITH_ONLY_WEAK_EVIDENCE') ||
345
+ issue.code.includes('EVIDENCE_REQUIRED') ||
346
+ issue.code.includes('WITHOUT_SUBSTANTIVE_EVIDENCE') ||
347
+ issue.code.includes('ONLY_WEAK_EVIDENCE'));
348
+ }
349
+ function fallbackStep(taskId, id) {
350
+ return {
351
+ id,
352
+ status: 'unknown',
353
+ summary: 'Step state could not be determined.',
354
+ command: id === 'audit-close' ? `hadara task audit-close --task ${taskId} --json` : `hadara task ${id} --task ${taskId} --json`,
355
+ mode: 'read-only',
356
+ writeBoundary: 'read-only',
357
+ expectedWritePaths: [],
358
+ alreadySatisfied: false,
359
+ sourceReport: 'unknown'
360
+ };
361
+ }
362
+ function hashPlan(taskId, steps) {
363
+ const stable = {
364
+ taskId,
365
+ steps: steps.map((step) => ({
366
+ id: step.id,
367
+ status: step.status,
368
+ command: step.command,
369
+ mode: step.mode,
370
+ writeBoundary: step.writeBoundary,
371
+ expectedWritePaths: step.expectedWritePaths,
372
+ alreadySatisfied: step.alreadySatisfied,
373
+ sourceReport: step.sourceReport
374
+ }))
375
+ };
376
+ return `sha256:${node_crypto_1.default.createHash('sha256').update(JSON.stringify(stable)).digest('hex')}`;
377
+ }
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTaskLifecycleReport = createTaskLifecycleReport;
4
+ exports.formatTaskLifecycleReport = formatTaskLifecycleReport;
5
+ const task_close_1 = require("./task-close");
6
+ const task_finish_1 = require("./task-finish");
7
+ const lifecycle_next_actions_1 = require("./lifecycle-next-actions");
8
+ const task_ready_1 = require("./task-ready");
9
+ function createTaskLifecycleReport(projectRoot, taskId, options = {}) {
10
+ const actor = options.actor ?? (0, lifecycle_next_actions_1.defaultTaskLifecycleActor)();
11
+ const reports = {
12
+ finish: (0, task_finish_1.createTaskFinishReport)(projectRoot, taskId, 'dry-run', { actor }),
13
+ ready: (0, task_ready_1.createTaskReadyReport)(projectRoot, taskId, 'done', { actor }),
14
+ close: (0, task_close_1.createTaskCloseReport)(projectRoot, taskId, 'dry-run', { actor }),
15
+ audit: (0, task_close_1.createTaskAuditCloseReport)(projectRoot, taskId, { actor })
16
+ };
17
+ const checks = createChecks(taskId, reports);
18
+ const repair = createRepair(taskId, reports.audit);
19
+ const phase = choosePhase(reports, checks, repair);
20
+ const nextAction = chooseNextAction(taskId, phase, reports, checks, repair);
21
+ const issues = collectIssues(reports);
22
+ const blockers = collectBlockers(phase, reports, checks, repair);
23
+ const satisfied = Object.entries(checks)
24
+ .filter(([, check]) => check.status === 'satisfied')
25
+ .map(([id]) => id);
26
+ return {
27
+ schemaVersion: 'hadara.task.lifecycle.v1',
28
+ command: 'task.lifecycle',
29
+ ok: !reports.finish.issues.some((issue) => issue.code === 'TASK_NOT_FOUND'),
30
+ readOnly: true,
31
+ taskId,
32
+ generatedAt: new Date().toISOString(),
33
+ actor,
34
+ phase,
35
+ checks,
36
+ satisfied,
37
+ blockers,
38
+ ...(repair ? { repair } : {}),
39
+ ...(nextAction ? { primaryNextAction: nextAction } : {}),
40
+ nextActions: nextAction ? [nextAction] : [],
41
+ issues
42
+ };
43
+ }
44
+ function formatTaskLifecycleReport(report) {
45
+ const lines = [`[HADARA] task lifecycle ${report.taskId}: ${report.phase}`];
46
+ lines.push(`readOnly=${report.readOnly} ok=${report.ok}`);
47
+ if (report.primaryNextAction)
48
+ lines.push(`next=${report.primaryNextAction.command ?? report.primaryNextAction.summary ?? report.primaryNextAction.id}`);
49
+ for (const [id, check] of Object.entries(report.checks))
50
+ lines.push(`${check.status.toUpperCase()}\t${id}\t${check.summary}`);
51
+ for (const blocker of report.blockers)
52
+ lines.push(`[${blocker.severity}] ${blocker.code}: ${blocker.summary}`);
53
+ if (report.repair && report.repair.classification !== 'closed-valid')
54
+ lines.push(`repair=${report.repair.classification}\t${report.repair.summary}`);
55
+ return lines.join('\n');
56
+ }
57
+ function createChecks(taskId, reports) {
58
+ const sharedDocsPending = reports.finish.stateDocs.filter((doc) => doc.state === 'pending' || doc.state === 'missing');
59
+ return {
60
+ finish: {
61
+ status: reports.finish.ok ? (reports.finish.summary.plannedWrites > 0 ? 'required' : 'satisfied') : 'blocked',
62
+ summary: reports.finish.summary.plannedWrites > 0 ? 'Task status bookkeeping has planned writes.' : reports.finish.ok ? 'Task finish bookkeeping is current.' : 'Task finish blockers exist.',
63
+ sourceReport: 'hadara.task.finish.v1',
64
+ command: reports.finish.summary.plannedWrites > 0 ? `hadara task finish --task ${taskId} --json` : undefined
65
+ },
66
+ sharedDocs: {
67
+ status: sharedDocsPending.length > 0 ? 'warning' : reports.finish.ok ? 'satisfied' : 'pending',
68
+ summary: sharedDocsPending.length > 0 ? `${sharedDocsPending.length} shared state doc(s) are pending or missing this task.` : 'Shared state docs are current for this task.',
69
+ sourceReport: 'hadara.task.finish.v1'
70
+ },
71
+ ready: {
72
+ status: reports.ready.ok ? 'satisfied' : 'required',
73
+ summary: reports.ready.ok ? 'Done-level readiness passed.' : 'Done-level readiness requires remediation.',
74
+ sourceReport: 'hadara.task.ready.v1',
75
+ command: `hadara task ready --task ${taskId} --level done --json`
76
+ },
77
+ close: {
78
+ status: reports.audit.auditVerdict.closeEvidenceFound ? 'satisfied' : reports.close.ok ? 'required' : 'blocked',
79
+ summary: reports.audit.auditVerdict.closeEvidenceFound ? 'Close evidence exists.' : reports.close.ok ? 'Close evidence has not been appended.' : 'Close preconditions have blockers.',
80
+ sourceReport: 'hadara.task.close.v1',
81
+ command: reports.audit.auditVerdict.closeEvidenceFound ? undefined : `hadara task close --task ${taskId} --json`
82
+ },
83
+ audit: {
84
+ status: reports.audit.ok ? 'satisfied' : reports.audit.auditVerdict.closeEvidenceFound ? 'required' : 'pending',
85
+ summary: reports.audit.ok ? 'Close audit passed.' : reports.audit.auditVerdict.closeEvidenceFound ? 'Close audit requires repair.' : 'Audit waits for close evidence.',
86
+ sourceReport: 'hadara.task.audit_close.v1',
87
+ command: `hadara task audit-close --task ${taskId} --json`
88
+ }
89
+ };
90
+ }
91
+ function choosePhase(reports, checks, repair) {
92
+ if (reports.finish.issues.some((issue) => issue.code === 'TASK_NOT_FOUND'))
93
+ return 'unknown';
94
+ if (!reports.finish.ok)
95
+ return 'blocked';
96
+ if (checks.finish.status === 'required')
97
+ return 'finish-required';
98
+ if (checks.sharedDocs.status === 'warning')
99
+ return 'ready-required';
100
+ if (!reports.ready.ok)
101
+ return 'ready-required';
102
+ if (!reports.close.ok)
103
+ return 'ready-required';
104
+ if (!reports.audit.auditVerdict.closeEvidenceFound)
105
+ return 'close-required';
106
+ if (reports.audit.ok)
107
+ return 'closed-valid';
108
+ if (repair && repair.classification !== 'closed-valid')
109
+ return 'repair-required';
110
+ return 'audit-required';
111
+ }
112
+ function chooseNextAction(taskId, phase, reports, checks, repair) {
113
+ if (phase === 'closed-valid')
114
+ return undefined;
115
+ if (phase === 'unknown')
116
+ return reviewAction('select-existing-task', `Select an existing Task Capsule before inspecting lifecycle state for ${taskId}.`);
117
+ if (phase === 'blocked')
118
+ return reports.finish.primaryNextAction ?? reviewAction('resolve-finish-blockers', `Resolve task finish blockers for ${taskId}.`);
119
+ if (phase === 'finish-required')
120
+ return reports.finish.primaryNextAction;
121
+ if (checks.sharedDocs.status === 'warning') {
122
+ return reports.finish.primaryNextAction ?? reviewAction('update-state-docs', `Update shared state docs for ${taskId} before close.`, 'shared-doc', 'coordinator', true, 'medium');
123
+ }
124
+ if (phase === 'ready-required')
125
+ return reports.ready.primaryNextAction ?? reports.close.primaryNextAction;
126
+ if (phase === 'close-required')
127
+ return reports.close.nextActions.find((action) => action.id === 'append-close-evidence') ?? reports.close.primaryNextAction;
128
+ if (phase === 'repair-required') {
129
+ return reviewAction('repair-close-proof', repair?.summary ?? `Repair close proof for ${taskId}.`, 'read-only', 'reviewer', false, 'none', repair?.nextCommand);
130
+ }
131
+ if (phase === 'audit-required')
132
+ return reports.audit.primaryNextAction ?? reviewAction('run-audit-close', `Run close audit for ${taskId}.`, 'read-only', 'reviewer', false, 'none', `hadara task audit-close --task ${taskId} --json`);
133
+ return undefined;
134
+ }
135
+ function createRepair(taskId, audit) {
136
+ if (audit.ok) {
137
+ return {
138
+ classification: 'closed-valid',
139
+ summary: 'Close proof is current and valid.',
140
+ sourceReport: 'hadara.task.audit_close.v1'
141
+ };
142
+ }
143
+ if (!audit.auditVerdict.closeEvidenceFound) {
144
+ return {
145
+ classification: 'not-closed',
146
+ summary: 'No close evidence exists for this task.',
147
+ nextCommand: `hadara task close --task ${taskId} --json`,
148
+ sourceReport: 'hadara.task.audit_close.v1'
149
+ };
150
+ }
151
+ if (!audit.auditVerdict.closeEvidenceValid) {
152
+ return {
153
+ classification: 'closed-invalid',
154
+ summary: 'Close evidence exists but is invalid or malformed.',
155
+ nextCommand: `hadara task close --task ${taskId} --json`,
156
+ sourceReport: 'hadara.task.audit_close.v1'
157
+ };
158
+ }
159
+ if (!audit.auditVerdict.reportHashMatches || !audit.auditVerdict.sourceHashMatches) {
160
+ return {
161
+ classification: 'closed-stale',
162
+ summary: 'Close evidence exists but no longer matches current validation or source hashes.',
163
+ nextCommand: `hadara task close --task ${taskId} --json`,
164
+ sourceReport: 'hadara.task.audit_close.v1'
165
+ };
166
+ }
167
+ return {
168
+ classification: 'unknown',
169
+ summary: 'Close audit did not pass and no specific repair classification matched.',
170
+ nextCommand: `hadara task audit-close --task ${taskId} --json`,
171
+ sourceReport: 'hadara.task.audit_close.v1'
172
+ };
173
+ }
174
+ function collectBlockers(phase, reports, checks, repair) {
175
+ const blockers = [];
176
+ if (phase === 'unknown')
177
+ blockers.push({ code: 'TASK_LIFECYCLE_TASK_UNKNOWN', severity: 'error', summary: 'Task was not found.' });
178
+ if (phase === 'blocked')
179
+ blockers.push({ code: 'TASK_LIFECYCLE_FINISH_BLOCKED', severity: 'error', summary: 'Task finish report has blockers.', command: reports.finish.primaryNextAction?.command });
180
+ if (checks.sharedDocs.status === 'warning')
181
+ blockers.push({ code: 'TASK_LIFECYCLE_SHARED_DOCS_PENDING', severity: 'warning', summary: checks.sharedDocs.summary, command: reports.finish.primaryNextAction?.command });
182
+ if (phase === 'repair-required' && repair)
183
+ blockers.push({ code: 'TASK_LIFECYCLE_CLOSE_REPAIR_REQUIRED', severity: 'warning', summary: repair.summary, command: repair.nextCommand });
184
+ return blockers;
185
+ }
186
+ function collectIssues(reports) {
187
+ const seen = new Set();
188
+ const issues = [];
189
+ for (const issue of [...reports.finish.issues, ...reports.ready.issues, ...reports.close.issues, ...reports.audit.issues]) {
190
+ const key = `${issue.severity}:${issue.code}:${issue.path ?? ''}:${issue.message}`;
191
+ if (seen.has(key))
192
+ continue;
193
+ seen.add(key);
194
+ issues.push({ severity: issue.severity, code: issue.code, message: issue.message, ...(issue.path ? { path: issue.path } : {}) });
195
+ }
196
+ return issues;
197
+ }
198
+ function reviewAction(id, message, writeBoundary = 'read-only', recommendedActorRole = 'worker', requiresBeforeHash = false, stalePlanRisk = 'none', command) {
199
+ return (0, lifecycle_next_actions_1.createTaskLifecycleNextAction)({
200
+ id,
201
+ kind: command ? 'command' : 'review',
202
+ required: true,
203
+ message,
204
+ ...(command ? { command } : {}),
205
+ writeBoundary,
206
+ recommendedActorRole,
207
+ requiresBeforeHash,
208
+ stalePlanRisk
209
+ });
210
+ }
@@ -97,7 +97,7 @@ function recommendationFromHandoff(projectRoot, step, boardRows, capsules) {
97
97
  };
98
98
  }
99
99
  function recommendationFromTaskBoard(boardRows, capsules) {
100
- const row = boardRows.find((candidate) => isOpenBoardStatus(candidate.status));
100
+ const row = boardRows.find((candidate) => isPrimaryOpenBoardStatus(candidate.status)) ?? boardRows.find((candidate) => isOpenBoardStatus(candidate.status));
101
101
  if (!row)
102
102
  return null;
103
103
  const capsule = capsules.find((task) => task.id === row.taskId);
@@ -169,10 +169,16 @@ function isActionableHandoffStep(step) {
169
169
  const normalized = step.trim().toLowerCase();
170
170
  if (!normalized || normalized === 'step' || normalized === 'tbd')
171
171
  return false;
172
+ if (isTaskNextMetaGuidance(normalized))
173
+ return false;
172
174
  if (normalized.startsWith('migrate selected historical evidence only when explicitly requested'))
173
175
  return false;
174
176
  return true;
175
177
  }
178
+ function isTaskNextMetaGuidance(normalizedStep) {
179
+ const compact = normalizedStep.replace(/[`*_]/g, '').replace(/\s+/g, ' ');
180
+ return /\b(hadara\s+)?task\s+next\b/.test(compact) && /\b(run|select|choose|create)\b/.test(compact);
181
+ }
176
182
  function normalizeHandoffTitle(step) {
177
183
  return step
178
184
  .replace(/^continue\s+(with\s+)?/i, '')
@@ -189,6 +195,9 @@ function isOpenBoardStatus(status) {
189
195
  const normalized = status.trim().toLowerCase();
190
196
  return Boolean(normalized) && !normalized.startsWith('done') && !normalized.startsWith('superseded');
191
197
  }
198
+ function isPrimaryOpenBoardStatus(status) {
199
+ return isOpenBoardStatus(status) && !status.trim().toLowerCase().startsWith('partial');
200
+ }
192
201
  function toPortablePath(value) {
193
202
  return value.split(node_path_1.default.sep).join('/');
194
203
  }
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createTaskReadyReport = createTaskReadyReport;
4
+ exports.createTaskReadyReportFromClosePlan = createTaskReadyReportFromClosePlan;
4
5
  const task_close_1 = require("./task-close");
5
6
  const lifecycle_next_actions_1 = require("./lifecycle-next-actions");
6
7
  function createTaskReadyReport(projectRoot, taskId, level = 'done', options = {}) {
7
8
  const actor = options.actor ?? (0, lifecycle_next_actions_1.defaultTaskLifecycleActor)();
8
9
  const closePlan = (0, task_close_1.createTaskCloseReport)(projectRoot, taskId, 'dry-run', { actor });
10
+ return createTaskReadyReportFromClosePlan(projectRoot, taskId, level, closePlan, actor);
11
+ }
12
+ function createTaskReadyReportFromClosePlan(projectRoot, taskId, level, closePlan, actor) {
9
13
  const ready = closePlan.ok;
10
14
  const nextActions = ready
11
15
  ? [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadara",
3
- "version": "0.3.2",
3
+ "version": "0.3.3-rc.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Portable AI-assisted development workbench for evidence-backed task capsules, handoffs, and release gates.",