hadara 0.2.0-rc.1 → 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/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # HADARA
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/ictseoyoungmin/HADARA-dev/main/docs/assets/hadara_sub_right_name.png" alt="HADARA" width="720">
4
+ <img src="https://raw.githubusercontent.com/ictseoyoungmin/HADARA/main/docs/assets/hadara_sub_right_name.png" alt="HADARA" width="720">
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.1-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.1
26
+ hadara@0.2.0-rc.3
27
27
  ```
28
28
 
29
- T-0275 refreshes package smoke, clean-checkout smoke, release artifact, release dry-run, and publish dry-run evidence for `0.2.0-rc.1`. T-0269 prepared the previous `0.2.0-rc.0` publish path, but rc.1 supersedes it 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,10 +35,12 @@ 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 source and publish-candidate version. |
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. |
39
41
  | GitHub Release | Secondary target, still approval-gated. |
40
42
  | Docker image | Deferred. |
41
- | PyPI/Python package | Advisory preview only. |
43
+ | PyPI/Python package | `hadara==0.2.0rc1` published preview bridge. |
42
44
  | Installer scripts / USB launchers | Deferred. |
43
45
 
44
46
  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.
@@ -47,10 +49,10 @@ No release command should publish, create a GitHub Release, build Docker images,
47
49
 
48
50
  Requires Node.js 22.
49
51
 
50
- Install the current RC:
52
+ Install the current published RC:
51
53
 
52
54
  ```bash
53
- npm install -g hadara@0.2.0-rc.1
55
+ npm install -g hadara@0.2.0-rc.2
54
56
  hadara doctor --json
55
57
  hadara task list --json
56
58
  hadara tools list --json
@@ -59,11 +61,11 @@ hadara tools list --json
59
61
  Run without a global install:
60
62
 
61
63
  ```bash
62
- npx hadara@0.2.0-rc.1 doctor --json
63
- npx hadara@0.2.0-rc.1 tools list --json
64
+ npx hadara@0.2.0-rc.2 doctor --json
65
+ npx hadara@0.2.0-rc.2 tools list --json
64
66
  ```
65
67
 
66
- Previous published RC: `hadara@0.1.0-rc.0` remains available on npm for comparison or rollback, but new installs should use the current RC once it is published.
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.
67
69
 
68
70
  ## What HADARA Gives You
69
71
 
@@ -99,9 +101,9 @@ Task workflow:
99
101
  hadara task next --json
100
102
  hadara task create "implement a focused change" --json
101
103
  hadara task status --task T-0001 --json
102
- hadara task ready --task T-0001 --level done --json
103
104
  hadara task finish --task T-0001 --json
104
105
  hadara task finish --task T-0001 --execute --json
106
+ hadara task ready --task T-0001 --level done --json
105
107
  hadara task close --task T-0001 --json
106
108
  hadara task close --task T-0001 --execute --json
107
109
  hadara task audit-close --task T-0001 --json
@@ -113,6 +115,9 @@ Evidence and handoff:
113
115
  hadara evidence collect --task T-0001 --json
114
116
  hadara evidence add-command --task T-0001 --summary "Focused validation passed." --result passed --json
115
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
116
121
  hadara handoff suggest --task T-0001 --json
117
122
  ```
118
123
 
@@ -150,15 +155,18 @@ hadara task status --task T-XXXX --json
150
155
 
151
156
  # Do the scoped work.
152
157
 
153
- 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
159
+
160
+ hadara task finish --task T-XXXX --json
161
+ hadara task finish --task T-XXXX --execute --json
162
+
163
+ # Finalize Task Capsule docs and tracked state docs before closing.
164
+
154
165
  hadara task ready --task T-XXXX --level done --json
155
166
 
156
167
  # Optional workflow compression / next action preview:
157
168
  hadara task complete --task T-XXXX --json
158
169
 
159
- hadara task finish --task T-XXXX --json
160
- hadara task finish --task T-XXXX --execute --json
161
-
162
170
  hadara task close --task T-XXXX --json
163
171
  hadara task close --task T-XXXX --execute --json
164
172
 
@@ -175,6 +183,8 @@ Important distinctions:
175
183
  | `task close --execute` | Appends close evidence only. |
176
184
  | `task audit-close` | Read-only close proof audit. |
177
185
 
186
+ Before `task close --execute`, finish Task Capsule docs, acceptance/tests/handoff notes, evidence summaries, Task Board updates, and tracked state docs. After close execute, changing those close-source docs intentionally invalidates the previous close proof and requires rerunning ready/close/audit. Use stable wording for close results instead of pasting volatile close evidence ids into close-source docs.
187
+
178
188
  ## Initialize a Project
179
189
 
180
190
  ```bash
