hadara 0.4.0-rc.0 → 0.4.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 (39) hide show
  1. package/README.md +19 -19
  2. package/dist/cli/doctor.js +74 -3
  3. package/dist/cli/evidence-json.js +3 -0
  4. package/dist/cli/evidence.js +1 -0
  5. package/dist/cli/release-artifact.js +1 -1
  6. package/dist/cli/task.js +65 -11
  7. package/dist/cli/validation.js +14 -4
  8. package/dist/core/timing.js +12 -0
  9. package/dist/dev/docker-check.js +3 -2
  10. package/dist/evidence/evidence.js +3 -2
  11. package/dist/handoff/handoff-suggestion.js +1 -0
  12. package/dist/handoff/handoff.js +1 -0
  13. package/dist/harness/validate.js +140 -36
  14. package/dist/services/capability-registry.js +6 -5
  15. package/dist/services/ci-gate.js +1 -0
  16. package/dist/services/clean-checkout-smoke.js +4 -2
  17. package/dist/services/dashboard-bootstrap.js +16 -1
  18. package/dist/services/dashboard-task-detail.js +1 -0
  19. package/dist/services/package-recycle.js +5 -2
  20. package/dist/services/package-smoke.js +7 -2
  21. package/dist/services/proof-status.js +2 -0
  22. package/dist/services/protocol-consistency.js +2 -1
  23. package/dist/services/protocol-migration.js +1 -0
  24. package/dist/services/release-artifact.js +3 -2
  25. package/dist/services/release-closeout.js +1 -0
  26. package/dist/services/release-diagnostics.js +5 -4
  27. package/dist/services/release-dry-run.js +3 -2
  28. package/dist/services/task-read-model.js +4 -0
  29. package/dist/services/task-workbench.js +27 -11
  30. package/dist/services/validation-run.js +11 -3
  31. package/dist/services/write-preflight.js +2 -13
  32. package/dist/task/acceptance.js +27 -5
  33. package/dist/task/authoring-guidance.js +6 -4
  34. package/dist/task/task-capsule.js +20 -9
  35. package/dist/task/task-create.js +1 -0
  36. package/dist/task/task-templates.js +1 -1
  37. package/dist/task/task-upgrade-scaffold.js +8 -6
  38. package/dist/tui/read-model.js +2 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <img alt="Stable npm release" src="https://img.shields.io/badge/npm-0.3.3-blue">
9
- <img alt="Source version" src="https://img.shields.io/badge/source-0.4.0--rc.0-blue">
8
+ <img alt="Stable npm release" src="https://img.shields.io/badge/npm-0.4.0-blue">
9
+ <img alt="Next npm release" src="https://img.shields.io/badge/next-0.4.0--rc.0-blue">
10
10
  <img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D22-brightgreen">
11
11
  <img alt="License" src="https://img.shields.io/badge/license-MIT-lightgrey">
12
12
  </p>
@@ -24,30 +24,30 @@ This repository is both the HADARA source checkout and the HADARA protocol works
24
24
  Current stable npm release:
25
25
 
26
26
  ```text
27
- hadara@0.3.3
27
+ hadara@0.4.0
28
28
  ```
29
29
 
30
- Current source release candidate:
30
+ Previous npm release candidate:
31
31
 
32
32
  ```text
33
33
  hadara@0.4.0-rc.0
34
34
  ```
35
35
 
36
- Previous published release candidate:
36
+ Previous agent UX release candidate:
37
37
 
38
38
  ```text
39
39
  hadara@0.3.4-rc.0
40
40
  ```
41
41
 
