hadara 0.2.0-rc.2 → 0.2.0-rc.3

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.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <img alt="Release candidate" src="https://img.shields.io/badge/release-0.2.0--rc.2-blue">
8
+ <img alt="Release candidate" src="https://img.shields.io/badge/release-0.2.0--rc.3-blue">
9
9
  <img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D22-brightgreen">
10
10
  <img alt="License" src="https://img.shields.io/badge/license-MIT-lightgrey">
11
11
  </p>
@@ -23,10 +23,10 @@ This repository is both the HADARA source checkout and the HADARA protocol works
23
23
  The current source checkout targets:
24
24
 
25
25
  ```text
26
- hadara@0.2.0-rc.2
26
+ hadara@0.2.0-rc.3
27
27
  ```
28
28
 
29
- T-0282 refreshes the npm release-candidate source state for `0.2.0-rc.2` after the init scaffold protocol guidance follow-up. T-0275 published the previous `0.2.0-rc.1` RC after the installed-package recycle fixes.
29
+ T-0287 prepares the source checkout for `0.2.0-rc.3` after the rc2 dogfooding findings and the T-0284 through T-0286 proof reliability hardening. `hadara@0.2.0-rc.3` is a publish candidate until an operator explicitly runs an approval-gated publish capsule. The latest npm-published release candidate remains `hadara@0.2.0-rc.2`.
30
30
 
31
31
  Current publish boundaries:
32
32
 
@@ -35,8 +35,9 @@ Current publish boundaries:
35
35
  | npm package | Primary release target. |
36
36
  | `hadara@0.1.0-rc.0` | Published first RC. |
37
37
  | `hadara@0.2.0-rc.0` | Superseded internal publish candidate after recycle findings. |
38
- | `hadara@0.2.0-rc.1` | Current published npm RC until rc.2 is operator-published. |
39
- | `hadara@0.2.0-rc.2` | Current source version and next npm RC target. |
38
+ | `hadara@0.2.0-rc.1` | Previous published npm RC. |
39
+ | `hadara@0.2.0-rc.2` | Current published npm RC. |
40
+ | `hadara@0.2.0-rc.3` | Current source publish candidate; not published by this capsule. |
40
41
  | GitHub Release | Secondary target, still approval-gated. |
41
42
  | Docker image | Deferred. |
42
43
  | PyPI/Python package | `hadara==0.2.0rc1` published preview bridge. |
@@ -48,7 +49,7 @@ No release command should publish, create a GitHub Release, build Docker images,
48
49
 
49
50
  Requires Node.js 22.
50
51
 
51
- Install the current RC:
52
+ Install the current published RC:
52
53
 
53
54
  ```bash
54
55
  npm install -g hadara@0.2.0-rc.2
@@ -64,7 +65,7 @@ npx hadara@0.2.0-rc.2 doctor --json
64
65
  npx hadara@0.2.0-rc.2 tools list --json
65
66
  ```
66
67
 
67
- Previous published RCs: `hadara@0.2.0-rc.1` and `hadara@0.1.0-rc.0` remain available on npm for comparison or rollback, but new installs should use the current RC after the operator publishes it.
68
+ After an operator publishes rc3, use `hadara@0.2.0-rc.3` in the install and npx commands above. Previous published RCs remain available on npm for comparison or rollback.
68
69
 
69
70
  ## What HADARA Gives You
70
71
 
@@ -114,6 +115,9 @@ Evidence and handoff:
114
115
  hadara evidence collect --task T-0001 --json
115
116
  hadara evidence add-command --task T-0001 --summary "Focused validation passed." --result passed --json
116
117
  hadara evidence list --task T-0001 --json
118
+ hadara proof status --task T-0001 --json
119
+ hadara proof explain --task T-0001 --json
120
+ hadara ci gate --mode advisory --task T-0001 --json
117
121
  hadara handoff suggest --task T-0001 --json
