hadara 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/dist/agent/evidence.js +50 -0
  4. package/dist/agent/loop.js +124 -0
  5. package/dist/cli/args.js +70 -0
  6. package/dist/cli/dashboard.js +185 -0
  7. package/dist/cli/debt.js +41 -0
  8. package/dist/cli/doctor.js +68 -0
  9. package/dist/cli/errors.js +58 -0
  10. package/dist/cli/evidence-json.js +75 -0
  11. package/dist/cli/evidence.js +80 -0
  12. package/dist/cli/handoff.js +16 -0
  13. package/dist/cli/harness.js +57 -0
  14. package/dist/cli/hermes-json.js +31 -0
  15. package/dist/cli/hermes.js +28 -0
  16. package/dist/cli/init.js +142 -0
  17. package/dist/cli/install.js +34 -0
  18. package/dist/cli/main.js +216 -0
  19. package/dist/cli/mcp.js +15 -0
  20. package/dist/cli/package-smoke.js +37 -0
  21. package/dist/cli/policy-json.js +22 -0
  22. package/dist/cli/policy.js +43 -0
  23. package/dist/cli/release-artifact.js +47 -0
  24. package/dist/cli/release-dry-run.js +24 -0
  25. package/dist/cli/release-gate.js +28 -0
  26. package/dist/cli/release-publish.js +41 -0
  27. package/dist/cli/run-scaffold.js +68 -0
  28. package/dist/cli/run-state.js +41 -0
  29. package/dist/cli/run.js +191 -0
  30. package/dist/cli/smoke.js +58 -0
  31. package/dist/cli/status-json.js +6 -0
  32. package/dist/cli/status.js +26 -0
  33. package/dist/cli/task-json.js +8 -0
  34. package/dist/cli/task.js +64 -0
  35. package/dist/cli/tools.js +25 -0
  36. package/dist/cli/tui.js +72 -0
  37. package/dist/cli/write-preflight.js +27 -0
  38. package/dist/core/audit.js +41 -0
  39. package/dist/core/events.js +63 -0
  40. package/dist/core/fs.js +44 -0
  41. package/dist/core/paths.js +59 -0
  42. package/dist/core/redaction.js +178 -0
  43. package/dist/core/schema.js +253 -0
  44. package/dist/core/workspace.js +47 -0
  45. package/dist/evidence/evidence.js +170 -0
  46. package/dist/evidence/private-manifest.js +101 -0
  47. package/dist/handoff/handoff.js +49 -0
  48. package/dist/harness/replay.js +200 -0
  49. package/dist/harness/validate.js +465 -0
  50. package/dist/hermes/context-export.js +104 -0
  51. package/dist/index.js +29 -0
  52. package/dist/mcp/server.js +104 -0
  53. package/dist/mcp/tool-dispatch.js +159 -0
  54. package/dist/mcp/tool-registry.js +150 -0
  55. package/dist/mcp/tool-schemas.js +18 -0
  56. package/dist/policy/command-risk.js +39 -0
  57. package/dist/policy/permission-matrix.js +42 -0
  58. package/dist/policy/policy.js +20 -0
  59. package/dist/policy/preflight.js +47 -0
  60. package/dist/policy/presets.js +24 -0
  61. package/dist/policy/tokenizer.js +53 -0
  62. package/dist/providers/fallback-executor.js +46 -0
  63. package/dist/providers/mock-provider.js +49 -0
  64. package/dist/providers/provider-contract.js +2 -0
  65. package/dist/providers/provider-preparation.js +220 -0
  66. package/dist/providers/scripted-provider.js +69 -0
  67. package/dist/schemas/active-run-projection.schema.json +73 -0
  68. package/dist/schemas/active-run-resume.schema.json +68 -0
  69. package/dist/schemas/clean-checkout-smoke.schema.json +126 -0
  70. package/dist/schemas/context-export.schema.json +35 -0
  71. package/dist/schemas/event.schema.json +17 -0
  72. package/dist/schemas/evidence-list.schema.json +49 -0
  73. package/dist/schemas/feature-smoke.schema.json +67 -0
  74. package/dist/schemas/install-plan.schema.json +93 -0
  75. package/dist/schemas/package-smoke.schema.json +130 -0
  76. package/dist/schemas/private-evidence.schema.json +48 -0
  77. package/dist/schemas/provider-call.schema.json +42 -0
  78. package/dist/schemas/provider-config.schema.json +43 -0
  79. package/dist/schemas/release-artifact-manifest.schema.json +55 -0
  80. package/dist/schemas/release-artifact.schema.json +140 -0
  81. package/dist/schemas/release-dry-run.schema.json +141 -0
  82. package/dist/schemas/release-gate.schema.json +42 -0
  83. package/dist/schemas/release-publish.schema.json +114 -0
  84. package/dist/schemas/schema-index.json +145 -0
  85. package/dist/schemas/smoke-evidence-summary.schema.json +88 -0
  86. package/dist/schemas/tools-list.schema.json +78 -0
  87. package/dist/schemas/write-preflight.schema.json +47 -0
  88. package/dist/services/active-run-state.js +215 -0
  89. package/dist/services/capability-registry.js +540 -0
  90. package/dist/services/clean-checkout-smoke.js +393 -0
  91. package/dist/services/evidence-list.js +136 -0
  92. package/dist/services/feature-smoke.js +155 -0
  93. package/dist/services/harness-service.js +7 -0
  94. package/dist/services/install-plan.js +233 -0
  95. package/dist/services/operational-debt.js +767 -0
  96. package/dist/services/operations-status-service.js +195 -0
  97. package/dist/services/package-smoke.js +676 -0
  98. package/dist/services/policy-service.js +25 -0
  99. package/dist/services/project-read-model.js +101 -0
  100. package/dist/services/release-artifact-evidence.js +77 -0
  101. package/dist/services/release-artifact.js +351 -0
  102. package/dist/services/release-dry-run.js +253 -0
  103. package/dist/services/release-evidence.js +138 -0
  104. package/dist/services/release-publish.js +163 -0
  105. package/dist/services/smoke-evidence.js +104 -0
  106. package/dist/services/task-read-model.js +125 -0
  107. package/dist/services/tools-list.js +26 -0
  108. package/dist/services/write-preflight.js +240 -0
  109. package/dist/task/task-capsule.js +121 -0
  110. package/dist/tools/fake-shell.js +56 -0
  111. package/dist/tui/cache.js +341 -0
  112. package/dist/tui/constants.js +44 -0
  113. package/dist/tui/layout.js +140 -0
  114. package/dist/tui/markdown.js +238 -0
  115. package/dist/tui/read-model-worker.js +24 -0
  116. package/dist/tui/read-model.js +502 -0
  117. package/dist/tui/snapshot.js +434 -0
  118. package/dist/tui/state.js +229 -0
  119. package/dist/tui/terminal.js +475 -0
  120. package/dist/tui/theme.js +86 -0
  121. package/package.json +16 -0