42
- The 0.4.0 source line is a breaking productization release candidate after stable 0.3.3 and the published 0.3.4-rc.0 agent UX line. It keeps the context-routing and finalize-first lifecycle baseline, then simplifies new projects around a productized 0.4 scaffold, registry-backed docs read maps, a four-file Task Capsule, generated evidence projection, a normalized close-source contract, and fail-closed legacy mutation boundaries.
42
+ The 0.4.0 source line is a breaking productization release after stable 0.3.3 and the published 0.3.4-rc.0 agent UX line. It keeps the context-routing and finalize-first lifecycle baseline, then simplifies new projects around a productized 0.4 scaffold, registry-backed docs read maps, a four-file Task Capsule, generated evidence projection, a normalized close-source contract, and fail-closed legacy mutation boundaries.
43
43
 
44
- Phase labels are internal implementation phases, not npm release-candidate labels. The stable `0.3.3` package is the default install target; `0.4.0-rc.0` is the current source candidate and must not be treated as published until an approval-gated publish capsule completes.
44
+ Phase labels are internal implementation phases, not npm release-candidate labels. The stable `0.4.0` package is the default install target through the `latest` dist-tag after the approval-gated publish completes; `0.4.0-rc.0` remains the previous release candidate on npm under the `next` dist-tag.
45
45
 
46
46
  | Surface | Status |
47
47
  |---|---|