@@ -184,6 +194,8 @@ hadara init --profile standard
184
194
  hadara init --profile governed
185
195
  ```
186
196
 
197
+ Every profile generates `docs/TASK_WORKFLOW_COMMANDS.md` so fresh projects get the current evidence, ready, finish, close, and audit-close loop.
198
+
187
199
  | Profile | Use When |
188
200
  |---|---|
189
201
  | `basic` | Small project, only task/handoff discipline needed. |
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
@@ -14,9 +14,9 @@ const args_1 = require("./args");
14
14
  const INIT_PROFILE_SPECS = {
15
15
  basic: {
16
16
  profile: 'basic',
17
- generatedDocsDescription: 'Core session docs only',
17
+ generatedDocsDescription: 'Core session docs plus task workflow commands',
18
18
  intendedUse: 'Small projects that need Task Capsules, evidence, and handoff discipline without planning overhead.',
19
- specialNotes: 'SOP required reading references only core docs plus active Task Capsule docs.',
19
+ specialNotes: 'SOP required reading references core docs, task workflow docs, and active Task Capsule docs.',
20
20
  docs: {
21
21
  architecture: false,
22
22
  developmentSlices: false,
@@ -141,6 +141,7 @@ function createGeneratedScaffoldFiles(profile) {
141
141
  { path: 'docs/TASK_BOARD.md', content: '# TASK_BOARD\n\n| ID | Title | Status | Capsule | Notes |\n|---|---|---|---|---|\n' },
142
142
  { path: 'docs/AGENT_HANDOFF.md', content: createAgentHandoffDoc() },
143
143
  { path: 'docs/IMPLEMENTATION_SOP.md', content: createImplementationSopDoc(spec) },
144
+ { path: 'docs/TASK_WORKFLOW_COMMANDS.md', content: createTaskWorkflowCommandsDoc() },
144
145
  { path: 'AGENTS.md', content: createAgentsDoc(spec) },
145
146
  { path: '.gitignore', content: createGitignoreDoc() }
146
147
  ];
@@ -163,7 +164,7 @@ function createGeneratedScaffoldFiles(profile) {
163
164
  function createInitDoctorReport(projectRoot) {
164
165
  const issues = [];
165
166
  const actions = [];
166
- const requiredCore = ['AGENTS.md', '.gitignore', 'docs/PROJECT_STATE.md', 'docs/AGENT_HANDOFF.md', 'docs/TASK_BOARD.md', 'docs/IMPLEMENTATION_SOP.md'];
167
+ const requiredCore = ['AGENTS.md', '.gitignore', 'docs/PROJECT_STATE.md', 'docs/AGENT_HANDOFF.md', 'docs/TASK_BOARD.md', 'docs/IMPLEMENTATION_SOP.md', 'docs/TASK_WORKFLOW_COMMANDS.md'];
167
168
  for (const relativePath of requiredCore) {
168
169
  if (!node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, relativePath))) {
169
170
  issues.push({ severity: 'error', code: 'INIT_CORE_DOC_MISSING', path: relativePath, message: `${relativePath} is missing from the init scaffold.` });
@@ -391,6 +392,7 @@ const CANONICAL_TABLE_HEADERS = {
391
392
  'docs/AGENT_HANDOFF.md': ['| Area | State | Notes |', '| Task | Summary | Evidence |', '| Issue | Impact | Next Step |', '| Step | Reason | Done Evidence |', '| Check | Latest Evidence | Notes |', '| History Type | Path | When to Use |'],
392
393
  'docs/TASK_BOARD.md': ['| ID | Title | Status | Capsule | Notes |'],
393
394
  'docs/IMPLEMENTATION_SOP.md': ['| Document | When to Read | Purpose |', '| Profile | Scale | Generated Docs | Intended Use | Special Notes |', '| Document | Required Structure |'],
395
+ 'docs/TASK_WORKFLOW_COMMANDS.md': ['| Command | Default Write Behavior | Notes |'],
394
396
  'docs/ARCHITECTURE.md': ['| Field | Value |', '| Boundary | Rule | Notes |', '| Component | Path / Surface | Responsibility | Status |'],
395
397
  'docs/DEVELOPMENT_SLICES.md': ['| Order | Slice | Capsule | Purpose | Done Evidence |'],
396
398
  'docs/DECISIONS.md': ['| ID | Date | Decision | Status | Rationale | Evidence |'],
@@ -493,7 +495,8 @@ function sopRequiredReadingRowsForProfile(profile) {
493
495
  ['`docs/PROJECT_STATE.md`', 'Every session', 'Current product state and source-of-truth map.'],
494
496
  ['`docs/AGENT_HANDOFF.md`', 'Every session', 'Compact handoff and next recommended step.'],
495
497
  ['`docs/TASK_BOARD.md`', 'Every session', 'Work queue and task status.'],
496
- ['`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local HADARA workflow rules and project-specific required-reading registry.']
498
+ ['`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local HADARA workflow rules and project-specific required-reading registry.'],
499
+ ['`docs/TASK_WORKFLOW_COMMANDS.md`', 'Starting, finishing, closing, auditing, or explaining task workflow commands', 'Standard task loop, dry-run boundaries, and command `ok` semantics.']
497
500
  ];