@@ -0,0 +1,465 @@
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.validateTaskCapsule = validateTaskCapsule;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const task_capsule_1 = require("../task/task-capsule");
10
+ const REQUIRED_TASK_FILES = [
11
+ 'TASK.md',
12
+ 'PLAN.md',
13
+ 'CONTEXT.md',
14
+ 'FILES.md',
15
+ 'ACCEPTANCE.md',
16
+ 'TESTS.md',
17
+ 'RISKS.md',
18
+ 'DECISIONS.md',
19
+ 'EVIDENCE.md',
20
+ 'evidence.jsonl',
21
+ 'HANDOFF.md'
22
+ ];
23
+ const EVIDENCE_KINDS = new Set(['test-log', 'command-log', 'diff-summary', 'screenshot', 'note']);
24
+ const EVIDENCE_RESULTS = new Set(['passed', 'failed', 'blocked', 'unknown']);
25
+ const EVIDENCE_VISIBILITIES = new Set(['public', 'private']);
26
+ function validateTaskCapsule(projectRoot, taskId, options = {}) {
27
+ const level = options.level ?? 'draft';
28
+ const task = findTask(projectRoot, taskId);
29
+ if (!task) {
30
+ return {
31
+ schemaVersion: 'hadara.harness.validate.v1',
32
+ command: 'harness.validate',
33
+ ok: false,
34
+ level,
35
+ task: { id: taskId, title: '', capsule: '' },
36
+ checkedFiles: [],
37
+ issues: [
38
+ {
39
+ severity: 'error',
40
+ code: 'TASK_NOT_FOUND',
41
+ message: `Task Capsule not found: ${taskId}`
42
+ }
43
+ ]
44
+ };
45
+ }
46
+ const issues = [];
47
+ const checkedFiles = [];
48
+ for (const fileName of REQUIRED_TASK_FILES) {
49
+ const filePath = node_path_1.default.join(task.dir, fileName);
50
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, filePath));
51
+ checkedFiles.push(relativePath);
52
+ if (!node_fs_1.default.existsSync(filePath)) {
53
+ issues.push({
54
+ severity: 'error',
55
+ code: 'MISSING_TASK_FILE',
56
+ message: `Required Task Capsule file is missing: ${fileName}`,
57
+ path: relativePath
58
+ });
59
+ }
60
+ }
61
+ validateTaskMarkdown(projectRoot, task, issues);
62
+ validateCapsuleFormatMarkdown(projectRoot, task, issues);
63
+ validateEvidenceMarkdown(projectRoot, task, issues);
64
+ validateEvidenceIndex(projectRoot, task, issues);
65
+ if (level === 'done') {
66
+ validateDoneLevel(projectRoot, task, issues, checkedFiles);
67
+ }
68
+ return {
69
+ schemaVersion: 'hadara.harness.validate.v1',
70
+ command: 'harness.validate',
71
+ ok: !issues.some((issue) => issue.severity === 'error'),
72
+ level,
73
+ task: {
74
+ id: task.id,
75
+ title: task.title,
76
+ capsule: toPortablePath(node_path_1.default.relative(projectRoot, task.dir))
77
+ },
78
+ checkedFiles,
79
+ issues
80
+ };
81
+ }
82
+ function findTask(projectRoot, taskId) {
83
+ return (0, task_capsule_1.listTaskCapsules)(projectRoot).find((task) => task.id === taskId);
84
+ }
85
+ function validateTaskMarkdown(projectRoot, task, issues) {
86
+ const taskPath = node_path_1.default.join(task.dir, 'TASK.md');
87
+ if (!node_fs_1.default.existsSync(taskPath))
88
+ return;
89
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, taskPath));
90
+ const content = node_fs_1.default.readFileSync(taskPath, 'utf8');
91
+ for (const heading of ['## Goal', '## Scope', '## Out of Scope', '## Status']) {
92
+ if (!content.includes(heading)) {
93
+ issues.push({
94
+ severity: 'error',
95
+ code: 'TASK_SECTION_MISSING',
96
+ message: `TASK.md is missing required section: ${heading}`,
97
+ path: relativePath
98
+ });
99
+ }
100
+ }
101
+ }
102
+ function validateCapsuleFormatMarkdown(projectRoot, task, issues) {
103
+ validateMarkdownFile(projectRoot, task, issues, 'ACCEPTANCE.md', [
104
+ { code: 'ACCEPTANCE_HEADING_INVALID', anyText: ['# Acceptance Criteria'] },
105
+ { code: 'ACCEPTANCE_CHECKLIST_MISSING', anyText: ['- [ ]', '- [x]'] }
106
+ ]);
107
+ validateMarkdownFile(projectRoot, task, issues, 'FILES.md', [
108
+ { code: 'FILES_TABLE_INVALID', anyText: ['| Path | Action | Reason |'] },
109
+ { code: 'FILES_TABLE_INVALID', anyText: ['|---|---|---|'] }
110
+ ]);
111
+ validateMarkdownFile(projectRoot, task, issues, 'TESTS.md', [
112
+ { code: 'TESTS_SECTION_MISSING', anyText: ['## Required'] },
113
+ { code: 'TESTS_SECTION_MISSING', anyText: ['## Optional'] }
114
+ ]);
115
+ validateMarkdownFile(projectRoot, task, issues, 'RISKS.md', [
116
+ { code: 'RISKS_TABLE_INVALID', anyText: ['| Risk | Mitigation |'] },
117
+ { code: 'RISKS_TABLE_INVALID', anyText: ['|---|---|'] }
118
+ ]);
119
+ validateMarkdownFile(projectRoot, task, issues, 'HANDOFF.md', [
120
+ { code: 'HANDOFF_SECTION_MISSING', anyText: ['## Last Completed'] },
121
+ { code: 'HANDOFF_SECTION_MISSING', anyText: ['## Next Recommended Step'] }
122
+ ]);
123
+ }
124
+ function validateMarkdownFile(projectRoot, task, issues, fileName, checks) {
125
+ const filePath = node_path_1.default.join(task.dir, fileName);
126
+ if (!node_fs_1.default.existsSync(filePath))
127
+ return;
128
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, filePath));
129
+ const content = node_fs_1.default.readFileSync(filePath, 'utf8');
130
+ for (const check of checks) {
131
+ if (!check.anyText.some((text) => content.includes(text))) {
132
+ issues.push({
133
+ severity: 'error',
134
+ code: check.code,
135
+ message: `${fileName} is missing standard Task Capsule format marker: ${check.anyText.join(' or ')}`,
136
+ path: relativePath
137
+ });
138
+ }
139
+ }
140
+ }
141
+ function validateEvidenceMarkdown(projectRoot, task, issues) {
142
+ const evidencePath = node_path_1.default.join(task.dir, 'EVIDENCE.md');
143
+ if (!node_fs_1.default.existsSync(evidencePath))
144
+ return;
145
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, evidencePath));
146
+ const lines = node_fs_1.default.readFileSync(evidencePath, 'utf8').split(/\r?\n/);
147
+ const headerIndex = lines.findIndex((line) => line.trim() === '| Time | Kind | Summary | Result |');
148
+ if (headerIndex < 0 || lines[headerIndex + 1]?.trim() !== '|---|---|---|---|') {
149
+ issues.push({
150
+ severity: 'error',
151
+ code: 'EVIDENCE_TABLE_INVALID',
152
+ message: 'EVIDENCE.md must contain the standard evidence table header.',
153
+ path: relativePath
154
+ });
155
+ }
156
+ }
157
+ function validateEvidenceIndex(projectRoot, task, issues) {
158
+ const indexPath = node_path_1.default.join(task.dir, 'evidence.jsonl');
159
+ if (!node_fs_1.default.existsSync(indexPath))
160
+ return;
161
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, indexPath));
162
+ const content = node_fs_1.default.readFileSync(indexPath, 'utf8').trim();
163
+ if (!content)
164
+ return;
165
+ content.split(/\r?\n/).forEach((line, index) => {
166
+ try {
167
+ const record = JSON.parse(line);
168
+ if (record.schemaVersion !== 'hadara.evidence.v1') {
169
+ issues.push({
170
+ severity: 'error',
171
+ code: 'EVIDENCE_INDEX_SCHEMA_INVALID',
172
+ message: `evidence.jsonl line ${index + 1} has an unsupported schemaVersion.`,
173
+ path: relativePath
174
+ });
175
+ }
176
+ if (record.taskId !== task.id || typeof record.kind !== 'string' || typeof record.result !== 'string') {
177
+ issues.push({
178
+ severity: 'error',
179
+ code: 'EVIDENCE_INDEX_RECORD_INVALID',
180
+ message: `evidence.jsonl line ${index + 1} is missing required evidence fields.`,
181
+ path: relativePath
182
+ });
183
+ }
184
+ if (typeof record.time !== 'string' || !record.time.trim()) {
185
+ issues.push({
186
+ severity: 'error',
187
+ code: 'EVIDENCE_INDEX_TIME_MISSING',
188
+ message: `evidence.jsonl line ${index + 1} is missing required time.`,
189
+ path: relativePath
190
+ });
191
+ }
192
+ if (typeof record.summary !== 'string' || !record.summary.trim()) {
193
+ issues.push({
194
+ severity: 'error',
195
+ code: 'EVIDENCE_INDEX_SUMMARY_MISSING',
196
+ message: `evidence.jsonl line ${index + 1} is missing required summary.`,
197
+ path: relativePath
198
+ });
199
+ }
200
+ if (typeof record.visibility !== 'string') {
201
+ issues.push({
202
+ severity: 'error',
203
+ code: 'EVIDENCE_INDEX_VISIBILITY_MISSING',
204
+ message: `evidence.jsonl line ${index + 1} is missing required visibility.`,
205
+ path: relativePath
206
+ });
207
+ }
208
+ if ((typeof record.kind === 'string' && !EVIDENCE_KINDS.has(record.kind)) ||
209
+ (typeof record.result === 'string' && !EVIDENCE_RESULTS.has(record.result)) ||
210
+ (typeof record.visibility === 'string' && !EVIDENCE_VISIBILITIES.has(record.visibility))) {
211
+ issues.push({
212
+ severity: 'error',
213
+ code: 'EVIDENCE_INDEX_ENUM_INVALID',
214
+ message: `evidence.jsonl line ${index + 1} has an unsupported evidence enum value.`,
215
+ path: relativePath
216
+ });
217
+ }
218
+ }
219
+ catch {
220
+ issues.push({
221
+ severity: 'error',
222
+ code: 'EVIDENCE_INDEX_JSON_INVALID',
223
+ message: `evidence.jsonl line ${index + 1} is not valid JSON.`,
224
+ path: relativePath
225
+ });
226
+ }
227
+ });
228
+ }
229
+ function validateDoneLevel(projectRoot, task, issues, checkedFiles) {
230
+ validateTaskStatusDone(projectRoot, task, issues);
231
+ validateDoneLevelScaffoldContent(projectRoot, task, issues);
232
+ validateAcceptanceDone(projectRoot, task, issues);
233
+ validateEvidenceMarkdownSingleTable(projectRoot, task, issues);
234
+ validateEvidenceIndexHasRecords(projectRoot, task, issues);
235
+ validateHandoffDone(projectRoot, task, issues);
236
+ validateTaskBoardDone(projectRoot, task, issues, checkedFiles);
237
+ }
238
+ function validateTaskStatusDone(projectRoot, task, issues) {
239
+ const taskPath = node_path_1.default.join(task.dir, 'TASK.md');
240
+ if (!node_fs_1.default.existsSync(taskPath))
241
+ return;
242
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, taskPath));
243
+ const status = readSectionBody(taskPath, '## Status');
244
+ if (!/^Done\b/i.test(status.trim())) {
245
+ issues.push({
246
+ severity: 'error',
247
+ code: 'TASK_STATUS_NOT_DONE',
248
+ message: 'Done-level validation requires TASK.md status to be Done.',
249
+ path: relativePath
250
+ });
251
+ }
252
+ }
253
+ function validateAcceptanceDone(projectRoot, task, issues) {
254
+ const acceptancePath = node_path_1.default.join(task.dir, 'ACCEPTANCE.md');
255
+ if (!node_fs_1.default.existsSync(acceptancePath))
256
+ return;
257
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, acceptancePath));
258
+ const checklistLines = node_fs_1.default
259
+ .readFileSync(acceptancePath, 'utf8')
260
+ .split(/\r?\n/)
261
+ .filter((line) => /^-\s+\[[ xX]\]/.test(line.trim()));
262
+ if (checklistLines.length === 0 || checklistLines.some((line) => /^-\s+\[\s\]/.test(line.trim()))) {
263
+ issues.push({
264
+ severity: 'error',
265
+ code: 'ACCEPTANCE_INCOMPLETE',
266
+ message: 'Done-level validation requires all acceptance checkboxes to be checked.',
267
+ path: relativePath
268
+ });
269
+ }
270
+ }
271
+ function validateDoneLevelScaffoldContent(projectRoot, task, issues) {
272
+ const checks = [
273
+ {
274
+ fileName: 'TASK.md',
275
+ code: 'TASK_SCAFFOLD_PLACEHOLDER',
276
+ message: 'Done-level validation requires TASK.md Goal, Scope, and Out of Scope to replace scaffold placeholders.'
277
+ },
278
+ {
279
+ fileName: 'PLAN.md',
280
+ code: 'PLAN_SCAFFOLD_UNCHANGED',
281
+ message: 'Done-level validation requires PLAN.md to replace the default scaffold plan.'
282
+ },
283
+ {
284
+ fileName: 'CONTEXT.md',
285
+ code: 'CONTEXT_SCAFFOLD_UNCHANGED',
286
+ message: 'Done-level validation requires CONTEXT.md to contain task-specific context.'
287
+ },
288
+ {
289
+ fileName: 'FILES.md',
290
+ code: 'FILES_SCAFFOLD_UNCHANGED',
291
+ message: 'Done-level validation requires FILES.md to list touched files or explain that no files changed.'
292
+ },
293
+ {
294
+ fileName: 'ACCEPTANCE.md',
295
+ code: 'ACCEPTANCE_SCAFFOLD_UNCHANGED',
296
+ message: 'Done-level validation requires ACCEPTANCE.md to replace the default checklist items.'
297
+ },
298
+ {
299
+ fileName: 'TESTS.md',
300
+ code: 'TESTS_SCAFFOLD_UNCHANGED',
301
+ message: 'Done-level validation requires TESTS.md to replace the default npm test/npm run check scaffold.'
302
+ },
303
+ {
304
+ fileName: 'RISKS.md',
305
+ code: 'RISKS_SCAFFOLD_UNCHANGED',
306
+ message: 'Done-level validation requires RISKS.md to list risks or record why no material risks remain.'
307
+ },
308
+ {
309
+ fileName: 'DECISIONS.md',
310
+ code: 'DECISIONS_SCAFFOLD_UNCHANGED',
311
+ message: 'Done-level validation requires DECISIONS.md to replace the default scaffold note.'
312
+ },
313
+ {
314
+ fileName: 'EVIDENCE.md',
315
+ code: 'EVIDENCE_SCAFFOLD_UNCHANGED',
316
+ message: 'Done-level validation requires EVIDENCE.md to contain at least one evidence table row.'
317
+ }
318
+ ];
319
+ for (const check of checks) {
320
+ const filePath = node_path_1.default.join(task.dir, check.fileName);
321
+ if (!node_fs_1.default.existsSync(filePath))
322
+ continue;
323
+ const content = node_fs_1.default.readFileSync(filePath, 'utf8');
324
+ if ((0, task_capsule_1.isTaskCapsuleScaffoldContent)(task, check.fileName, content)) {
325
+ issues.push({
326
+ severity: 'error',
327
+ code: check.code,
328
+ message: check.message,
329
+ path: toPortablePath(node_path_1.default.relative(projectRoot, filePath))
330
+ });
331
+ }
332
+ }
333
+ }
334
+ function validateEvidenceIndexHasRecords(projectRoot, task, issues) {
335
+ const indexPath = node_path_1.default.join(task.dir, 'evidence.jsonl');
336
+ if (!node_fs_1.default.existsSync(indexPath))
337
+ return;
338
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, indexPath));
339
+ if (!node_fs_1.default.readFileSync(indexPath, 'utf8').trim()) {
340
+ issues.push({
341
+ severity: 'error',
342
+ code: 'EVIDENCE_REQUIRED',
343
+ message: 'Done-level validation requires at least one evidence.jsonl record.',
344
+ path: relativePath
345
+ });
346
+ }
347
+ }
348
+ function validateEvidenceMarkdownSingleTable(projectRoot, task, issues) {
349
+ const evidencePath = node_path_1.default.join(task.dir, 'EVIDENCE.md');
350
+ if (!node_fs_1.default.existsSync(evidencePath))
351
+ return;
352
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, evidencePath));
353
+ const lines = node_fs_1.default.readFileSync(evidencePath, 'utf8').split(/\r?\n/);
354
+ const tableHeaderCount = lines.filter((line, index) => line.trim() === '| Time | Kind | Summary | Result |' && lines[index + 1]?.trim() === '|---|---|---|---|').length;
355
+ if (tableHeaderCount > 1) {
356
+ issues.push({
357
+ severity: 'error',
358
+ code: 'EVIDENCE_TABLE_DUPLICATE_HEADER',
359
+ message: `Done-level validation requires EVIDENCE.md to contain exactly one evidence table header; found ${tableHeaderCount}.`,
360
+ path: relativePath
361
+ });
362
+ }
363
+ }
364
+ function validateHandoffDone(projectRoot, task, issues) {
365
+ const handoffPath = node_path_1.default.join(task.dir, 'HANDOFF.md');
366
+ if (!node_fs_1.default.existsSync(handoffPath))
367
+ return;
368
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, handoffPath));
369
+ const lastCompleted = readSectionBody(handoffPath, '## Last Completed').trim();
370
+ const nextStep = readSectionBody(handoffPath, '## Next Recommended Step').trim();
371
+ if (isPlaceholderSection(lastCompleted) || isPlaceholderSection(nextStep)) {
372
+ issues.push({
373
+ severity: 'error',
374
+ code: 'HANDOFF_PLACEHOLDER',
375
+ message: 'Done-level validation requires non-placeholder handoff sections.',
376
+ path: relativePath
377
+ });
378
+ }
379
+ }
380
+ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
381
+ const taskBoardPath = node_path_1.default.join(projectRoot, 'docs', 'TASK_BOARD.md');
382
+ const relativePath = toPortablePath(node_path_1.default.relative(projectRoot, taskBoardPath));
383
+ checkedFiles.push(relativePath);
384
+ if (!node_fs_1.default.existsSync(taskBoardPath)) {
385
+ issues.push({
386
+ severity: 'error',
387
+ code: 'TASK_BOARD_MISSING',
388
+ message: 'Done-level validation requires docs/TASK_BOARD.md to contain the completed task row.',
389
+ path: relativePath
390
+ });
391
+ return;
392
+ }
393
+ const rows = parseTaskBoardRows(node_fs_1.default.readFileSync(taskBoardPath, 'utf8')).filter((row) => row.id === task.id);
394
+ if (rows.length === 0) {
395
+ issues.push({
396
+ severity: 'error',
397
+ code: 'TASK_BOARD_ROW_MISSING',
398
+ message: `Done-level validation requires docs/TASK_BOARD.md to contain exactly one row for ${task.id}.`,
399
+ path: relativePath
400
+ });
401
+ return;
402
+ }
403
+ if (rows.length > 1) {
404
+ issues.push({
405
+ severity: 'error',
406
+ code: 'TASK_BOARD_ROW_DUPLICATE',
407
+ message: `docs/TASK_BOARD.md contains ${rows.length} rows for ${task.id}; expected exactly one.`,
408
+ path: relativePath
409
+ });
410
+ return;
411
+ }
412
+ const row = rows[0];
413
+ const expectedCapsule = toPortablePath(node_path_1.default.relative(projectRoot, task.dir));
414
+ if (row.status !== 'Done') {
415
+ issues.push({
416
+ severity: 'error',
417
+ code: 'TASK_BOARD_STATUS_NOT_DONE',
418
+ message: `Done-level validation requires docs/TASK_BOARD.md status for ${task.id} to be Done.`,
419
+ path: relativePath
420
+ });
421
+ }
422
+ if (row.capsule !== expectedCapsule) {
423
+ issues.push({
424
+ severity: 'error',
425
+ code: 'TASK_BOARD_CAPSULE_MISMATCH',
426
+ message: `docs/TASK_BOARD.md capsule for ${task.id} is ${row.capsule || '(empty)'}, expected ${expectedCapsule}.`,
427
+ path: relativePath
428
+ });
429
+ }
430
+ }
431
+ function parseTaskBoardRows(content) {
432
+ return content
433
+ .split(/\r?\n/)
434
+ .map((line) => line.trim())
435
+ .filter((line) => /^\|\s*T-\d{4}\s*\|/.test(line))
436
+ .map((line) => {
437
+ const cells = line
438
+ .slice(1, line.endsWith('|') ? -1 : undefined)
439
+ .split('|')
440
+ .map((cell) => cell.trim());
441
+ return {
442
+ id: cells[0] ?? '',
443
+ title: cells[1] ?? '',
444
+ status: cells[2] ?? '',
445
+ capsule: cells[3] ?? '',
446
+ notes: cells[4] ?? ''
447
+ };
448
+ });
449
+ }
450
+ function readSectionBody(filePath, heading) {
451
+ const content = node_fs_1.default.readFileSync(filePath, 'utf8');
452
+ const start = content.indexOf(heading);
453
+ if (start < 0)
454
+ return '';
455
+ const afterHeading = content.slice(start + heading.length);
456
+ const nextHeading = afterHeading.search(/\n##\s+/);
457
+ return nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
458
+ }
459
+ function isPlaceholderSection(value) {
460
+ const normalized = value.trim();
461
+ return normalized.length === 0 || /^TBD\.?$/i.test(normalized);
462
+ }
463
+ function toPortablePath(value) {
464
+ return value.split(node_path_1.default.sep).join('/');
465
+ }
@@ -0,0 +1,104 @@
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.detectHermesContext = detectHermesContext;
7
+ exports.buildHadaraContextContent = buildHadaraContextContent;
8
+ exports.createContextExportReport = createContextExportReport;
9
+ exports.exportHadaraContext = exportHadaraContext;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const fs_1 = require("../core/fs");
13
+ const HERMES_CONTEXT_FILES = ['AGENTS.md', '.hermes.md', 'HERMES.md', 'CLAUDE.md', '.cursorrules'];
14
+ const CONTEXT_OUTPUT_PATH = '.hadara/context/HADARA_CONTEXT.md';
15
+ function detectHermesContext(projectRoot) {
16
+ const found = [];
17
+ const missing = [];
18
+ for (const file of HERMES_CONTEXT_FILES) {
19
+ const filePath = node_path_1.default.join(projectRoot, file);
20
+ if (node_fs_1.default.existsSync(filePath))
21
+ found.push(file);
22
+ else
23
+ missing.push(file);
24
+ }
25
+ return { found, missing };
26
+ }
27
+ function buildHadaraContextContent(projectRoot) {
28
+ const sourceFiles = [
29
+ 'docs/PROJECT_STATE.md',
30
+ 'docs/AGENT_HANDOFF.md',
31
+ 'docs/IMPLEMENTATION_SOP.md',
32
+ 'docs/TASK_BOARD.md',
33
+ 'docs/ROADMAP.md',
34
+ 'docs/DEVELOPMENT_SLICES.md',
35
+ 'docs/CLI_JSON_CONTRACT.md',
36
+ 'docs/MCP_BRIDGE_CONTRACT.md',
37
+ 'docs/MCP_EVIDENCE_ATTACH_CONTRACT.md',
38
+ 'docs/ARCHITECTURE.md',
39
+ 'docs/SECURITY_MODEL.md',
40
+ 'docs/TEST_STRATEGY.md'
41
+ ];
42
+ const sections = sourceFiles.map((relativePath) => {
43
+ const content = (0, fs_1.readTextIfExists)(node_path_1.default.join(projectRoot, relativePath)) ?? '_Missing._';
44
+ return `## ${relativePath}\n\n${content}`;
45
+ });
46
+ const output = `# HADARA_CONTEXT
47
+
48
+ This file is generated by HADARA for Hermes Agent and external agent-harness compatibility.
49
+ Do not treat this file as the source of truth; prefer HADARA CLI JSON, MCP read tools, and the referenced docs.
50
+ Follow docs/IMPLEMENTATION_SOP.md for implementation, validation, and session-end procedure.
51
+
52
+ Agents must:
53
+ 1. Preserve the portable/project store boundary.
54
+ 2. Work inside the active Task Capsule.
55
+ 3. Attach evidence before marking work complete.
56
+ 4. Update AGENT_HANDOFF.md before stopping.
57
+ 5. Respect policy decisions for shell/file/git operations.
58
+ 6. Treat AGENT_HANDOFF.md as compact current state and follow its Historical Index for older history.
59
+ 7. Prefer stable HADARA read surfaces before scraping raw files: use hadara.project.state.read or hadara status --json for project state.
60
+ 8. Use hadara.task.list and hadara.task.read, or hadara task list --json and hadara task show <task-id> --json, for task state.
61
+ 9. Use hadara.handoff.read, hadara.policy.evaluate, and hadara.harness.validate, or their CLI JSON equivalents, for handoff, policy, and validation state.
62
+ 10. Use hadara.context.export for in-memory MCP context export; only hadara hermes export-context writes .hadara/context/HADARA_CONTEXT.md.
63
+ 11. Treat MCP default mode as read-only; do not assume MCP task mutation, file writes, shell execution, or release/package execution exists.
64
+ 12. If MCP is unavailable, fall back to CLI JSON commands and then to repository documents.
65
+ 13. Respect the single active agent/session model; do not assume queues, worker lanes, or multi-agent concurrent execution.
66
+ 14. Treat policy.evaluate as policy evaluation only, not MCP execution authorization.
67
+ 15. Treat MCP evidence attachment as disabled by default; it requires explicit server opt-in, per-call approval metadata, and private audit logging.
68
+
69
+ ${sections.join('\n\n---\n\n')}
70
+ `;
71
+ return output;
72
+ }
73
+ function createContextExportReport(projectRoot, options = {}) {
74
+ const format = options.format ?? 'markdown';
75
+ const markdown = buildHadaraContextContent(projectRoot);
76
+ const content = format === 'json' ? JSON.stringify({ schemaVersion: 'hadara.context.content.v1', summaryOnly: options.summaryOnly === true, markdown }) : markdown;
77
+ const issues = options.summaryOnly === true
78
+ ? [
79
+ {
80
+ severity: 'warning',
81
+ code: 'SUMMARY_ONLY_NOT_IMPLEMENTED',
82
+ message: 'summaryOnly is accepted for forward compatibility but currently returns the full context.'
83
+ }
84
+ ]
85
+ : [];
86
+ return {
87
+ schemaVersion: 'hadara.context.export.v1',
88
+ command: 'context.export',
89
+ ok: true,
90
+ format,
91
+ mode: 'memory',
92
+ content,
93
+ contextPath: null,
94
+ wouldWritePath: CONTEXT_OUTPUT_PATH,
95
+ issues
96
+ };
97
+ }
98
+ function exportHadaraContext(projectRoot) {
99
+ const contextDir = node_path_1.default.join(projectRoot, '.hadara', 'context');
100
+ (0, fs_1.ensureDir)(contextDir);
101
+ const outputPath = node_path_1.default.join(projectRoot, CONTEXT_OUTPUT_PATH);
102
+ node_fs_1.default.writeFileSync(outputPath, buildHadaraContextContent(projectRoot), 'utf8');
103
+ return outputPath;
104
+ }
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./core/paths"), exports);
18
+ __exportStar(require("./core/redaction"), exports);
19
+ __exportStar(require("./core/workspace"), exports);
20
+ __exportStar(require("./cli/args"), exports);
21
+ __exportStar(require("./providers/provider-contract"), exports);
22
+ __exportStar(require("./providers/mock-provider"), exports);
23
+ __exportStar(require("./providers/provider-preparation"), exports);
24
+ __exportStar(require("./task/task-capsule"), exports);
25
+ __exportStar(require("./evidence/evidence"), exports);
26
+ __exportStar(require("./handoff/handoff"), exports);
27
+ __exportStar(require("./tools/fake-shell"), exports);
28
+ __exportStar(require("./agent/loop"), exports);
29
+ __exportStar(require("./agent/evidence"), exports);