118
122
  ```
119
123
 
@@ -151,7 +155,7 @@ hadara task status --task T-XXXX --json
151
155
 
152
156
  # Do the scoped work.
153
157
 
154
- hadara evidence add-command --task T-XXXX --summary "..." --result passed --json
158
+ hadara evidence add-command --task T-XXXX --summary "..." --result passed --idempotency-key "command:T-XXXX:check" --json
155
159
 
156
160
  hadara task finish --task T-XXXX --json
157
161
  hadara task finish --task T-XXXX --execute --json
package/dist/cli/ci.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleCiCommand = handleCiCommand;
4
+ const args_1 = require("./args");
5
+ const ci_gate_1 = require("../services/ci-gate");
6
+ function handleCiCommand(input) {
7
+ if (input.args[0] !== 'ci' || input.args[1] !== 'gate')
8
+ return false;
9
+ const mode = parseCiGateMode((0, args_1.getStringOption)(input.args, '--mode', 'advisory') ?? 'advisory');
10
+ const report = (0, ci_gate_1.createCiGateReport)(input.projectRoot, mode, {
11
+ taskId: (0, args_1.getStringOption)(input.args, '--task'),
12
+ allowEmpty: (0, args_1.getFlag)(input.args, '--allow-empty')
13
+ });
14
+ if (input.jsonOutput) {
15
+ console.log(JSON.stringify(report, null, 2));
16
+ }
17
+ else {
18
+ console.log(`[HADARA] ci gate ${mode}: ${report.ok ? 'ok' : 'blocked'} | blockers ${report.blockers.length} | warnings ${report.warnings.length}`);
19
+ }
20
+ if (!report.ok)
21
+ process.exitCode = 6;
22
+ return true;
23
+ }
24
+ function parseCiGateMode(value) {
25
+ if (value === 'advisory' || value === 'strict')
26
+ return value;
27
+ throw new Error(`unsupported ci gate mode: ${value}`);
28
+ }
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createEvidenceCollectReport = createEvidenceCollectReport;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
7
  const node_path_1 = __importDefault(require("node:path"));
9
8
  const evidence_1 = require("../evidence/evidence");
10
9
  const task_capsule_1 = require("../task/task-capsule");
@@ -25,19 +24,20 @@ function createEvidenceCollectReport(projectRoot, input) {
25
24
  ]
26
25
  };
27
26
  }
28
- let markdownPath;
27
+ let appendResult;
29
28
  try {
30
- markdownPath = (0, evidence_1.appendEvidence)(projectRoot, {
29
+ appendResult = (0, evidence_1.appendEvidenceWithResult)(projectRoot, {
31
30
  taskId: input.taskId,
32
31
  kind: input.kind,
33
32
  path: input.path,
34
33
  summary: input.summary,
35
34
  result: input.result,
36
- visibility: input.visibility
35
+ visibility: input.visibility,
36
+ idempotencyKey: input.idempotencyKey
37
37
  });
38
38
  }
39
39
  catch (error) {
40
- if (error instanceof workspace_1.WorkspaceFileError || error instanceof evidence_1.EvidenceArtifactPolicyError) {
40
+ if (error instanceof workspace_1.WorkspaceFileError || error instanceof evidence_1.EvidenceArtifactPolicyError || error instanceof evidence_1.EvidenceAppendLockError) {
41
41
  return {
42
42
  schemaVersion: 'hadara.evidence.collect.v1',
43
43
  command: 'evidence.collect',
@@ -53,23 +53,20 @@ function createEvidenceCollectReport(projectRoot, input) {
53
53
  }
54
54
  throw error;
55
55
  }
56
- const indexRecord = readLastEvidenceIndexRecord(task.dir);
57
56
  return {
58
57
  schemaVersion: 'hadara.evidence.collect.v1',
59
58
  command: 'evidence.collect',
60
59
  ok: true,
61
60
  evidence: {
62
- ...indexRecord,
63
- markdownPath: toPortablePath(node_path_1.default.relative(projectRoot, markdownPath))
61
+ ...appendResult.evidence,
62
+ markdownPath: toPortablePath(node_path_1.default.relative(projectRoot, appendResult.markdownPath)),
63
+ markdownAppended: appendResult.markdownAppended,
64
+ jsonlAppended: appendResult.jsonlAppended,
65
+ existing: appendResult.existing
64
66
  },
65
67
  issues: []
66
68
  };
67
69
  }
68
- function readLastEvidenceIndexRecord(taskDir) {
69
- const indexPath = node_path_1.default.join(taskDir, 'evidence.jsonl');
70
- const lines = node_fs_1.default.readFileSync(indexPath, 'utf8').trim().split(/\r?\n/);
71
- return JSON.parse(lines.at(-1) ?? '{}');
72
- }
73
70
  function toPortablePath(value) {
74
71
  return value.split(node_path_1.default.sep).join('/');
75
72
  }
@@ -78,21 +78,28 @@ function handleEvidenceCommand(input) {
78
78
  const summary = (0, args_1.getStringOption)(input.args, '--summary') ?? 'Command completed.';
79
79
  const result = parseEvidenceResult((0, args_1.getStringOption)(input.args, '--result', 'unknown') ?? 'unknown');
80
80
  const visibility = parseEvidenceVisibility((0, args_1.getStringOption)(input.args, '--visibility', 'public') ?? 'public', (0, args_1.getFlag)(input.args, '--private'));
81
+ const idempotencyKey = (0, args_1.getStringOption)(input.args, '--idempotency-key');
81
82
  if (input.jsonOutput) {
82
83
  const report = (0, evidence_json_1.createEvidenceCollectReport)(input.projectRoot, {
83
84
  taskId,
84
85
  kind: 'command-log',
85
86
  summary,
86
87
  result,
87
- visibility
88
+ visibility,
89
+ idempotencyKey
88
90
  });
89
91
  console.log(JSON.stringify({ ...report, command: 'evidence.add-command' }, null, 2));
90
92
  if (!report.ok)
91
93
  process.exitCode = 6;
92
94
  }
93
95
  else {
94
- const filePath = (0, evidence_1.appendEvidence)(input.projectRoot, { taskId, kind: 'command-log', summary, result, visibility });
95
- console.log(`[HADARA] Command evidence updated: ${filePath}`);
96
+ const appendResult = (0, evidence_1.appendEvidenceWithResult)(input.projectRoot, { taskId, kind: 'command-log', summary, result, visibility, idempotencyKey });
97
+ if (appendResult.existing) {
98
+ console.log(`[HADARA] Command evidence already exists: ${persistedEvidenceId(appendResult.evidence)}`);
99
+ }
100
+ else {
101
+ console.log(`[HADARA] Command evidence recorded: ${appendResult.markdownPath}`);
102
+ }
96
103
  }
97
104
  return true;
98
105
  }
@@ -104,6 +111,7 @@ function handleEvidenceCommand(input) {
104
111
  const result = parseEvidenceResult((0, args_1.getStringOption)(input.args, '--result', 'unknown') ?? 'unknown');
105
112
  const evidenceFile = (0, args_1.getStringOption)(input.args, '--path');
106
113
  const visibility = parseEvidenceVisibility((0, args_1.getStringOption)(input.args, '--visibility', 'public') ?? 'public', (0, args_1.getFlag)(input.args, '--private'));
114
+ const idempotencyKey = (0, args_1.getStringOption)(input.args, '--idempotency-key');
107
115
  if (input.jsonOutput) {
108
116
  const report = (0, evidence_json_1.createEvidenceCollectReport)(input.projectRoot, {
109
117
  taskId,
@@ -111,18 +119,27 @@ function handleEvidenceCommand(input) {
111
119
  path: evidenceFile,
112
120
  summary,
113
121
  result,
114
- visibility
122
+ visibility,
123
+ idempotencyKey
115
124
  });
116
125
  console.log(JSON.stringify(report, null, 2));
117
126
  if (!report.ok)
118
127
  process.exitCode = 6;
119
128
  }
120
129
  else {
121
- const filePath = (0, evidence_1.appendEvidence)(input.projectRoot, { taskId, kind, path: evidenceFile, summary, result, visibility });
122
- console.log(`[HADARA] Evidence updated: ${filePath}`);
130
+ const appendResult = (0, evidence_1.appendEvidenceWithResult)(input.projectRoot, { taskId, kind, path: evidenceFile, summary, result, visibility, idempotencyKey });
131
+ if (appendResult.existing) {
132
+ console.log(`[HADARA] Evidence already exists: ${persistedEvidenceId(appendResult.evidence)}`);
133
+ }
134
+ else {
135
+ console.log(`[HADARA] Evidence recorded: ${appendResult.markdownPath}`);
136
+ }
123
137
  }
124
138
  return true;
125
139
  }
140
+ function persistedEvidenceId(record) {
141
+ return record.schemaVersion === 'hadara.evidence.v2' && record.id ? record.id : 'evidence.jsonl';
142
+ }
126
143
  function parseEvidenceKind(value) {
127
144
  if (['test-log', 'command-log', 'diff-summary', 'screenshot', 'note'].includes(value)) {
128
145
  return value;
package/dist/cli/init.js CHANGED
@@ -963,7 +963,7 @@ hadara task status --task T-XXXX --json
963
963
 
964
964
  # Do the scoped work.
965
965
 
966
- hadara evidence add-command --task T-XXXX --summary "..." --result passed --json
966
+ hadara evidence add-command --task T-XXXX --summary "..." --result passed --idempotency-key "command:T-XXXX:check" --json
967
967
 
968
968
  hadara task finish --task T-XXXX --json
969
969
  hadara task finish --task T-XXXX --execute --json
@@ -985,7 +985,7 @@ hadara task audit-close --task T-XXXX --json
985
985
  |---|---|---|
986
986
  | \`task next\` | Read-only | Recommends work; does not create tasks. |
987
987
  | \`task status\` | Read-only | \`ok\` means report generation succeeded; readiness is in \`state.ready\`, \`summary.blockers\`, and \`issues\`. |
988
- | \`evidence add-command\` | Write | Appends command-log evidence; does not execute shell commands. |
988
+ | \`evidence add-command\` | Write | Appends command-log evidence; does not execute shell commands; optional \`--idempotency-key\` prevents duplicate same-key records. |
989
989
  | \`task ready\` | Read-only | Checks readiness; does not mutate evidence or status docs. |
990
990
  | \`task finish\` | Dry-run by default; writes only with \`--execute\` | Bounded to \`TASK.md\` and \`docs/TASK_BOARD.md\`. |
991
991
  | \`task close\` | Dry-run by default; writes only with \`--execute\` | Bounded to close evidence append. |
@@ -1010,7 +1010,7 @@ Before running \`task ready\` and \`task close\`, finish all close-source edits:
1010
1010
  1. Do not hand-edit Task Capsule \`evidence.jsonl\`.
1011
1011
  2. Append evidence through HADARA commands so schema, visibility, and artifact-safety checks run consistently.
1012
1012
  3. Record failed or blocked checks honestly. Do not replace them later with optimistic summaries; add newer evidence that explains the fix or residual risk.
1013
- 4. Use \`hadara evidence add-command --task <task-id> --summary <text> --result passed|failed|blocked|unknown --json\` for command results when no artifact file is attached.
1013
+ 4. Use \`hadara evidence add-command --task <task-id> --summary <text> --result passed|failed|blocked|unknown --json\` for command results when no artifact file is attached. Add \`--idempotency-key <key>\` when rerunning the same logical check should report one durable evidence identity instead of appending duplicates.
1014
1014
  5. Use \`hadara evidence lint --task <task-id> --json\` when evidence drift is suspected or before close if evidence files were touched manually by mistake.
1015
1015
 
1016
1016
  ## Session End
@@ -1177,7 +1177,7 @@ hadara task status --task T-XXXX --json
1177
1177
 
1178
1178
  # Do the scoped work.
1179
1179
 
1180
- hadara evidence add-command --task T-XXXX --summary "..." --result passed --json
1180
+ hadara evidence add-command --task T-XXXX --summary "..." --result passed --idempotency-key "command:T-XXXX:check" --json
1181
1181
 
1182
1182
  hadara task finish --task T-XXXX --json
1183
1183
  hadara task finish --task T-XXXX --execute --json
@@ -1210,7 +1210,7 @@ Before close, finish all close-source edits: Task Capsule docs, acceptance/tests
1210
1210
  | \`task next\` | Read-only | Recommends work; does not create tasks. |
1211
1211
  | \`task status\` | Read-only | \`ok\` means report generation succeeded; readiness is in \`state.ready\`, \`summary.blockers\`, and \`issues\`. |
1212
1212
  | \`task create\` | Write | Creates a Draft Task Capsule and Task Board row. It does not imply the task is ready or done. |
1213
- | \`evidence add-command\` | Write | Appends operator-supplied command-log evidence. It does not execute shell commands or capture stdout/stderr. |
1213
+ | \`evidence add-command\` | Write | Appends operator-supplied command-log evidence. It does not execute shell commands or capture stdout/stderr; optional \`--idempotency-key\` prevents duplicate same-key records. |
1214
1214
  | \`task finish\` | Dry-run by default; writes only with \`--execute\` | Updates only \`TASK.md\` status bookkeeping and the matching \`docs/TASK_BOARD.md\` row. |
1215
1215
  | \`task ready\` | Read-only | Checks whether the task can satisfy the requested readiness level after finish. |
1216
1216
  | \`task complete\` | Read-only | Summarizes the current completion stage and next command; it does not execute lifecycle writes. |
@@ -1224,7 +1224,7 @@ Before close, finish all close-source edits: Task Capsule docs, acceptance/tests
1224
1224
  - \`task ready\` checks readiness; it does not write evidence or status.
1225
1225
  - \`harness validate\` is a direct diagnostic for Task Capsule structure and done-level gates; it is not a replacement for close evidence.
1226
1226
  - \`task complete\` is a read-only workflow compressor. It may report the next lifecycle command, but it must not execute finish, ready, close, or audit commands.
1227
- - \`evidence add-command\` records an operator-supplied command result; it does not run the command.
1227
+ - \`evidence add-command\` records an operator-supplied command result; it does not run the command. \`--idempotency-key\` is optional; when supplied, same-key repeats return the existing record without appending duplicate Markdown or JSONL rows.
1228
1228
  - \`task finish\` may update only the Task Capsule \`TASK.md\` status and matching \`docs/TASK_BOARD.md\` status/path row.
1229
1229
  - \`task close\` may append only close evidence. It must not update status docs, Task Board rows, handoff, Project State, roadmap docs, or arbitrary evidence.
1230
1230
  - After \`task close --execute --json\`, close-source document edits intentionally invalidate the previous close proof. Make those edits before close, or rerun ready/close/audit if the edit is unavoidable.
package/dist/cli/main.js CHANGED
@@ -62,10 +62,13 @@ Usage:
62
62
  hadara task audit-close --task <task-id> [--json]
63
63
  hadara task ready --task <task-id> [--level done] [--json]
64
64
  hadara evidence collect --task <task-id> [--kind note|test-log|command-log|diff-summary|screenshot] [--path <path>] [--summary <text>] [--result passed|failed|blocked|unknown] [--private|--visibility public|private]
65
- hadara evidence add-command --task <task-id> --summary <text> [--result passed|failed|blocked|unknown] [--private|--visibility public|private] [--json]
65
+ hadara evidence add-command --task <task-id> --summary <text> [--result passed|failed|blocked|unknown] [--idempotency-key <key>] [--private|--visibility public|private] [--json]
66
66
  hadara evidence list --task <task-id> [--limit <n>] [--include-private] [--json]
67
67
  hadara evidence lint --task <task-id> [--json]
68
68
  hadara evidence migrate --task <task-id> --to v2 [--execute --before-hash <hash>] [--json]
69
+ hadara proof status --task <task-id> [--json]
70
+ hadara proof explain --task <task-id> [--json]
71
+ hadara ci gate [--mode advisory|strict] [--task <task-id>] [--allow-empty] [--json]
69
72
  hadara debt list [--json]
70
73
  hadara debt show <id> [--json]
71
74
  hadara protocol doctor [--json]
@@ -150,6 +153,18 @@ async function main(args = process.argv.slice(2)) {
150
153
  return;
151
154
  break;
152
155
  }
156
+ case 'proof': {
157
+ const { handleProofCommand } = await Promise.resolve().then(() => __importStar(require('./proof')));
158
+ if (handleProofCommand({ args, projectRoot: paths.projectRoot, jsonOutput }))
159
+ return;
160
+ break;
161
+ }
162
+ case 'ci': {
163
+ const { handleCiCommand } = await Promise.resolve().then(() => __importStar(require('./ci')));
164
+ if (handleCiCommand({ args, projectRoot: paths.projectRoot, jsonOutput }))
165
+ return;
166
+ break;
167
+ }
153
168
  case 'tools': {
154
169
  const { handleToolsCommand } = await Promise.resolve().then(() => __importStar(require('./tools')));
155
170
  if (handleToolsCommand({ args, jsonOutput }))
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleProofCommand = handleProofCommand;
4
+ const args_1 = require("./args");
5
+ const proof_status_1 = require("../services/proof-status");
6
+ function handleProofCommand(input) {
7
+ if (input.args[0] !== 'proof')
8
+ return false;
9
+ const sub = input.args[1];
10
+ if (sub !== 'status' && sub !== 'explain')
11
+ return false;
12
+ const taskId = (0, args_1.getRequiredStringOption)(input.args, '--task');
13
+ const report = (0, proof_status_1.createProofStatusReport)(input.projectRoot, taskId, sub);
14
+ if (input.jsonOutput) {
15
+ console.log(JSON.stringify(report, null, 2));
16
+ }
17
+ else {
18
+ console.log(`[HADARA] proof ${sub} ${taskId}: ${report.verdict}`);
19
+ console.log(`freshness: ${report.freshness.status}`);
20
+ for (const issue of [...report.blockers, ...report.warnings]) {
21
+ console.log(`[${issue.severity}] ${issue.code}: ${issue.message}`);
22
+ }
23
+ }
24
+ if (!report.ok)
25
+ process.exitCode = 6;
26
+ return true;
27
+ }
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EvidenceArtifactPolicyError = void 0;
6
+ exports.EvidenceAppendLockError = exports.EvidenceArtifactPolicyError = void 0;
7
7
  exports.persistedEvidenceKind = persistedEvidenceKind;
8
8
  exports.persistedEvidenceResult = persistedEvidenceResult;
9
9
  exports.persistedEvidencePath = persistedEvidencePath;
@@ -11,6 +11,7 @@ exports.appendEvidence = appendEvidence;
11
11
  exports.appendEvidenceWithResult = appendEvidenceWithResult;
12
12
  exports.appendEvidenceTextArtifact = appendEvidenceTextArtifact;
13
13
  exports.createPublicEvidenceArtifactPolicyReport = createPublicEvidenceArtifactPolicyReport;
14
+ exports.persistedEvidenceIdempotencyKey = persistedEvidenceIdempotencyKey;
14
15
  exports.createSessionEvidenceDirs = createSessionEvidenceDirs;
15
16
  const node_fs_1 = __importDefault(require("node:fs"));
16
17
  const node_path_1 = __importDefault(require("node:path"));
@@ -39,6 +40,14 @@ class EvidenceArtifactPolicyError extends Error {
39
40
  }
40
41
  }
41
42
  exports.EvidenceArtifactPolicyError = EvidenceArtifactPolicyError;
43
+ class EvidenceAppendLockError extends Error {
44
+ code = 'EVIDENCE_APPEND_LOCK_TIMEOUT';
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = 'EvidenceAppendLockError';
48
+ }
49
+ }
50
+ exports.EvidenceAppendLockError = EvidenceAppendLockError;
42
51
  function appendEvidence(projectRoot, record) {
43
52
  return appendEvidenceWithResult(projectRoot, record).markdownPath;
44
53
  }
@@ -49,8 +58,14 @@ function appendEvidenceWithResult(projectRoot, record) {
49
58
  }
50
59
  const time = new Date().toISOString();
51
60
  const visibility = record.visibility ?? 'public';
52
- const attachedPath = copyPublicEvidenceArtifact({ projectRoot, taskDir, kind: record.kind, sourcePath: record.path, time, visibility });
53
- return appendEvidenceRecord({ projectRoot, taskDir, time, record, visibility, attachedPath });
61
+ return appendEvidenceRecord({
62
+ projectRoot,
63
+ taskDir,
64
+ time,
65
+ record,
66
+ visibility,
67
+ createAttachedPath: () => copyPublicEvidenceArtifact({ projectRoot, taskDir, kind: record.kind, sourcePath: record.path, time, visibility })
68
+ });
54
69
  }
55
70
  function appendEvidenceTextArtifact(projectRoot, record, artifact, options = {}) {
56
71
  const taskDir = findTaskDir(projectRoot, record.taskId);
@@ -59,18 +74,24 @@ function appendEvidenceTextArtifact(projectRoot, record, artifact, options = {})
59
74
  }
60
75
  const time = new Date().toISOString();
61
76
  const visibility = record.visibility ?? 'public';
62
- const attachedPath = visibility === 'public'
63
- ? writePublicEvidenceTextArtifact({
64
- taskDir,
65
- kind: record.kind,
66
- time,
67
- fileName: artifact.fileName,
68
- content: artifact.content,
69
- artifactDirName: artifact.artifactDirName,
70
- policyOptions: options
71
- })
72
- : undefined;
73
- return appendEvidenceRecord({ projectRoot, taskDir, time, record, visibility, attachedPath });
77
+ return appendEvidenceRecord({
78
+ projectRoot,
79
+ taskDir,
80
+ time,
81
+ record,
82
+ visibility,
83
+ createAttachedPath: () => visibility === 'public'
84
+ ? writePublicEvidenceTextArtifact({
85
+ taskDir,
86
+ kind: record.kind,
87
+ time,
88
+ fileName: artifact.fileName,
89
+ content: artifact.content,
90
+ artifactDirName: artifact.artifactDirName,
91
+ policyOptions: options
92
+ })
93
+ : undefined
94
+ });
74
95
  }
75
96
  function createPublicEvidenceArtifactPolicyReport(content, options = {}) {
76
97
  const redaction = (0, redaction_1.createRedactionReport)(content, { patterns: options.redactionPatterns });
@@ -95,41 +116,128 @@ function createPublicEvidenceArtifactPolicyReport(content, options = {}) {
95
116
  function appendEvidenceIndex(taskDir, record) {
96
117
  node_fs_1.default.appendFileSync(node_path_1.default.join(taskDir, 'evidence.jsonl'), `${JSON.stringify(record)}\n`, 'utf8');
97
118
  }
119
+ function readEvidenceIndex(taskDir) {
120
+ const indexPath = node_path_1.default.join(taskDir, 'evidence.jsonl');
121
+ if (!node_fs_1.default.existsSync(indexPath))
122
+ return [];
123
+ return node_fs_1.default
124
+ .readFileSync(indexPath, 'utf8')
125
+ .split(/\r?\n/)
126
+ .filter((line) => line.trim() !== '')
127
+ .map((line) => JSON.parse(line));
128
+ }
129
+ function persistedEvidenceIdempotencyKey(record) {
130
+ if (record.schemaVersion !== 'hadara.evidence.v2')
131
+ return undefined;
132
+ if (record.idempotencyKey)
133
+ return record.idempotencyKey;
134
+ const tag = record.tags.find((item) => item.startsWith('idempotency:'));
135
+ return tag ? tag.replace(/^idempotency:/, '') : undefined;
136
+ }
137
+ function withEvidenceAppendLock(projectRoot, taskId, fn) {
138
+ const lockRoot = node_path_1.default.join(projectRoot, '.hadara', 'local', 'locks', 'evidence');
139
+ (0, fs_1.ensureDir)(lockRoot);
140
+ const lockDir = node_path_1.default.join(lockRoot, `${safeFilePart(taskId)}.lock`);
141
+ const lockPortablePath = toPortablePath(node_path_1.default.relative(projectRoot, lockDir));
142
+ const started = Date.now();
143
+ const timeoutMs = 5000;
144
+ while (true) {
145
+ try {
146
+ node_fs_1.default.mkdirSync(lockDir);
147
+ break;
148
+ }
149
+ catch (error) {
150
+ if (error.code !== 'EEXIST')
151
+ throw error;
152
+ if (Date.now() - started >= timeoutMs) {
153
+ throw new EvidenceAppendLockError(`Timed out waiting for the evidence append lock for ${taskId}. Lock directory: ${lockPortablePath}. ` +
154
+ `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.`);
155
+ }
156
+ sleepSync(25);
157
+ }
158
+ }
159
+ writeLockMetadata(lockDir, taskId);
160
+ try {
161
+ return fn();
162
+ }
163
+ finally {
164
+ try {
165
+ node_fs_1.default.rmSync(lockDir, { recursive: true, force: true });
166
+ }
167
+ catch {
168
+ // Best-effort cleanup; later writers fail closed through the timeout.
169
+ }
170
+ }
171
+ }
172
+ function writeLockMetadata(lockDir, taskId) {
173
+ try {
174
+ node_fs_1.default.writeFileSync(node_path_1.default.join(lockDir, 'lock.json'), `${JSON.stringify({ pid: process.pid, taskId, command: 'evidence.append', createdAt: new Date().toISOString() })}\n`, 'utf8');
175
+ }
176
+ catch {
177
+ // Lock ownership is held by the directory; metadata is a best-effort diagnostic aid only.
178
+ }
179
+ }
180
+ function sleepSync(ms) {
181
+ const signal = new Int32Array(new SharedArrayBuffer(4));
182
+ Atomics.wait(signal, 0, 0, ms);
183
+ }
98
184
  function appendEvidenceRecord(input) {
99
185
  const summary = (0, redaction_1.redactSecrets)(input.record.summary.replace(/\|/g, '/'));
100
186
  const markdownPath = node_path_1.default.join(input.taskDir, 'EVIDENCE.md');
101
- const rowSummary = input.visibility === 'private' || !input.attachedPath ? summary : `${summary} (${input.attachedPath})`;
102
- const jsonlMarker = input.visibility === 'public' && input.attachedPath ? input.attachedPath : 'evidence.jsonl';
103
- const row = `| ${input.time} | ${input.record.kind} | ${rowSummary} | ${input.record.result} | ${input.visibility} | ${jsonlMarker} |\n`;
104
- if (!node_fs_1.default.existsSync(markdownPath)) {
105
- node_fs_1.default.writeFileSync(markdownPath, '# Evidence\n\n| Time | Kind | Summary | Result | Visibility | JSONL |\n|---|---|---|---|---|---|\n', 'utf8');
106
- }
107
- node_fs_1.default.appendFileSync(markdownPath, row, 'utf8');
108
- const evidence = createEvidenceV2Record({
109
- time: input.time,
110
- taskId: input.record.taskId,
111
- kind: input.record.kind,
112
- summary,
113
- result: input.record.result,
114
- visibility: input.visibility,
115
- attachedPath: input.attachedPath,
116
- tags: input.record.tags,
117
- idempotencyKey: input.record.idempotencyKey,
118
- actor: input.record.actor
119
- });
120
- appendEvidenceIndex(input.taskDir, evidence);
121
- if (input.visibility === 'private') {
122
- (0, private_manifest_1.writePrivateEvidenceManifest)({
123
- projectRoot: input.projectRoot,
187
+ return withEvidenceAppendLock(input.projectRoot, input.record.taskId, () => {
188
+ const idempotencyKey = input.record.idempotencyKey;
189
+ if (idempotencyKey) {
190
+ const existing = readEvidenceIndex(input.taskDir).find((record) => persistedEvidenceIdempotencyKey(record) === idempotencyKey);
191
+ if (existing) {
192
+ return {
193
+ markdownPath,
194
+ evidence: existing,
195
+ markdownAppended: false,
196
+ jsonlAppended: false,
197
+ existing: true
198
+ };
199
+ }
200
+ }
201
+ const attachedPath = input.createAttachedPath();
202
+ const rowSummary = input.visibility === 'private' || !attachedPath ? summary : `${summary} (${attachedPath})`;
203
+ const jsonlMarker = input.visibility === 'public' && attachedPath ? attachedPath : 'evidence.jsonl';
204
+ const row = `| ${input.time} | ${input.record.kind} | ${rowSummary} | ${input.record.result} | ${input.visibility} | ${jsonlMarker} |\n`;
205
+ if (!node_fs_1.default.existsSync(markdownPath)) {
206
+ node_fs_1.default.writeFileSync(markdownPath, '# Evidence\n\n| Time | Kind | Summary | Result | Visibility | JSONL |\n|---|---|---|---|---|---|\n', 'utf8');
207
+ }
208
+ node_fs_1.default.appendFileSync(markdownPath, row, 'utf8');
209
+ const evidence = createEvidenceV2Record({
210
+ time: input.time,
124
211
  taskId: input.record.taskId,
125
212
  kind: input.record.kind,
126
213
  summary,
127
214
  result: input.record.result,
128
- sourcePath: input.record.path,
129
- time: input.time
215
+ visibility: input.visibility,
216
+ attachedPath,
217
+ tags: input.record.tags,
218
+ idempotencyKey: input.record.idempotencyKey,
219
+ actor: input.record.actor
130
220
  });
131
- }
132
- return { markdownPath, evidence };
221
+ appendEvidenceIndex(input.taskDir, evidence);
222
+ if (input.visibility === 'private') {
223
+ (0, private_manifest_1.writePrivateEvidenceManifest)({
224
+ projectRoot: input.projectRoot,
225
+ taskId: input.record.taskId,
226
+ kind: input.record.kind,
227
+ summary,
228
+ result: input.record.result,
229
+ sourcePath: input.record.path,
230
+ time: input.time
231
+ });
232
+ }
233
+ return {
234
+ markdownPath,
235
+ evidence,
236
+ markdownAppended: true,
237
+ jsonlAppended: true,
238
+ existing: false
239
+ };
240
+ });
133
241
  }
134
242
  function createEvidenceV2Record(input) {
135
243
  const legacy = {
@@ -0,0 +1,125 @@
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.createCiGateReport = createCiGateReport;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const proof_status_1 = require("./proof-status");
10
+ const evidence_lint_1 = require("./evidence-lint");
11
+ const protocol_consistency_1 = require("./protocol-consistency");
12
+ const task_capsule_1 = require("../task/task-capsule");
13
+ function createCiGateReport(projectRoot, mode, options = {}) {
14
+ const allowEmpty = options.allowEmpty ?? false;
15
+ const tasks = selectTasks(projectRoot, options.taskId);
16
+ const checks = [];
17
+ const blockers = [];
18
+ const warnings = [];
19
+ applyScopeGuard({ taskId: options.taskId, mode, allowEmpty, taskCount: tasks.length, checks, blockers, warnings });
20
+ const protocol = options.taskId ? (0, protocol_consistency_1.createTaskProtocolConsistencyReport)(projectRoot, options.taskId) : (0, protocol_consistency_1.createAllProtocolConsistencyReport)(projectRoot);
21
+ checks.push({
22
+ id: options.taskId ? `protocol:${options.taskId}` : 'protocol:all',
23
+ source: 'protocol',
24
+ ok: protocol.ok,
25
+ ...(options.taskId ? { taskId: options.taskId } : {}),
26
+ summary: protocol.ok ? 'Protocol checks passed.' : 'Protocol checks reported issues.'
27
+ });
28
+ for (const issue of protocol.issues) {
29
+ const target = issue.severity === 'error' ? blockers : warnings;
30
+ target.push({ severity: issue.severity, source: 'protocol', code: issue.code, message: issue.message, path: issue.path });
31
+ }
32
+ for (const task of tasks) {
33
+ const evidence = (0, evidence_lint_1.createEvidenceLintReport)(projectRoot, task.id);
34
+ checks.push({
35
+ id: `evidence:${task.id}`,
36
+ source: 'evidence',
37
+ ok: evidence.ok,
38
+ taskId: task.id,
39
+ summary: evidence.ok ? 'Evidence lint passed.' : 'Evidence lint reported issues.'
40
+ });
41
+ for (const issue of evidence.issues) {
42
+ const target = issue.severity === 'error' ? blockers : warnings;
43
+ target.push({ severity: issue.severity, source: 'evidence', code: issue.code, message: issue.message, taskId: task.id, path: issue.path });
44
+ }
45
+ if (taskLooksDone(task.dir) || options.taskId) {
46
+ const proof = (0, proof_status_1.createProofStatusReport)(projectRoot, task.id);
47
+ checks.push(toProofCheck(task.id, proof));
48
+ for (const issue of proof.blockers)
49
+ blockers.push({ ...issue, source: 'proof', taskId: task.id });
50
+ for (const issue of proof.warnings)
51
+ warnings.push({ ...issue, source: 'proof', taskId: task.id });
52
+ }
53
+ }
54
+ checks.push({
55
+ id: 'release:deferred',
56
+ source: 'release',
57
+ ok: true,
58
+ summary: 'Release gate aggregation is deferred unless release work is explicitly requested.'
59
+ });
60
+ return {
61
+ schemaVersion: 'hadara.ci.gate.v1',
62
+ command: 'ci.gate',
63
+ ok: mode === 'advisory' ? true : blockers.length === 0,
64
+ mode,
65
+ scope: { ...(options.taskId ? { taskId: options.taskId } : {}), taskCount: tasks.length, allowEmpty },
66
+ checks,
67
+ blockers,
68
+ warnings
69
+ };
70
+ }
71
+ function applyScopeGuard(input) {
72
+ if (input.taskCount > 0) {
73
+ input.checks.push({
74
+ id: 'scope:tasks',
75
+ source: 'proof',
76
+ ok: true,
77
+ ...(input.taskId ? { taskId: input.taskId } : {}),
78
+ summary: `Validating ${input.taskCount} task capsule${input.taskCount === 1 ? '' : 's'}.`
79
+ });
80
+ return;
81
+ }
82
+ if (input.taskId) {
83
+ input.checks.push({ id: 'scope:tasks', source: 'proof', ok: false, taskId: input.taskId, summary: `Requested task ${input.taskId} was not found.` });
84
+ input.blockers.push({
85
+ severity: 'error',
86
+ source: 'proof',
87
+ code: 'CI_GATE_TASK_NOT_FOUND',
88
+ message: `CI gate found no Task Capsule for ${input.taskId}. Pass an existing task id.`,
89
+ taskId: input.taskId
90
+ });
91
+ return;
92
+ }
93
+ const emptyScopeIsBlocking = input.mode === 'strict' && !input.allowEmpty;
94
+ input.checks.push({ id: 'scope:tasks', source: 'proof', ok: !emptyScopeIsBlocking, summary: 'No Done Task Capsules were found to validate.' });
95
+ const issue = {
96
+ severity: emptyScopeIsBlocking ? 'error' : 'warning',
97
+ source: 'proof',
98
+ code: 'CI_GATE_NO_DONE_TASKS',
99
+ message: emptyScopeIsBlocking
100
+ ? 'Strict CI gate found no Done Task Capsules to validate. Pass --task <id> to scope a specific task, or --allow-empty only for bootstrap projects.'
101
+ : 'CI gate found no Done Task Capsules to validate; proof checks were skipped.'
102
+ };
103
+ (emptyScopeIsBlocking ? input.blockers : input.warnings).push(issue);
104
+ }
105
+ function selectTasks(projectRoot, taskId) {
106
+ const tasks = (0, task_capsule_1.listTaskCapsules)(projectRoot);
107
+ if (taskId)
108
+ return tasks.filter((task) => task.id === taskId);
109
+ return tasks.filter((task) => taskLooksDone(task.dir));
110
+ }
111
+ function taskLooksDone(taskDir) {
112
+ const taskPath = node_path_1.default.join(taskDir, 'TASK.md');
113
+ if (!node_fs_1.default.existsSync(taskPath))
114
+ return false;
115
+ return /\| Status \| Done \|/.test(node_fs_1.default.readFileSync(taskPath, 'utf8'));
116
+ }
117
+ function toProofCheck(taskId, proof) {
118
+ return {
119
+ id: `proof:${taskId}`,
120
+ source: 'proof',
121
+ ok: proof.ok,
122
+ taskId,
123
+ summary: `Proof verdict ${proof.verdict}; freshness ${proof.freshness.status}.`
124
+ };
125
+ }
@@ -0,0 +1,151 @@
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.createProofStatusReport = createProofStatusReport;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const normalizer_1 = require("../evidence/normalizer");
9
+ const semantics_1 = require("../evidence/semantics");
10
+ const task_capsule_1 = require("../task/task-capsule");
11
+ const task_close_1 = require("../task/task-close");
12
+ const evidence_lint_1 = require("./evidence-lint");
13
+ function createProofStatusReport(projectRoot, taskId, mode = 'status') {
14
+ const task = (0, task_capsule_1.findTaskCapsule)(projectRoot, taskId);
15
+ if (!task) {
16
+ return {
17
+ schemaVersion: mode === 'explain' ? 'hadara.proof.explain.v1' : 'hadara.proof.status.v1',
18
+ command: mode === 'explain' ? 'proof.explain' : 'proof.status',
19
+ ok: false,
20
+ target: { kind: 'task', taskId },
21
+ claim: 'task-readiness',
22
+ verdict: 'unknown',
23
+ freshness: { status: 'unknown', checkedSources: [] },
24
+ summary: { passed: 0, failed: 0, blocked: 0, privateOnlySubstantive: 0, substantivePositive: 0 },
25
+ supportingEvidence: [],
26
+ blockers: [{ severity: 'error', code: 'TASK_NOT_FOUND', message: `Task Capsule not found: ${taskId}` }],
27
+ warnings: [],
28
+ nextActions: [{ id: 'create-task', message: `Create or select an existing Task Capsule for ${taskId}.` }],
29
+ ...(mode === 'explain' ? { explanation: createExplanation([], []) } : {})
30
+ };
31
+ }
32
+ const lint = (0, evidence_lint_1.createEvidenceLintReport)(projectRoot, taskId);
33
+ const audit = (0, task_close_1.createTaskAuditCloseReport)(projectRoot, taskId);
34
+ const normalized = (0, normalizer_1.normalizeEvidenceRecordsInMemoryOrder)(lint.records, { taskDir: task.dir });
35
+ const semanticIssues = lint.issues.filter((issue) => issue.code.startsWith('TASK_DONE_'));
36
+ const freshnessIssues = audit.issues.map((issue) => ({
37
+ severity: 'warning',
38
+ code: issue.code,
39
+ message: issue.message,
40
+ path: issue.path
41
+ }));
42
+ const blockers = semanticIssues.filter((issue) => issue.severity === 'error').map(toProofIssue);
43
+ const warnings = [...semanticIssues.filter((issue) => issue.severity === 'warning').map(toProofIssue), ...freshnessIssues];
44
+ const summary = summarizeProofEvidence(normalized);
45
+ const freshness = createFreshness(projectRoot, task.dir, audit);
46
+ const verdict = selectVerdict({ blockers, warnings, summary, freshnessStatus: freshness.status });
47
+ return {
48
+ schemaVersion: mode === 'explain' ? 'hadara.proof.explain.v1' : 'hadara.proof.status.v1',
49
+ command: mode === 'explain' ? 'proof.explain' : 'proof.status',
50
+ ok: blockers.length === 0,
51
+ target: { kind: 'task', taskId },
52
+ claim: 'task-readiness',
53
+ verdict,
54
+ freshness,
55
+ summary,
56
+ supportingEvidence: normalized.filter((record) => (0, semantics_1.classifyEvidenceStrength)(record) === 'substantive-positive').slice(-5).map(toEvidenceRef),
57
+ blockers,
58
+ warnings,
59
+ nextActions: createNextActions(taskId, verdict, blockers, freshness.status),
60
+ ...(mode === 'explain' ? { explanation: createExplanation(semanticIssues, freshnessIssues) } : {})
61
+ };
62
+ }
63
+ function summarizeProofEvidence(records) {
64
+ const substantive = records.filter((record) => (0, semantics_1.classifyEvidenceStrength)(record) === 'substantive-positive');
65
+ return {
66
+ passed: records.filter((record) => record.outcome === 'passed').length,
67
+ failed: records.filter((record) => record.outcome === 'failed').length,
68
+ blocked: records.filter((record) => record.outcome === 'blocked').length,
69
+ privateOnlySubstantive: substantive.length > 0 && substantive.every((record) => record.visibility === 'private') ? substantive.length : 0,
70
+ substantivePositive: substantive.length
71
+ };
72
+ }
73
+ function createFreshness(projectRoot, taskDir, audit) {
74
+ // Freshness is derived from the task close audit, whose source hash covers the full
75
+ // close-relevant document set; expose that same set plus the evidence files the proof reads.
76
+ const checkedSources = Array.from(new Set([
77
+ ...(0, task_close_1.closeRelevantSourceRelativePaths)(projectRoot, taskDir),
78
+ toPortablePath(node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, 'evidence.jsonl'))),
79
+ toPortablePath(node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, 'EVIDENCE.md')))
80
+ ])).sort();
81
+ const verdict = audit.auditVerdict.verdict;
82
+ const status = !audit.auditVerdict.closeEvidenceFound
83
+ ? 'missing'
84
+ : audit.auditVerdict.verdict === 'closed-valid'
85
+ ? 'fresh'
86
+ : 'stale';
87
+ return {
88
+ status,
89
+ checkedSources,
90
+ closeVerdict: verdict,
91
+ ...(audit.auditVerdict.reportHashMatches !== undefined ? { reportHashMatches: audit.auditVerdict.reportHashMatches } : {}),
92
+ ...(audit.auditVerdict.sourceHashMatches !== undefined ? { sourceHashMatches: audit.auditVerdict.sourceHashMatches } : {})
93
+ };
94
+ }
95
+ function selectVerdict(input) {
96
+ const codes = new Set(input.blockers.map((issue) => issue.code));
97
+ if (codes.has('TASK_DONE_WITH_FAILED_EVIDENCE') || codes.has('TASK_DONE_WITH_UNEXPLAINED_BLOCKED_EVIDENCE'))
98
+ return 'blocked';
99
+ if (input.summary.substantivePositive === 0 || codes.has('TASK_DONE_WITHOUT_SUBSTANTIVE_EVIDENCE') || codes.has('TASK_DONE_WITH_ONLY_WEAK_EVIDENCE')) {
100
+ return 'insufficient';
101
+ }
102
+ if (input.blockers.length > 0)
103
+ return 'blocked';
104
+ if (input.warnings.length > 0 || input.summary.privateOnlySubstantive > 0 || input.freshnessStatus !== 'fresh')
105
+ return 'warning';
106
+ return 'sufficient';
107
+ }
108
+ function createNextActions(taskId, verdict, blockers, freshnessStatus) {
109
+ const actions = [];
110
+ if (blockers.length > 0)
111
+ actions.push({ id: 'inspect-evidence-lint', command: `hadara evidence lint --task ${taskId} --json`, message: 'Inspect semantic evidence blockers.' });
112
+ if (freshnessStatus !== 'fresh')
113
+ actions.push({ id: 'refresh-close-proof', command: `hadara task close --task ${taskId} --json`, message: 'Review close proof freshness and append a fresh close proof when appropriate.' });
114
+ if (verdict === 'insufficient')
115
+ actions.push({ id: 'add-substantive-evidence', command: `hadara evidence add-command --task ${taskId} --summary "..." --result passed --json`, message: 'Record substantive public evidence for the readiness claim.' });
116
+ return actions;
117
+ }
118
+ function createExplanation(semanticIssues, freshnessIssues) {
119
+ return {
120
+ rules: [
121
+ 'Substantive passed evidence is required for a sufficient task-readiness claim.',
122
+ 'Unresolved failed or unexplained blocked evidence makes the proof blocked.',
123
+ 'Private-only substantive evidence and stale or missing close proof produce warnings.',
124
+ 'Freshness is derived from task close audit source/report hash comparison.'
125
+ ],
126
+ semanticIssueCodes: semanticIssues.map((issue) => issue.code),
127
+ freshnessIssueCodes: freshnessIssues.map((issue) => issue.code)
128
+ };
129
+ }
130
+ function toProofIssue(issue) {
131
+ return {
132
+ severity: issue.severity,
133
+ code: issue.code,
134
+ message: issue.message,
135
+ ...(issue.evidenceId ? { evidenceId: issue.evidenceId } : {}),
136
+ ...(issue.path ? { path: issue.path } : {})
137
+ };
138
+ }
139
+ function toEvidenceRef(record) {
140
+ return {
141
+ id: record.id,
142
+ time: record.time,
143
+ category: record.category,
144
+ outcome: record.outcome,
145
+ visibility: record.visibility,
146
+ summary: record.summary
147
+ };
148
+ }
149
+ function toPortablePath(value) {
150
+ return value.split(node_path_1.default.sep).join('/');
151
+ }
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createTaskCloseReport = createTaskCloseReport;
7
+ exports.closeRelevantSourceRelativePaths = closeRelevantSourceRelativePaths;
7
8
  exports.executeTaskCloseEvidence = executeTaskCloseEvidence;
8
9
  exports.createTaskAuditCloseReport = createTaskAuditCloseReport;
9
10
  exports.formatTaskAuditCloseReport = formatTaskAuditCloseReport;
@@ -298,8 +299,8 @@ function hashValidationInputs(validation, evidenceLint, protocolDoctor) {
298
299
  });
299
300
  return `sha256:${node_crypto_1.default.createHash('sha256').update(payload, 'utf8').digest('hex')}`;
300
301
  }
301
- function hashCloseRelevantSource(projectRoot, taskDir) {
302
- const relativePaths = [
302
+ function closeRelevantSourceRelativePaths(projectRoot, taskDir) {
303
+ return [
303
304
  node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, 'TASK.md')),
304
305
  node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, 'PLAN.md')),
305
306
  node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, 'CONTEXT.md')),
@@ -313,6 +314,9 @@ function hashCloseRelevantSource(projectRoot, taskDir) {
313
314
  ]
314
315
  .map(toPortablePath)
315
316
  .sort();
317
+ }
318
+ function hashCloseRelevantSource(projectRoot, taskDir) {
319
+ const relativePaths = closeRelevantSourceRelativePaths(projectRoot, taskDir);
316
320
  const payload = relativePaths.map((relativePath) => {
317
321
  const absolutePath = node_path_1.default.join(projectRoot, relativePath);
318
322
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadara",
3
- "version": "0.2.0-rc.2",
3
+ "version": "0.2.0-rc.3",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "HADARA: portable agentic development workbench",