48
- | Current stable | [`hadara@0.3.3`](docs/RELEASE_NOTES.md#033) |
49
- | Source candidate | [`hadara@0.4.0-rc.0`](docs/RELEASE_NOTES.md#040-rc0) |
50
- | Previous RC | [`hadara@0.3.4-rc.0`](docs/RELEASE_NOTES.md#034-rc0) |
48
+ | Current stable | [`hadara@0.4.0`](docs/RELEASE_NOTES.md#040) |
49
+ | Previous RC | [`hadara@0.4.0-rc.0`](docs/RELEASE_NOTES.md#040-rc0) on `next` |
50
+ | Previous agent UX RC | [`hadara@0.3.4-rc.0`](docs/RELEASE_NOTES.md#034-rc0) |
51
51
  | Previous stable | [`hadara@0.3.2`](docs/RELEASE_NOTES.md#032) |
52
52
  | Historical RCs | See [Release Notes](docs/RELEASE_NOTES.md). |
53
53
  | GitHub Release | Secondary target, approval-gated. |
@@ -55,7 +55,7 @@ Phase labels are internal implementation phases, not npm release-candidate label
55
55
  | PyPI/Python package | `hadara==0.2.0rc1` published preview bridge. |
56
56
  | Installer scripts / USB launchers | Deferred. |
57
57
 
58
- No release command should publish, create a GitHub Release, build Docker images, upload artifacts, or load token values unless an operator explicitly approves the mutation path for the active release capsule. Stable `0.3.3` is the npm package line until the `0.4.0-rc.0` publish capsule completes; GitHub Release, Docker/PyPI publish, installer execution, and MCP release/package execution remain separate explicit mutations.
58
+ No release command should publish, create a GitHub Release, build Docker images, upload artifacts, or load token values unless an operator explicitly approves the mutation path for the active release capsule. Stable `0.4.0` is the default npm package line after the T-0490 approval-gated publish completes; `0.4.0-rc.0` remains available for explicit RC evaluation through `hadara@next` or the exact version. The `v0.4.0-rc.0` GitHub Release exists as a draft prerelease; Docker/PyPI publish, installer execution, and MCP release/package execution remain separate explicit mutations.
59
59
 
60
60
  ## Install
61
61
 
@@ -64,7 +64,7 @@ Requires Node.js 22.
64
64
  Install the stable release:
65
65
 
66
66
  ```bash
67
- npm install -g hadara@0.3.3
67
+ npm install -g hadara@0.4.0
68
68
  hadara help
69
69
  hadara doctor --json
70
70
  ```
@@ -72,11 +72,11 @@ hadara doctor --json
72
72
  Run without a global install:
73
73
 
74
74
  ```bash
75
- npx hadara@0.3.3 help
76
- npx hadara@0.3.3 doctor --json
75
+ npx hadara@0.4.0 help
76
+ npx hadara@0.4.0 doctor --json
77
77
  ```
78
78
 
79
- After `0.4.0-rc.0` is published, evaluate it explicitly:
79
+ Evaluate the previous `0.4.0-rc.0` explicitly:
80
80
 
81
81
  ```bash
82
82
  npm install -g hadara@0.4.0-rc.0
@@ -87,11 +87,11 @@ For release or recycle evidence, prefer an isolated prefix install when PATH, gl
87
87
 
88
88
  ```bash
89
89
  tmp="$(mktemp -d)"
90
- npm --prefix "$tmp" install hadara@0.3.3
90
+ npm --prefix "$tmp" install hadara@0.4.0
91
91
  "$tmp/node_modules/.bin/hadara" version --json
92
92
  ```
93
93
 
94
- `npx hadara@0.3.3 ...` remains convenient for normal use. The isolated installed-bin path is stronger proof that the published package installed and executed from the intended package tree.
94
+ `npx hadara@0.4.0 ...` remains convenient for normal use. The isolated installed-bin path is stronger proof that the published package installed and executed from the intended package tree.
95
95
 
96
96
  ## What HADARA Gives You
97
97
 
@@ -268,7 +268,7 @@ Dashboard, TUI, Hermes, MCP, installer, package, release, and run commands stay
268
268
 
269
269
  ## Safety Boundaries
270
270
 
271
- HADARA 0.4.0-rc.0 is not:
271
+ HADARA 0.4.0 is not:
272
272
 
273
273
  - a full agent runtime;
274
274
  - Rack/enterprise behavior;
@@ -308,7 +308,7 @@ Portable/local state is not committed. Project docs, Task Capsules, and reduced
308
308
 
309
309
  `evidence.jsonl` is the canonical Task Capsule evidence source. `EVIDENCE.md` is a non-canonical human summary that can help review validation history, but it must not be treated as the source of truth for rebuild, migration, or resolution logic.
310
310
 
311
- 0.4.0-rc.0 does not implement `hadara evidence rebuild --json` or an execute mode. Future rebuild work must first define whether a difference is formatting regeneration, managed-section drift, or data inconsistency before reporting `wouldChange`. Any later write-capable rebuild flow must be dry-run-first, reviewed, and before-hash guarded before it rewrites derived Markdown.
311
+ 0.4.0 does not implement `hadara evidence rebuild --json` or an execute mode. Future rebuild work must first define whether a difference is formatting regeneration, managed-section drift, or data inconsistency before reporting `wouldChange`. Any later write-capable rebuild flow must be dry-run-first, reviewed, and before-hash guarded before it rewrites derived Markdown.
312
312
 
313
313
  ## Development / Contributing
314
314
 
@@ -7,18 +7,33 @@ exports.createDoctorReport = createDoctorReport;
7
7
  exports.formatDoctorReport = formatDoctorReport;
8
8
  exports.handleDoctorCommand = handleDoctorCommand;
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
- function createDoctorReport(paths, nodeVersion = process.version) {
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const package_json_1 = __importDefault(require("../../package.json"));
12
+ function createDoctorReport(paths, nodeVersion = process.version, options = {}) {
11
13
  const checks = [
12
14
  pathCheck('docs', paths.projectDocsDir),
13
15
  pathCheck('tasks', paths.projectTasksDir),
14
16
  pathCheck('project-context', `${paths.projectContextDir}/HADARA_CONTEXT.md`)
15
17
  ];
18
+ const executablePath = normalizeOptionalPath(options.cliEntry ?? process.argv[1] ?? null);
19
+ const resolvedExecutablePath = resolveExecutablePath(executablePath);
20
+ const packageRoot = findPackageRoot([resolvedExecutablePath, executablePath]);
16
21
  return {
17
22
  schemaVersion: 'hadara.doctor.v1',
18
23
  command: 'doctor',
19
24
  ok: checks.every((check) => check.status === 'ok'),
20
25
  runtime: {
21
- node: nodeVersion
26
+ node: nodeVersion,
27
+ nodePath: normalizePath(options.nodePath ?? process.execPath)
28
+ },
29
+ installation: {
30
+ executablePath,
31
+ resolvedExecutablePath,
32
+ packageRoot,
33
+ packageVersion: package_json_1.default.version,
34
+ registry: 'https://registry.npmjs.org',
35
+ installCommand: `npm install -g ${package_json_1.default.name}@${package_json_1.default.version}`,
36
+ latestInstallCommand: `npm install -g ${package_json_1.default.name}`
22
37
  },
23
38
  paths: {
24
39
  portableRoot: paths.portableRoot,
@@ -35,11 +50,18 @@ function formatDoctorReport(report) {
35
50
  `dataRoot: ${report.paths.dataRoot}`,
36
51
  `projectRoot: ${report.paths.projectRoot}`,
37
52
  `Node: ${report.runtime.node}`,
53
+ `Node path: ${report.runtime.nodePath}`,
54
+ 'Install:',
55
+ ` executable: ${report.installation.executablePath ?? 'unknown'}`,
56
+ ` resolved: ${report.installation.resolvedExecutablePath ?? 'unknown'}`,
57
+ ` package: ${report.installation.packageVersion}${report.installation.packageRoot ? ` at ${report.installation.packageRoot}` : ' (package root unknown)'}`,
58
+ ` registry: ${report.installation.registry}`,
59
+ ` install: ${report.installation.installCommand}`,
38
60
  ...report.checks.map((check) => `${formatCheckLabel(check.id)}${check.status}`)
39
61
  ].join('\n');
40
62
  }
41
63
  function handleDoctorCommand(input) {
42
- const report = createDoctorReport(input.paths);
64
+ const report = createDoctorReport(input.paths, process.version, { cliEntry: process.argv[1] });
43
65
  if (input.jsonOutput) {
44
66
  console.log(JSON.stringify(report, null, 2));
45
67
  }
@@ -57,6 +79,55 @@ function pathCheck(id, targetPath) {
57
79
  path: targetPath
58
80
  };
59
81
  }
82
+ function normalizeOptionalPath(value) {
83
+ if (!value)
84
+ return null;
85
+ return normalizePath(value);
86
+ }
87
+ function normalizePath(value) {
88
+ return node_path_1.default.normalize(value);
89
+ }
90
+ function resolveExecutablePath(executablePath) {
91
+ if (!executablePath)
92
+ return null;
93
+ try {
94
+ return normalizePath(node_fs_1.default.realpathSync.native(executablePath));
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ function findPackageRoot(candidates) {
101
+ for (const candidate of candidates) {
102
+ const found = candidate ? findPackageRootFromFile(candidate) : null;
103
+ if (found)
104
+ return found;
105
+ }
106
+ return null;
107
+ }
108
+ function findPackageRootFromFile(filePath) {
109
+ let current = node_fs_1.default.existsSync(filePath) && node_fs_1.default.statSync(filePath).isDirectory()
110
+ ? filePath
111
+ : node_path_1.default.dirname(filePath);
112
+ while (true) {
113
+ const packagePath = node_path_1.default.join(current, 'package.json');
114
+ if (isHadaraPackageJson(packagePath))
115
+ return normalizePath(current);
116
+ const parent = node_path_1.default.dirname(current);
117
+ if (parent === current)
118
+ return null;
119
+ current = parent;
120
+ }
121
+ }
122
+ function isHadaraPackageJson(packagePath) {
123
+ try {
124
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(packagePath, 'utf8'));
125
+ return parsed.name === package_json_1.default.name;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ }
60
131
  function formatCheckLabel(id) {
61
132
  if (id === 'docs')
62
133
  return 'docs/: ';
@@ -15,6 +15,7 @@ function createEvidenceCollectReport(projectRoot, input) {
15
15
  schemaVersion: 'hadara.evidence.collect.v1',
16
16
  command: 'evidence.collect',
17
17
  ok: false,
18
+ taskId: input.taskId,
18
19
  issues: [
19
20
  {
20
21
  severity: 'error',
@@ -49,6 +50,7 @@ function createEvidenceCollectReport(projectRoot, input) {
49
50
  schemaVersion: 'hadara.evidence.collect.v1',
50
51
  command: 'evidence.collect',
51
52
  ok: false,
53
+ taskId: input.taskId,
52
54
  issues: [
53
55
  {
54
56
  severity: 'error',
@@ -64,6 +66,7 @@ function createEvidenceCollectReport(projectRoot, input) {
64
66
  schemaVersion: 'hadara.evidence.collect.v1',
65
67
  command: 'evidence.collect',
66
68
  ok: true,
69
+ taskId: input.taskId,
67
70
  evidence: {
68
71
  ...appendResult.evidence,
69
72
  markdownPath: toPortablePath(node_path_1.default.relative(projectRoot, appendResult.markdownPath)),
@@ -138,6 +138,7 @@ function handleEvidenceCommand(input) {
138
138
  schemaVersion: 'hadara.evidence.collect.v1',
139
139
  command: 'evidence.add-command',
140
140
  ok: false,
141
+ taskId,
141
142
  issues: [resultOutcomeIssue]
142
143
  };
143
144
  if (input.jsonOutput) {
@@ -38,7 +38,7 @@ function handleReleaseArtifactCommand(input) {
38
38
  })
39
39
  : undefined;
40
40
  if (input.jsonOutput) {
41
- console.log(JSON.stringify(attachment ? { ...report, attachedEvidence: attachment } : report, null, 2));
41
+ console.log(JSON.stringify(attachment && taskId ? { ...report, taskId, attachedEvidence: attachment } : report, null, 2));
42
42
  }
43
43
  else {
44
44
  console.log(`${report.ok ? 'passed' : 'failed'} | release artifact | ${report.output.retention}`);
package/dist/cli/task.js CHANGED
@@ -12,12 +12,13 @@ const task_finish_1 = require("../task/task-finish");
12
12
  const task_next_1 = require("../task/task-next");
13
13
  const task_upgrade_scaffold_1 = require("../task/task-upgrade-scaffold");
14
14
  const task_workbench_1 = require("../services/task-workbench");
15
+ const timing_1 = require("../core/timing");
15
16
  const actor_1 = require("./actor");
16
17
  const args_1 = require("./args");
17
18
  const legacy_boundary_1 = require("./legacy-boundary");
18
19
  const task_json_1 = require("./task-json");
19
20
  function handleTaskCommand(input) {
20
- const startedAtMs = Date.now();
21
+ const timer = (0, timing_1.startMonotonicTimer)();
21
22
  const sub = input.args[1];
22
23
  if (sub === 'create') {
23
24
  if (blockLegacyMutation(input, 'task.create'))
@@ -47,9 +48,9 @@ function handleTaskCommand(input) {
47
48
  return true;
48
49
  }
49
50
  if (sub === 'show') {
50
- const id = input.args[2];
51
- if (!id)
52
- throw new Error('task show requires <task-id>');
51
+ const id = (0, args_1.getStringOption)(input.args, '--task') ?? input.args[2];
52
+ if (!id || id.startsWith('--'))
53
+ throw new Error('task show requires --task <task-id>');
53
54
  const report = (0, task_json_1.createTaskShowReport)(input.projectRoot, id);
54
55
  if (input.jsonOutput) {
55
56
  console.log(JSON.stringify(report, null, 2));
@@ -140,11 +141,15 @@ function handleTaskCommand(input) {
140
141
  return true;
141
142
  }
142
143
  if (sub === 'status') {
144
+ const summaryJsonOutput = (0, args_1.getFlag)(input.args, '--summary-json');
143
145
  const id = (0, args_1.getStringOption)(input.args, '--task') ?? input.args[2];
144
146
  if (!id || id.startsWith('--')) {
145
147
  const report = (0, task_workbench_1.createTaskStatusSelectionReport)(input.projectRoot);
146
- attachCliDiagnostics(report, startedAtMs, 'task.status');
147
- if (input.jsonOutput) {
148
+ attachCliDiagnostics(report, timer, 'task.status');
149
+ if (summaryJsonOutput) {
150
+ console.log(JSON.stringify(createTaskStatusSummaryReport(report), null, 2));
151
+ }
152
+ else if (input.jsonOutput) {
148
153
  console.log(JSON.stringify(report, null, 2));
149
154
  }
150
155
  else {
@@ -158,8 +163,11 @@ function handleTaskCommand(input) {
158
163
  if (detail && detail !== 'fast' && detail !== 'full')
159
164
  throw new Error('task status --detail must be fast or full');
160
165
  const report = (0, task_workbench_1.createTaskWorkbenchReport)(input.projectRoot, id, new Date(), { detail: detail === 'full' ? 'full' : 'fast' });
161
- attachCliDiagnostics(report, startedAtMs, 'task.status');
162
- if (input.jsonOutput) {
166
+ attachCliDiagnostics(report, timer, 'task.status');
167
+ if (summaryJsonOutput) {
168
+ console.log(JSON.stringify(createTaskStatusSummaryReport(report), null, 2));
169
+ }
170
+ else if (input.jsonOutput) {
163
171
  console.log(JSON.stringify(report, null, 2));
164
172
  }
165
173
  else {
@@ -199,7 +207,7 @@ function handleTaskCommand(input) {
199
207
  actor: (0, actor_1.getActorContextOption)(input.args),
200
208
  onProgress: executeRequested ? createTaskFinalizeProgressWriter(id) : undefined
201
209
  });
202
- attachCliDiagnostics(report, startedAtMs, 'task.finalize');
210
+ attachCliDiagnostics(report, timer, 'task.finalize');
203
211
  if (input.jsonOutput) {
204
212
  console.log(JSON.stringify(report, null, 2));
205
213
  }
@@ -281,14 +289,60 @@ function handleTaskCommand(input) {
281
289
  }
282
290
  return false;
283
291
  }
292
+ function createTaskStatusSummaryReport(report) {
293
+ if (report.schemaVersion === 'hadara.task.status.v1') {
294
+ return {
295
+ schemaVersion: 'hadara.task.status.summary.v1',
296
+ command: 'task.status',
297
+ ok: report.ok,
298
+ mode: 'select-work',
299
+ phase: report.loop.phase,
300
+ recommendations: report.summary.recommendations,
301
+ ...(report.loop.primaryNextAction ? { primaryNextAction: report.loop.primaryNextAction } : {}),
302
+ ...(report.diagnostics ? { diagnostics: report.diagnostics } : {}),
303
+ issues: report.issues
304
+ };
305
+ }
306
+ return {
307
+ schemaVersion: 'hadara.task.status.summary.v1',
308
+ command: 'task.status',
309
+ ok: report.ok,
310
+ mode: 'selected-task',
311
+ taskId: report.taskId,
312
+ task: {
313
+ title: report.task.title,
314
+ capsule: report.task.capsule,
315
+ taskStatus: report.task.taskStatus,
316
+ taskBoardStatus: report.task.taskBoardStatus
317
+ },
318
+ phase: report.loop.phase,
319
+ readiness: {
320
+ status: report.state.readiness.status,
321
+ ready: report.state.ready,
322
+ closeProofValid: report.state.readiness.closeProofValid,
323
+ summary: report.state.readiness.summary
324
+ },
325
+ counts: {
326
+ blockers: report.summary.blockers,
327
+ warnings: report.summary.warnings,
328
+ evidenceRecords: report.summary.evidenceRecords,
329
+ validationChecks: report.sources.evidenceList.validationAttempts?.checks ?? 0,
330
+ unresolvedValidation: report.sources.evidenceList.validationAttempts?.unresolvedFailedOrBlocked ?? 0,
331
+ nextActions: report.summary.nextActions
332
+ },
333
+ ...(report.loop.primaryNextAction ? { primaryNextAction: report.loop.primaryNextAction } : {}),
334
+ ...(report.diagnostics ? { diagnostics: report.diagnostics } : {}),
335
+ issues: report.issues
336
+ };
337
+ }
284
338
  function createTaskFinalizeProgressWriter(taskId) {
285
339
  return (event) => {
286
340
  const ok = event.ok === undefined ? '' : ` ok=${event.ok}`;
287
341
  process.stderr.write(`[HADARA] task finalize ${taskId}: ${event.step} ${event.phase}${ok} - ${event.summary}\n`);
288
342
  };
289
343
  }
290
- function attachCliDiagnostics(report, startedAtMs, commandPath) {
291
- const durationMs = Math.max(0, Date.now() - startedAtMs);
344
+ function attachCliDiagnostics(report, timer, commandPath) {
345
+ const durationMs = timer.elapsedMs();
292
346
  const slowThresholdMs = 10000;
293
347
  const slow = durationMs >= slowThresholdMs;
294
348
  report.diagnostics = {
@@ -28,14 +28,24 @@ function handleValidationCommand(input) {
28
28
  }
29
29
  else {
30
30
  console.log(`[HADARA] validation run ${taskId}: ${report.result}`);
31
+ console.log(`[HADARA] child command`);
32
+ console.log(`command=${report.argv.join(' ')}`);
33
+ console.log(`exitCode=${report.execution.exitCode ?? 'null'} signal=${report.execution.signal ?? 'null'} durationMs=${report.execution.durationMs}`);
34
+ console.log(`stdoutHash=${report.execution.stdoutHash}`);
35
+ console.log(`stderrHash=${report.execution.stderrHash}`);
36
+ console.log(`childOutput=not printed; stdout/stderr hashes are recorded in HADARA evidence`);
37
+ console.log(`[HADARA] evidence`);
31
38
  if (report.evidence)
32
- console.log(`evidence=${report.evidence.id}`);
33
- if (!report.taskValidationRow.updated)
34
- console.log(`taskValidationRow=${report.taskValidationRow.mode}`);
39
+ console.log(`id=${report.evidence.id}`);
40
+ console.log(`result=${report.result}`);
41
+ console.log(`taskValidationRow=${report.taskValidationRow.mode}${report.taskValidationRow.updated ? ' updated' : ' not-updated'}`);
42
+ console.log(`acceptanceRows=not-updated`);
35
43
  for (const issue of report.issues)
36
44
  console.log(`[${issue.severity}] ${issue.code}: ${issue.message}`);
45
+ if (report.nextActions.length > 0)
46
+ console.log(`[HADARA] next actions`);
37
47
  for (const action of report.nextActions)
38
- console.log(`next=${action.command ?? action.message}`);
48
+ console.log(`${action.id}=${action.command ?? action.message}`);
39
49
  }
40
50
  if (!report.ok)
41
51
  process.exitCode = 6;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startMonotonicTimer = startMonotonicTimer;
4
+ const node_perf_hooks_1 = require("node:perf_hooks");
5
+ function startMonotonicTimer(now = () => node_perf_hooks_1.performance.now()) {
6
+ const started = now();
7
+ return {
8
+ elapsedMs() {
9
+ return Math.max(0, Math.round(now() - started));
10
+ }
11
+ };
12
+ }
@@ -9,6 +9,7 @@ const node_child_process_1 = require("node:child_process");
9
9
  const node_crypto_1 = __importDefault(require("node:crypto"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
+ const timing_1 = require("../core/timing");
12
13
  const lifecycle_next_actions_1 = require("../task/lifecycle-next-actions");
13
14
  function createDevDockerCheckReport(projectRoot, options = {}, runner = defaultRunner) {
14
15
  const focusedTests = normalizeFocusedTests(options.focusedTests ?? []);
@@ -47,9 +48,9 @@ function createDevDockerCheckReport(projectRoot, options = {}, runner = defaultR
47
48
  steps.push({ id: step.id, status: 'skipped', summary: step.runWhen ? 'Skipped because an earlier step failed.' : step.summary });
48
49
  continue;
49
50
  }
50
- const started = Date.now();
51
+ const timer = (0, timing_1.startMonotonicTimer)();
51
52
  const result = runner.run('docker', dockerExecArgs(container, step.script), env);
52
- const elapsedMs = Date.now() - started;
53
+ const elapsedMs = timer.elapsedMs();
53
54
  if (result.ok) {
54
55
  steps.push({ id: step.id, status: 'passed', elapsedMs, summary: step.summary });
55
56
  if (step.mark)
@@ -20,6 +20,7 @@ const node_path_1 = __importDefault(require("node:path"));
20
20
  const node_crypto_1 = __importDefault(require("node:crypto"));
21
21
  const fs_1 = require("../core/fs");
22
22
  const redaction_1 = require("../core/redaction");
23
+ const timing_1 = require("../core/timing");
23
24
  const workspace_1 = require("../core/workspace");
24
25
  const private_manifest_1 = require("./private-manifest");
25
26
  function persistedEvidenceKind(record) {
@@ -181,7 +182,7 @@ function withEvidenceAppendLock(projectRoot, taskId, fn) {
181
182
  (0, fs_1.ensureDir)(lockRoot);
182
183
  const lockDir = node_path_1.default.join(lockRoot, `${safeFilePart(taskId)}.lock`);
183
184
  const lockPortablePath = toPortablePath(node_path_1.default.relative(projectRoot, lockDir));
184
- const started = Date.now();
185
+ const timer = (0, timing_1.startMonotonicTimer)();
185
186
  const timeoutMs = 5000;
186
187
  while (true) {
187
188
  try {
@@ -191,7 +192,7 @@ function withEvidenceAppendLock(projectRoot, taskId, fn) {
191
192
  catch (error) {
192
193
  if (error.code !== 'EEXIST')
193
194
  throw error;
194
- if (Date.now() - started >= timeoutMs) {
195
+ if (timer.elapsedMs() >= timeoutMs) {
195
196
  throw new EvidenceAppendLockError(`Timed out waiting for the evidence append lock for ${taskId}. Lock directory: ${lockPortablePath}. ` +
196
197
  `If no HADARA process is writing evidence, the lock is stale (inspect ${lockPortablePath}/lock.json for the owning pid); remove the lock directory and retry.`);
197
198
  }
@@ -30,6 +30,7 @@ function createHandoffSuggestionReport(projectRoot, taskId, options = {}) {
30
30
  schemaVersion: 'hadara.handoff.suggestion.v1',
31
31
  command: 'handoff.suggest',
32
32
  ok: !issues.some((issue) => issue.severity === 'error'),
33
+ taskId,
33
34
  readOnly: true,
34
35
  generatedAt: new Date().toISOString(),
35
36
  actor,
@@ -49,6 +49,7 @@ Attach test logs, command outputs, and diff summaries under the active Task Caps
49
49
  schemaVersion: 'hadara.handoff.update.v1',
50
50
  command: 'handoff.update',
51
51
  ok: true,
52
+ taskId: input.taskId ?? null,
52
53
  target: {
53
54
  path: 'docs/AGENT_HANDOFF.md',
54
55
  writeBoundary: 'shared-doc'