498
501
  if (profile === 'standard' || profile === 'governed') {
499
502
  rows.push(['`docs/ARCHITECTURE.md`', 'Architecture, component, or boundary work', 'Current system shape and ownership boundaries.'], ['`docs/DEVELOPMENT_SLICES.md`', 'Starting, completing, or reclassifying slices', 'Roadmap ordering and completion evidence.'], ['`docs/DECISIONS.md`', 'Project-level decision work', 'Durable decisions that affect architecture or workflow.'], ['`docs/TEST_STRATEGY.md`', 'Validation planning or completion checks', 'Routine suites and special-case smoke boundaries.']);
@@ -508,7 +511,8 @@ function agentsRequiredReadingRowsForProfile(profile) {
508
511
  { document: '`docs/PROJECT_STATE.md`', when: 'Every session', purpose: 'Current product and capability state.' },
509
512
  { document: '`docs/AGENT_HANDOFF.md`', when: 'Every session', purpose: 'Compact continuation state.' },
510
513
  { document: '`docs/TASK_BOARD.md`', when: 'Every session', purpose: 'Current task queue and status.' },
511
- { document: '`docs/IMPLEMENTATION_SOP.md`', when: 'Every session', purpose: 'Local workflow and required-reading registry.' }
514
+ { document: '`docs/IMPLEMENTATION_SOP.md`', when: 'Every session', purpose: 'Local workflow and required-reading registry.' },
515
+ { document: '`docs/TASK_WORKFLOW_COMMANDS.md`', when: 'Starting, finishing, closing, auditing, or explaining task workflow commands', purpose: 'Standard task loop, dry-run boundaries, and command `ok` semantics.' }
512
516
  ];
513
517
  if (profile === 'standard' || profile === 'governed') {
514
518
  rows.push({ document: '`docs/ARCHITECTURE.md`', when: 'Architecture, component, or boundary work', purpose: 'Current system shape and ownership boundaries.' }, { document: '`docs/DEVELOPMENT_SLICES.md`', when: 'Starting, completing, or reclassifying a development slice', purpose: 'Roadmap ordering, prerequisites, and completion evidence.' }, { document: '`docs/DECISIONS.md`', when: 'Project-level decision work', purpose: 'Durable project decisions.' }, { document: '`docs/TEST_STRATEGY.md`', when: 'Validation planning or completion checks', purpose: 'Routine suites and special-case checks.' });
@@ -632,7 +636,7 @@ function inferProfileFromGeneratedDocs(projectRoot) {
632
636
  return 'basic';
633
637
  }
634
638
  function requiredDocsForProfile(profile) {
635
- const docs = ['docs/PROJECT_STATE.md', 'docs/AGENT_HANDOFF.md', 'docs/TASK_BOARD.md', 'docs/IMPLEMENTATION_SOP.md'];
639
+ const docs = ['docs/PROJECT_STATE.md', 'docs/AGENT_HANDOFF.md', 'docs/TASK_BOARD.md', 'docs/IMPLEMENTATION_SOP.md', 'docs/TASK_WORKFLOW_COMMANDS.md'];
636
640
  if (profile === 'standard' || profile === 'governed') {
637
641
  docs.push('docs/ARCHITECTURE.md', 'docs/DEVELOPMENT_SLICES.md', 'docs/DECISIONS.md', 'docs/TEST_STRATEGY.md');
638
642
  }
@@ -765,7 +769,7 @@ function createAgentHandoffDoc() {
765
769
  | Area | State | Notes |
766
770
  |---|---|---|
767
771
  | Scaffold | Initialized | HADARA protocol scaffold is initialized. |
768
- | Required Reading | Pending | Read \`PROJECT_STATE\`, \`TASK_BOARD\`, and \`IMPLEMENTATION_SOP\` before starting. |
772
+ | Required Reading | Pending | Read \`PROJECT_STATE\`, \`AGENT_HANDOFF\`, \`TASK_BOARD\`, \`IMPLEMENTATION_SOP\`, and \`TASK_WORKFLOW_COMMANDS\` before starting. |
769
773
 
770
774
  ## Last 3 Completed Tasks
771
775
 
@@ -843,7 +847,8 @@ function createImplementationSopDoc(spec) {
843
847
  ['`docs/PROJECT_STATE.md`', 'Every session', 'Current product state and source-of-truth map.'],
844
848
  ['`docs/AGENT_HANDOFF.md`', 'Every session', 'Compact handoff and next recommended step.'],
845
849
  ['`docs/TASK_BOARD.md`', 'Every session', 'Work queue and task status.'],
846
- ['`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local HADARA workflow rules and project-specific required-reading registry.']
850
+ ['`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local HADARA workflow rules and project-specific required-reading registry.'],
851
+ ['`docs/TASK_WORKFLOW_COMMANDS.md`', 'Starting, finishing, closing, auditing, or explaining task workflow commands', 'Standard task loop, dry-run boundaries, and command `ok` semantics.']
847
852
  ];
848
853
  if (spec.docs.architecture) {
849
854
  requiredReadingRows.push(['`docs/ARCHITECTURE.md`', 'Architecture, component, or boundary work', 'Current system shape and ownership boundaries.']);
@@ -872,7 +877,8 @@ function createImplementationSopDoc(spec) {
872
877
  ['`docs/PROJECT_STATE.md`', 'Product, Current Phase, Current Status, and Single Source of Truth sections.'],
873
878
  ['`docs/AGENT_HANDOFF.md`', 'Current State, Last 3 Completed Tasks, Current Known Problems, Next Recommended Step, Validation Baseline, and Historical Index sections.'],
874
879
  ['`docs/TASK_BOARD.md`', 'One task table with ID, Title, Status, Capsule, and Notes columns.'],
875
- ['`docs/IMPLEMENTATION_SOP.md`', 'Session Start, Required Reading, Init Profile Matrix, Scaffold Document Structure, Implementation, Validation, Session End, and Handoff Compaction sections.']
880
+ ['`docs/IMPLEMENTATION_SOP.md`', 'Session Start, Required Reading, Project-Specific Documents, Init Profile Matrix, Scaffold Document Structure, Implementation, Standard Task Workflow Loop, Validation, Evidence Records, Session End, and Handoff Compaction sections.'],
881
+ ['`docs/TASK_WORKFLOW_COMMANDS.md`', 'Standard Task Loop, Command Semantics, Non-Overlap Rules, and State Documents sections.']
876
882
  ];
877
883
  if (spec.docs.architecture)
878
884
  structureRows.push(['`docs/ARCHITECTURE.md`', 'Overview, Boundaries, and Current Components sections.']);
@@ -905,14 +911,23 @@ ${numberedList(sessionStart)}
905
911
  |---|---|---|
906
912
  ${requiredReadingRows.map(formatTableRow).join('\n')}
907
913
 
908
- When adding project-specific specs, contracts, or roadmap files, add them to this table and explain when agents must read them. Use \`hadara init register-doc --path <path> --when <text> --purpose <text> --json\` to preview registration, and add \`--execute\` to update this table.
914
+ ## Project-Specific Documents
915
+
916
+ When adding project-specific specs, contracts, roadmap files, or human/agent operating notes, register them in the Required Reading table before expecting people or agents to rely on them. Each row must explain when to read the document and what decision or workflow boundary it owns.
917
+
918
+ \`\`\`bash
919
+ hadara init register-doc --path docs/specs/example.md --when "When changing example behavior" --purpose "Example behavior contract" --json
920
+ hadara init register-doc --path docs/specs/example.md --when "When changing example behavior" --purpose "Example behavior contract" --execute --json
921
+ \`\`\`
922
+
923
+ Use \`--require-exists\` when the document must already exist before registration. Keep local-only notes out of committed required reading unless they are intentionally part of the project handoff.
909
924
 
910
925
  ## Init Profile Matrix
911
926
 
912
927
  | Profile | Scale | Generated Docs | Intended Use | Special Notes |
913
928
  |---|---|---|---|---|
914
- | \`basic\` | Small | Core session docs only | Small projects that need Task Capsules, evidence, and handoff discipline without planning overhead. | SOP required reading references only core docs plus active Task Capsule docs. |
915
- | \`standard\` | Medium, default | Core docs plus planning, architecture, decision, and validation docs | Most multi-session projects that need roadmap slices and repeatable validation. | Optional integrations must be registered before agents rely on them. |
929
+ | \`basic\` | Small | Core session docs plus task workflow commands | Small projects that need Task Capsules, evidence, and handoff discipline without planning overhead. | SOP required reading references core docs, task workflow docs, and active Task Capsule docs. |
930
+ | \`standard\` | Medium, default | Basic docs plus planning, architecture, decision, and validation docs | Most multi-session projects that need roadmap slices and repeatable validation. | Optional integrations must be registered before agents rely on them. |
916
931
  | \`governed\` | Heavy | Standard docs plus security, refactor log, and roadmap docs | Long-lived projects with stronger governance, security boundaries, refactor history, or roadmap-level planning. | Project-specific contracts still must be manually registered in Required Reading. |
917
932
 
918
933
  ## Scaffold Document Structure
@@ -932,12 +947,71 @@ Prefer tables for repeated records and \`##\`/\`###\` headings for durable secti
932
947
  3. Make the smallest coherent change that satisfies acceptance criteria.
933
948
  4. Update task-local docs when scope changes.
934
949
 
950
+ ## Standard Task Workflow Loop
951
+
952
+ The authoritative command semantics live in \`docs/TASK_WORKFLOW_COMMANDS.md\`. For ordinary implementation capsules, use this loop:
953
+
954
+ \`\`\`bash
955
+ hadara task next --json
956
+
957
+ # If a matching capsule already exists:
958
+ hadara task status --task T-XXXX --json
959
+
960
+ # If no matching capsule exists, create one first:
961
+ hadara task create "task title" --json
962
+ hadara task status --task T-XXXX --json
963
+
964
+ # Do the scoped work.
965
+
966
+ hadara evidence add-command --task T-XXXX --summary "..." --result passed --idempotency-key "command:T-XXXX:check" --json
967
+
968
+ hadara task finish --task T-XXXX --json
969
+ hadara task finish --task T-XXXX --execute --json
970
+
971
+ # Finalize Task Capsule docs and tracked state docs before closing.
972
+
973
+ hadara task ready --task T-XXXX --level done --json
974
+
975
+ # Optional workflow compression / next action preview:
976
+ hadara task complete --task T-XXXX --json
977
+
978
+ hadara task close --task T-XXXX --json
979
+ hadara task close --task T-XXXX --execute --json
980
+
981
+ hadara task audit-close --task T-XXXX --json
982
+ \`\`\`
983
+
984
+ | Command | Default Write Behavior | Notes |
985
+ |---|---|---|
986
+ | \`task next\` | Read-only | Recommends work; does not create tasks. |
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; optional \`--idempotency-key\` prevents duplicate same-key records. |
989
+ | \`task ready\` | Read-only | Checks readiness; does not mutate evidence or status docs. |
990
+ | \`task finish\` | Dry-run by default; writes only with \`--execute\` | Bounded to \`TASK.md\` and \`docs/TASK_BOARD.md\`. |
991
+ | \`task close\` | Dry-run by default; writes only with \`--execute\` | Bounded to close evidence append. |
992
+ | \`task audit-close\` | Read-only | Verifies close evidence after close. |
993
+
994
+ Before running \`task ready\` and \`task close\`, finish all close-source edits: Task Capsule docs, acceptance/tests/handoff notes, evidence summaries, \`docs/TASK_BOARD.md\`, and tracked state docs such as \`docs/PROJECT_STATE.md\`, \`docs/AGENT_HANDOFF.md\`, and roadmap/slice docs when they apply. After \`task close --execute --json\`, do not edit those close-source documents unless you intend to rerun \`task ready\`, \`task close\`, and \`task audit-close\`. Avoid writing volatile close evidence ids into close-source docs; use stable wording such as "close evidence appended; audit returned closed-valid".
995
+
935
996
  ## Validation
936
997
 
937
998
  1. Run relevant tests.
938
- 2. Run \`hadara harness validate --task <task-id> --json\`.
939
- 3. Record evidence in \`EVIDENCE.md\` and \`evidence.jsonl\`.
940
- 4. Add project-specific integration or deployment smoke checks only after those surfaces exist and are documented for this project.
999
+ 2. Record meaningful evidence in \`EVIDENCE.md\` and \`evidence.jsonl\`.
1000
+ 3. Preview and execute \`hadara task finish --task <task-id> --json\` and \`hadara task finish --task <task-id> --execute --json\`.
1001
+ 4. Finalize Task Capsule docs and tracked state docs before close so the close source hash remains stable.
1002
+ 5. Run \`hadara task ready --task <task-id> --level done --json\` after finish and before close.
1003
+ 6. Preview and execute \`hadara task close --task <task-id> --json\` and \`hadara task close --task <task-id> --execute --json\`, then run \`hadara task audit-close --task <task-id> --json\`.
1004
+ 7. Add project-specific integration or deployment smoke checks only after those surfaces exist and are documented for this project.
1005
+
1006
+ \`task ready\` and \`task close\` include done-level Task Capsule validation. Use \`hadara harness validate --task <task-id> --level done --json\` directly when you need to debug capsule format or done-level validation failures.
1007
+
1008
+ ## Evidence Records
1009
+
1010
+ 1. Do not hand-edit Task Capsule \`evidence.jsonl\`.
1011
+ 2. Append evidence through HADARA commands so schema, visibility, and artifact-safety checks run consistently.
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. Add \`--idempotency-key <key>\` when rerunning the same logical check should report one durable evidence identity instead of appending duplicates.
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.
941
1015
 
942
1016
  ## Session End
943
1017
 
@@ -1042,8 +1116,18 @@ function createTestStrategyDoc() {
1042
1116
  | Step | Check | Evidence Location |
1043
1117
  |---|---|---|
1044
1118
  | 1 | Run the relevant suite from the table above. | Task Capsule \`EVIDENCE.md\` |
1045
- | 2 | Run \`hadara harness validate --task <task-id> --json\`. | Task Capsule \`EVIDENCE.md\` and \`evidence.jsonl\` |
1046
- | 3 | Record meaningful evidence in the Task Capsule. | Task Capsule files |
1119
+ | 2 | Record meaningful evidence in the Task Capsule. | Task Capsule \`EVIDENCE.md\` and \`evidence.jsonl\` |
1120
+ | 3 | Preview and execute \`task finish\` to synchronize status bookkeeping. | Task Capsule \`TASK.md\` and \`docs/TASK_BOARD.md\` |
1121
+ | 4 | Finalize Task Capsule docs and tracked state docs before close. | Task Capsule docs and tracked state docs |
1122
+ | 5 | Run \`hadara task ready --task <task-id> --level done --json\` after finish and before close. | Task Capsule \`EVIDENCE.md\` and \`evidence.jsonl\` |
1123
+ | 6 | Preview and execute \`task close\`, then run \`task audit-close\`. | Task Capsule close evidence |
1124
+
1125
+ ## Diagnostic Checks
1126
+
1127
+ | Check | Command | When To Use |
1128
+ |---|---|---|
1129
+ | Task Capsule format | \`hadara harness validate --task <task-id> --level done --json\` | \`task ready\` or \`task close\` reports done-level validation failures. |
1130
+ | Evidence index | \`hadara evidence lint --task <task-id> --json\` | Evidence files were touched manually by mistake or evidence drift is suspected. |
1047
1131
 
1048
1132
  ## Special-Case Checks
1049
1133
 
@@ -1072,14 +1156,94 @@ function createRoadmapDoc() {
1072
1156
  |---|---|---|
1073
1157
  `;
1074
1158
  }
1159
+ function createTaskWorkflowCommandsDoc() {
1160
+ return `# TASK_WORKFLOW_COMMANDS
1161
+
1162
+ HADARA task workflow commands are split by responsibility. Similar-looking commands are not interchangeable: some only report state, some check readiness, some perform bounded bookkeeping writes, and some append close evidence.
1163
+
1164
+ ## Standard Task Loop
1165
+
1166
+ Use this loop for ordinary implementation capsules:
1167
+
1168
+ \`\`\`bash
1169
+ hadara task next --json
1170
+
1171
+ # If a matching capsule already exists:
1172
+ hadara task status --task T-XXXX --json
1173
+
1174
+ # If no matching capsule exists, create one first:
1175
+ hadara task create "task title" --json
1176
+ hadara task status --task T-XXXX --json
1177
+
1178
+ # Do the scoped work.
1179
+
1180
+ hadara evidence add-command --task T-XXXX --summary "..." --result passed --idempotency-key "command:T-XXXX:check" --json
1181
+
1182
+ hadara task finish --task T-XXXX --json
1183
+ hadara task finish --task T-XXXX --execute --json
1184
+
1185
+ # Finalize Task Capsule docs and tracked state docs before closing.
1186
+
1187
+ hadara task ready --task T-XXXX --level done --json
1188
+
1189
+ # Optional workflow compression / next action preview:
1190
+ hadara task complete --task T-XXXX --json
1191
+
1192
+ hadara task close --task T-XXXX --json
1193
+ hadara task close --task T-XXXX --execute --json
1194
+
1195
+ hadara task audit-close --task T-XXXX --json
1196
+ \`\`\`
1197
+
1198
+ \`task finish\`, \`task ready\`, and \`task close\` are intentionally separate. \`finish\` synchronizes bounded status bookkeeping first. \`ready\` then validates the Done-level state. \`close\` records close evidence after validation succeeds. \`audit-close\` checks the resulting close evidence after the write.
1199
+
1200
+ The close model has three separate phases: validation proves readiness, close records the proof, and audit checks the already-recorded close evidence. Close evidence is excluded from the current validation loop because it is appended after validation; requiring it as a same-run precondition would create a fixed-point loop.
1201
+
1202
+ \`task ready\` and \`task close\` include done-level Task Capsule validation. Use \`hadara harness validate --task T-XXXX --level done --json\` directly when debugging capsule format, status-history, acceptance, evidence, or handoff validation failures.
1203
+
1204
+ Before close, finish all close-source edits: Task Capsule docs, acceptance/tests/handoff notes, evidence summaries, \`docs/TASK_BOARD.md\`, and tracked state docs such as \`docs/PROJECT_STATE.md\`, \`docs/AGENT_HANDOFF.md\`, and roadmap/slice docs when they apply. After \`task close --execute --json\`, changing those documents changes the close source hash and requires rerunning \`task ready\`, \`task close\`, and \`task audit-close\`. Do not paste volatile close evidence ids into close-source docs; prefer stable wording such as "close evidence appended; audit returned closed-valid".
1205
+
1206
+ ## Command Semantics
1207
+
1208
+ | Command | Default Write Behavior | Notes |
1209
+ |---|---|---|
1210
+ | \`task next\` | Read-only | Recommends work; does not create tasks. |
1211
+ | \`task status\` | Read-only | \`ok\` means report generation succeeded; readiness is in \`state.ready\`, \`summary.blockers\`, and \`issues\`. |
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; optional \`--idempotency-key\` prevents duplicate same-key records. |
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
+ | \`task ready\` | Read-only | Checks whether the task can satisfy the requested readiness level after finish. |
1216
+ | \`task complete\` | Read-only | Summarizes the current completion stage and next command; it does not execute lifecycle writes. |
1217
+ | \`task close\` | Dry-run by default; writes only with \`--execute\` | Appends only canonical close evidence after close preconditions pass. |
1218
+ | \`task audit-close\` | Read-only | Verifies close evidence after close. |
1219
+
1220
+ ## Non-Overlap Rules
1221
+
1222
+ - \`task next\` chooses work; it does not create a capsule or infer completion.
1223
+ - \`task status\` is an operator console; \`ok: true\` means report generation succeeded, not that the task is ready.
1224
+ - \`task ready\` checks readiness; it does not write evidence or status.
1225
+ - \`harness validate\` is a direct diagnostic for Task Capsule structure and done-level gates; it is not a replacement for close evidence.
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. \`--idempotency-key\` is optional; when supplied, same-key repeats return the existing record without appending duplicate Markdown or JSONL rows.
1228
+ - \`task finish\` may update only the Task Capsule \`TASK.md\` status and matching \`docs/TASK_BOARD.md\` status/path row.
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
+ - 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.
1231
+ - \`task audit-close\` is read-only and should be run after \`task close --execute --json\`.
1232
+
1233
+ ## State Documents
1234
+
1235
+ \`task finish --execute --json\` deliberately does not update broad prose state. Operators still update \`docs/PROJECT_STATE.md\`, \`docs/AGENT_HANDOFF.md\`, and any roadmap/slice docs generated for the selected profile when the task changes project state.
1236
+ `;
1237
+ }
1075
1238
  function createAgentsDoc(spec) {
1076
1239
  const requiredReadingRows = [
1077
1240
  ['1', '`docs/PROJECT_STATE.md`', 'Every session', 'Current product and capability state.'],
1078
1241
  ['2', '`docs/AGENT_HANDOFF.md`', 'Every session', 'Compact continuation state.'],
1079
1242
  ['3', '`docs/TASK_BOARD.md`', 'Every session', 'Current task queue and status.'],
1080
- ['4', '`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local workflow and required-reading registry.']
1243
+ ['4', '`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local workflow and required-reading registry.'],
1244
+ ['5', '`docs/TASK_WORKFLOW_COMMANDS.md`', 'Starting, finishing, closing, auditing, or explaining task workflow commands', 'Standard task loop, dry-run boundaries, and command `ok` semantics.']
1081
1245
  ];
1082
- let order = 5;
1246
+ let order = 6;
1083
1247
  if (spec.docs.architecture)
1084
1248
  requiredReadingRows.push([String(order++), '`docs/ARCHITECTURE.md`', 'Architecture, component, or boundary work', 'Current system shape and ownership boundaries.']);
1085
1249
  if (spec.docs.developmentSlices)
@@ -1099,7 +1263,8 @@ function createAgentsDoc(spec) {
1099
1263
  const ruleRows = [
1100
1264
  ['Task boundary', 'Keep work inside one Task Capsule whenever possible.', 'Active Task Capsule'],
1101
1265
  ['Task creation', 'If no suitable capsule exists, create one with `hadara task create <title>`.', '`docs/TASK_BOARD.md`'],
1102
- ['Evidence', 'Do not mark work done without evidence.', '`EVIDENCE.md`, `evidence.jsonl`'],
1266
+ ['Evidence', 'Do not mark work done without evidence. Do not hand-edit `evidence.jsonl`; record failed or blocked checks honestly instead of replacing them with optimistic summaries.', '`EVIDENCE.md`, `evidence.jsonl`'],
1267
+ ['Task workflow', 'For task workflow commands, follow `docs/TASK_WORKFLOW_COMMANDS.md`: record evidence, preview and execute `task finish`, finalize close-source docs, run `task ready`, preview and execute `task close`, then run `task audit-close`.', 'Task Capsule evidence'],
1103
1268
  ['Safety', 'Do not execute dangerous commands without explicit user approval.', 'Task Capsule evidence'],
1104
1269
  ['Secrets', 'Do not write secrets, private logs, or machine-local state into committed files.', 'Changed-file review'],
1105
1270
  ['Store boundary', 'Preserve the portable/project store boundary.', spec.docs.architecture ? '`.gitignore`, `docs/ARCHITECTURE.md`' : '`.gitignore`'],
@@ -1143,6 +1308,22 @@ dist/
1143
1308
  coverage/
1144
1309
  *.log
1145
1310
 
1311
+ # Python and test artifacts
1312
+ __pycache__/
1313
+ *.py[cod]
1314
+ *$py.class
1315
+ .pytest_cache/
1316
+ .mypy_cache/
1317
+ .ruff_cache/
1318
+ .coverage
1319
+ htmlcov/
1320
+ .venv/
1321
+ venv/
1322
+ env/
1323
+ *.db
1324
+ *.sqlite
1325
+ *.sqlite3
1326
+
1146
1327
  # HADARA local/private state
1147
1328
  .hadara/local/
1148
1329
  .hadara/tmp/
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.1",
3
+ "version": "0.2.0-rc.3",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "HADARA: portable agentic development workbench",