hadara 0.3.0-rc.1 → 0.3.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -18
- package/dist/cli/init.js +70 -8
- package/dist/core/fs.js +62 -0
- package/dist/core/schema.js +2 -0
- package/dist/harness/validate.js +232 -19
- package/dist/schemas/docs-required-reading.schema.json +6 -0
- package/dist/schemas/harness-validate.schema.json +58 -0
- package/dist/schemas/schema-index.json +7 -0
- package/dist/schemas/task-close.schema.json +19 -1
- package/dist/schemas/task-ready.schema.json +30 -1
- package/dist/services/docs-cleanup.js +25 -4
- package/dist/services/docs-registry.js +47 -1
- package/dist/services/protocol-migration.js +109 -17
- package/dist/task/task-close.js +10 -1
- package/dist/task/task-finish.js +53 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<img alt="Published npm release" src="https://img.shields.io/badge/npm-0.3.0--rc.
|
|
8
|
+
<img alt="Published npm release" src="https://img.shields.io/badge/npm-0.3.0--rc.1-blue">
|
|
9
|
+
<img alt="Source candidate" src="https://img.shields.io/badge/source-0.3.0--rc.2-orange">
|
|
9
10
|
<img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D22-brightgreen">
|
|
10
11
|
<img alt="License" src="https://img.shields.io/badge/license-MIT-lightgrey">
|
|
11
12
|
</p>
|
|
@@ -20,13 +21,19 @@ This repository is both the HADARA source checkout and the HADARA protocol works
|
|
|
20
21
|
|
|
21
22
|
## Release Status
|
|
22
23
|
|
|
23
|
-
Current
|
|
24
|
+
Current published npm release:
|
|
24
25
|
|
|
25
26
|
```text
|
|
26
27
|
hadara@0.3.0-rc.1
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
Current
|
|
30
|
+
Current source release candidate:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
hadara@0.3.0-rc.2
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Previous published npm release:
|
|
30
37
|
|
|
31
38
|
```text
|
|
32
39
|
hadara@0.3.0-rc.0
|
|
@@ -34,7 +41,7 @@ hadara@0.3.0-rc.0
|
|
|
34
41
|
|
|
35
42
|
The 0.3.0 line is the Phase 7 Surface Refactor. It organizes HADARA's existing task, evidence, proof, lifecycle, release, and document surfaces so agents can distinguish primary lifecycle commands, diagnostics, advanced surfaces, canonical documents, historical documents, and safe Markdown update boundaries.
|
|
36
43
|
|
|
37
|
-
Phase 7.x labels are internal implementation phases, not npm release-candidate labels. Publishing
|
|
44
|
+
Phase 7.x labels are internal implementation phases, not npm release-candidate labels. Publishing any later release still requires the approval-gated release path and explicit operator confirmation.
|
|
38
45
|
|
|
39
46
|
| Surface | Status |
|
|
40
47
|
|---|---|
|
|
@@ -42,8 +49,9 @@ Phase 7.x labels are internal implementation phases, not npm release-candidate l
|
|
|
42
49
|
| `hadara@0.2.0-rc.1` | Previous published npm RC. |
|
|
43
50
|
| `hadara@0.2.0-rc.2` | Previous published npm RC. |
|
|
44
51
|
| `hadara@0.2.0-rc.3` | Previous published npm RC. |
|
|
45
|
-
| `hadara@0.3.0-rc.0` |
|
|
46
|
-
| `hadara@0.3.0-rc.1` | Current
|
|
52
|
+
| `hadara@0.3.0-rc.0` | Previous published npm RC; package metadata lacks the intended discovery fields. |
|
|
53
|
+
| `hadara@0.3.0-rc.1` | Current published npm RC; T-0301 publish evidence verified `npm view` returned `0.3.0-rc.1`. |
|
|
54
|
+
| `hadara@0.3.0-rc.2` | Current source candidate prepared by T-0310; publish remains approval-gated. |
|
|
47
55
|
| GitHub Release | Secondary target, approval-gated. |
|
|
48
56
|
| Docker image | Deferred. |
|
|
49
57
|
| PyPI/Python package | `hadara==0.2.0rc1` published preview bridge. |
|
|
@@ -55,10 +63,10 @@ No release command should publish, create a GitHub Release, build Docker images,
|
|
|
55
63
|
|
|
56
64
|
Requires Node.js 22.
|
|
57
65
|
|
|
58
|
-
Install the latest published RC
|
|
66
|
+
Install the latest published RC:
|
|
59
67
|
|
|
60
68
|
```bash
|
|
61
|
-
npm install -g hadara@0.3.0-rc.
|
|
69
|
+
npm install -g hadara@0.3.0-rc.1
|
|
62
70
|
hadara help
|
|
63
71
|
hadara doctor --json
|
|
64
72
|
```
|
|
@@ -66,16 +74,8 @@ hadara doctor --json
|
|
|
66
74
|
Run without a global install:
|
|
67
75
|
|
|
68
76
|
```bash
|
|
69
|
-
npx hadara@0.3.0-rc.
|
|
70
|
-
npx hadara@0.3.0-rc.
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
After the T-0301 npm publish helper verifies `hadara@0.3.0-rc.1` on the registry, install the new RC:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npm install -g hadara@0.3.0-rc.1
|
|
77
|
-
hadara help
|
|
78
|
-
hadara doctor --json
|
|
77
|
+
npx hadara@0.3.0-rc.1 help
|
|
78
|
+
npx hadara@0.3.0-rc.1 doctor --json
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
## What HADARA Gives You
|
package/dist/cli/init.js
CHANGED
|
@@ -140,6 +140,7 @@ function createGeneratedScaffoldFiles(profile) {
|
|
|
140
140
|
const spec = INIT_PROFILE_SPECS[profile];
|
|
141
141
|
const docsRegistry = (0, docs_registry_1.createSeedDocumentRegistry)(profile);
|
|
142
142
|
const files = [
|
|
143
|
+
{ path: '.hadara/context/HADARA_CONTEXT.md', content: (0, docs_registry_1.createHadaraContextDoc)(profile) },
|
|
143
144
|
{ path: '.hadara/docs-registry.json', content: (0, docs_registry_1.registryJson)(docsRegistry) },
|
|
144
145
|
{ path: 'docs/DOC_REGISTRY.md', content: (0, docs_registry_1.renderDocRegistryMarkdown)(docsRegistry) },
|
|
145
146
|
{ path: 'docs/PROJECT_STATE.md', content: createProjectStateDoc(profile) },
|
|
@@ -169,7 +170,7 @@ function createGeneratedScaffoldFiles(profile) {
|
|
|
169
170
|
function createInitDoctorReport(projectRoot) {
|
|
170
171
|
const issues = [];
|
|
171
172
|
const actions = [];
|
|
172
|
-
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'];
|
|
173
|
+
const requiredCore = ['AGENTS.md', '.gitignore', '.hadara/context/HADARA_CONTEXT.md', 'docs/PROJECT_STATE.md', 'docs/AGENT_HANDOFF.md', 'docs/TASK_BOARD.md', 'docs/IMPLEMENTATION_SOP.md', 'docs/TASK_WORKFLOW_COMMANDS.md'];
|
|
173
174
|
for (const relativePath of requiredCore) {
|
|
174
175
|
if (!node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, relativePath))) {
|
|
175
176
|
issues.push({ severity: 'error', code: 'INIT_CORE_DOC_MISSING', path: relativePath, message: `${relativePath} is missing from the init scaffold.` });
|
|
@@ -934,6 +935,7 @@ function createArchitectureDoc(profile) {
|
|
|
934
935
|
}
|
|
935
936
|
function createImplementationSopDoc(spec) {
|
|
936
937
|
const sessionStart = [
|
|
938
|
+
'Read `.hadara/context/HADARA_CONTEXT.md` as the compact project-local context anchor.',
|
|
937
939
|
'Read `docs/PROJECT_STATE.md`.',
|
|
938
940
|
'Read `docs/AGENT_HANDOFF.md`.',
|
|
939
941
|
'Read `docs/TASK_BOARD.md`.',
|
|
@@ -949,6 +951,7 @@ function createImplementationSopDoc(spec) {
|
|
|
949
951
|
'Propose or choose the smallest useful implementation slice.'
|
|
950
952
|
];
|
|
951
953
|
const requiredReadingRows = [
|
|
954
|
+
['`.hadara/context/HADARA_CONTEXT.md`', 'Every session', 'Compact project-local context anchor and read-routing guide.'],
|
|
952
955
|
['`docs/PROJECT_STATE.md`', 'Every session', 'Current product state and source-of-truth map.'],
|
|
953
956
|
['`docs/AGENT_HANDOFF.md`', 'Every session', 'Compact handoff and next recommended step.'],
|
|
954
957
|
['`docs/TASK_BOARD.md`', 'Every session', 'Work queue and task status.'],
|
|
@@ -1022,6 +1025,20 @@ This project was initialized with the \`${spec.profile}\` HADARA profile.
|
|
|
1022
1025
|
|
|
1023
1026
|
${numberedList(sessionStart)}
|
|
1024
1027
|
|
|
1028
|
+
## Required Reading Tiers
|
|
1029
|
+
|
|
1030
|
+
Use semantic tiers to keep session startup compact and deterministic:
|
|
1031
|
+
|
|
1032
|
+
| Tier | Meaning | Default Read Behavior |
|
|
1033
|
+
|---|---|---|
|
|
1034
|
+
| \`current-state\` | Compact docs that establish live project state and route deeper reading, starting with \`.hadara/context/HADARA_CONTEXT.md\`. | Read first at session start or resume. |
|
|
1035
|
+
| \`task-work\` | Active Task Capsule docs, \`docs/TASK_BOARD.md\`, and \`docs/TASK_WORKFLOW_COMMANDS.md\`. | Read when selecting, implementing, finishing, closing, or auditing a task. |
|
|
1036
|
+
| \`conditional-reference\` | Architecture, security, roadmap, validation, release, or project-specific specs. | Read only when the task type, capsule, or Required Reading row condition applies. |
|
|
1037
|
+
| \`historical\` | Completed-task history, older validation records, and previous-state detail. | Never default required reading; read only when investigating history through the handoff Historical Index. |
|
|
1038
|
+
| \`excluded\` | Superseded, archived, local-only, or intentionally non-default material. | Never default required reading unless explicitly reclassified. |
|
|
1039
|
+
|
|
1040
|
+
\`.hadara/context/HADARA_CONTEXT.md\` is the current-state entry point and read-routing guide. Full historical review of \`docs/PROJECT_STATE.md\` is not mandatory every session; rely on compact current-state docs first and follow \`docs/AGENT_HANDOFF.md\` Historical Index only when older history matters. Historical and superseded docs are never default required reading.
|
|
1041
|
+
|
|
1025
1042
|
## Required Reading
|
|
1026
1043
|
|
|
1027
1044
|
${requiredReadingTable}
|
|
@@ -1062,6 +1079,22 @@ Prefer tables for repeated records and \`##\`/\`###\` headings for durable secti
|
|
|
1062
1079
|
3. Make the smallest coherent change that satisfies acceptance criteria.
|
|
1063
1080
|
4. Update task-local docs when scope changes.
|
|
1064
1081
|
|
|
1082
|
+
## Documentation Timing and Write Coordination
|
|
1083
|
+
|
|
1084
|
+
Do not defer all documentation until after implementation. Documentation is part of the work, not a post-work report.
|
|
1085
|
+
|
|
1086
|
+
Keep capsule docs current as work changes:
|
|
1087
|
+
|
|
1088
|
+
| Timing | Documents |
|
|
1089
|
+
|---|---|
|
|
1090
|
+
| Before execution | \`PLAN.md\` |
|
|
1091
|
+
| During execution | \`DECISIONS.md\`, \`RISKS.md\`, and \`FILES.md\` |
|
|
1092
|
+
| Immediately after validation | \`TESTS.md\` and \`EVIDENCE.md\` |
|
|
1093
|
+
| Before finish/ready/close | \`ACCEPTANCE.md\`, \`HANDOFF.md\`, and shared state docs |
|
|
1094
|
+
| Before close-source hash is captured | \`docs/TASK_BOARD.md\`, \`docs/PROJECT_STATE.md\`, \`docs/AGENT_HANDOFF.md\`, and roadmap/slice docs when applicable |
|
|
1095
|
+
|
|
1096
|
+
Parallelize read-only discovery, \`rg\`/file inspection, independent validation commands, package or registry metadata inspection, read-only diagnostics, and draft preparation before writes. Serialize same-file writes, evidence append, Task Capsule doc writes, Task Board writes, Project State writes, Agent Handoff writes, before-hash execute operations, \`task finish --execute\`, \`task close --execute\`, and release artifact or publish operations.
|
|
1097
|
+
|
|
1065
1098
|
## Standard Task Workflow Loop
|
|
1066
1099
|
|
|
1067
1100
|
The authoritative command semantics live in \`docs/TASK_WORKFLOW_COMMANDS.md\`. For ordinary implementation capsules, use this loop:
|
|
@@ -1276,6 +1309,10 @@ function createTaskWorkflowCommandsDoc() {
|
|
|
1276
1309
|
|
|
1277
1310
|
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.
|
|
1278
1311
|
|
|
1312
|
+
## Required Reading Tier
|
|
1313
|
+
|
|
1314
|
+
\`docs/TASK_WORKFLOW_COMMANDS.md\` is \`task-work\` required reading. Read it when selecting, implementing, finishing, closing, auditing, or changing task workflow commands; do not treat it as a historical archive or a replacement for current-state docs. Start from \`.hadara/context/HADARA_CONTEXT.md\` and compact state docs, then use this document for lifecycle command semantics.
|
|
1315
|
+
|
|
1279
1316
|
## Standard Task Loop
|
|
1280
1317
|
|
|
1281
1318
|
Use this loop for ordinary implementation capsules:
|
|
@@ -1318,6 +1355,14 @@ The close model has three separate phases: validation proves readiness, close re
|
|
|
1318
1355
|
|
|
1319
1356
|
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".
|
|
1320
1357
|
|
|
1358
|
+
## Documentation Timing and Write Coordination
|
|
1359
|
+
|
|
1360
|
+
Do not defer all documentation until after implementation. Keep \`PLAN.md\` current before execution; update \`DECISIONS.md\`, \`RISKS.md\`, and \`FILES.md\` during execution; update \`TESTS.md\` and \`EVIDENCE.md\` immediately after validation; update \`ACCEPTANCE.md\`, \`HANDOFF.md\`, and shared state docs before finish/ready/close; and update shared close-source docs before the close-source hash is captured.
|
|
1361
|
+
|
|
1362
|
+
Parallelize read-only discovery, \`rg\`/file inspection, independent validation commands, package or registry metadata inspection, read-only diagnostics, and draft preparation before writes.
|
|
1363
|
+
|
|
1364
|
+
Serialize same-file writes, evidence append, Task Capsule doc writes, Task Board writes, Project State writes, Agent Handoff writes, before-hash execute operations, \`task finish --execute\`, \`task close --execute\`, and release artifact or publish operations.
|
|
1365
|
+
|
|
1321
1366
|
## Command Semantics
|
|
1322
1367
|
|
|
1323
1368
|
| Command | Default Write Behavior | Notes |
|
|
@@ -1340,7 +1385,7 @@ Before close, finish all close-source edits: Task Capsule docs, acceptance/tests
|
|
|
1340
1385
|
- \`harness validate\` is a direct diagnostic for Task Capsule structure and done-level gates; it is not a replacement for close evidence.
|
|
1341
1386
|
- \`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.
|
|
1342
1387
|
- \`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.
|
|
1343
|
-
- \`task finish\` may update only the Task Capsule \`TASK.md\` status and matching \`docs/TASK_BOARD.md\`
|
|
1388
|
+
- \`task finish\` may update only the Task Capsule \`TASK.md\` status and the matching \`docs/TASK_BOARD.md\` row's command-owned cells: \`ID\`, \`Title\`, \`Status\`, and \`Capsule\`. It preserves human/mixed-owned \`Notes\` and any extra cells.
|
|
1344
1389
|
- \`task close\` may append only close evidence. It must not update status docs, Task Board rows, handoff, Project State, roadmap docs, or arbitrary evidence.
|
|
1345
1390
|
- 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.
|
|
1346
1391
|
- \`task audit-close\` is read-only and should be run after \`task close --execute --json\`.
|
|
@@ -1352,13 +1397,14 @@ Before close, finish all close-source edits: Task Capsule docs, acceptance/tests
|
|
|
1352
1397
|
}
|
|
1353
1398
|
function createAgentsDoc(spec) {
|
|
1354
1399
|
const requiredReadingRows = [
|
|
1355
|
-
['1', '
|
|
1356
|
-
['2', '`docs/
|
|
1357
|
-
['3', '`docs/
|
|
1358
|
-
['4', '`docs/
|
|
1359
|
-
['5', '`docs/
|
|
1400
|
+
['1', '`.hadara/context/HADARA_CONTEXT.md`', 'Every session', 'Compact project-local context anchor and read routing.'],
|
|
1401
|
+
['2', '`docs/PROJECT_STATE.md`', 'Every session', 'Current product and capability state.'],
|
|
1402
|
+
['3', '`docs/AGENT_HANDOFF.md`', 'Every session', 'Compact continuation state.'],
|
|
1403
|
+
['4', '`docs/TASK_BOARD.md`', 'Every session', 'Current task queue and status.'],
|
|
1404
|
+
['5', '`docs/IMPLEMENTATION_SOP.md`', 'Every session', 'Local workflow and required-reading registry.'],
|
|
1405
|
+
['6', '`docs/TASK_WORKFLOW_COMMANDS.md`', 'Starting, finishing, closing, auditing, or explaining task workflow commands', 'Standard task loop, dry-run boundaries, and command `ok` semantics.']
|
|
1360
1406
|
];
|
|
1361
|
-
let order =
|
|
1407
|
+
let order = 7;
|
|
1362
1408
|
if (spec.docs.architecture)
|
|
1363
1409
|
requiredReadingRows.push([String(order++), '`docs/ARCHITECTURE.md`', 'Architecture, component, or boundary work', 'Current system shape and ownership boundaries.']);
|
|
1364
1410
|
if (spec.docs.developmentSlices)
|
|
@@ -1379,6 +1425,8 @@ function createAgentsDoc(spec) {
|
|
|
1379
1425
|
['Task boundary', 'Keep work inside one Task Capsule whenever possible.', 'Active Task Capsule'],
|
|
1380
1426
|
['Task creation', 'If no suitable capsule exists, create one with `hadara task create <title>`.', '`docs/TASK_BOARD.md`'],
|
|
1381
1427
|
['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`'],
|
|
1428
|
+
['Documentation timing', 'Do not defer all documentation until after implementation; keep capsule docs current as work changes.', 'Task Capsule docs and shared state docs'],
|
|
1429
|
+
['Write coordination', 'Parallelize read-only discovery and independent validation; serialize evidence append, Task Capsule doc writes, shared state doc writes, before-hash executes, finish/close executes, and release/publish operations.', 'Task Capsule evidence'],
|
|
1382
1430
|
['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'],
|
|
1383
1431
|
['Safety', 'Do not execute dangerous commands without explicit user approval.', 'Task Capsule evidence'],
|
|
1384
1432
|
['Secrets', 'Do not write secrets, private logs, or machine-local state into committed files.', 'Changed-file review'],
|
|
@@ -1403,6 +1451,20 @@ ${requiredReadingRows.map(formatTableRow).join('\n')}
|
|
|
1403
1451
|
|
|
1404
1452
|
\`docs/AGENT_HANDOFF.md\` is compact current-state handoff, not full project history. Follow its Historical Index when older completed-task or validation history is needed.
|
|
1405
1453
|
|
|
1454
|
+
## Required Reading Tiers
|
|
1455
|
+
|
|
1456
|
+
Use semantic tiers to keep session startup compact:
|
|
1457
|
+
|
|
1458
|
+
| Tier | Meaning | Default Read Behavior |
|
|
1459
|
+
|---|---|---|
|
|
1460
|
+
| \`current-state\` | Compact docs that establish the live project state and route deeper reading. | Read first at session start or resume. |
|
|
1461
|
+
| \`task-work\` | Active Task Capsule docs and task workflow docs needed to safely perform lifecycle commands. | Read when selecting, implementing, finishing, closing, or auditing a task. |
|
|
1462
|
+
| \`conditional-reference\` | Architecture, security, roadmap, validation, release, or project-specific specs. | Read only when the task type or active capsule references them. |
|
|
1463
|
+
| \`historical\` | Completed-task history, older validation records, and previous-state detail. | Never default required reading; read only when investigating history. |
|
|
1464
|
+
| \`excluded\` | Superseded, archived, local-only, or intentionally non-default material. | Never default required reading unless explicitly reclassified. |
|
|
1465
|
+
|
|
1466
|
+
\`.hadara/context/HADARA_CONTEXT.md\` is the current-state entry point. It should route readers to compact state before task-work or conditional-reference docs. Full historical review of \`docs/PROJECT_STATE.md\` is not mandatory every session; use \`docs/AGENT_HANDOFF.md\` and its Historical Index when older history is needed. Historical and superseded docs are never default required reading.
|
|
1467
|
+
|
|
1406
1468
|
## Rules
|
|
1407
1469
|
|
|
1408
1470
|
| Rule | Requirement | Evidence / Update Location |
|
package/dist/core/fs.js
CHANGED
|
@@ -8,6 +8,11 @@ exports.writeFileIfMissing = writeFileIfMissing;
|
|
|
8
8
|
exports.appendLine = appendLine;
|
|
9
9
|
exports.readTextIfExists = readTextIfExists;
|
|
10
10
|
exports.writeJsonl = writeJsonl;
|
|
11
|
+
exports.prepareAtomicTextFileWrite = prepareAtomicTextFileWrite;
|
|
12
|
+
exports.commitPreparedAtomicTextFileWrite = commitPreparedAtomicTextFileWrite;
|
|
13
|
+
exports.cleanupPreparedAtomicTextFileWrite = cleanupPreparedAtomicTextFileWrite;
|
|
14
|
+
exports.rollbackPreparedAtomicTextFileWrite = rollbackPreparedAtomicTextFileWrite;
|
|
15
|
+
exports.atomicWriteTextFile = atomicWriteTextFile;
|
|
11
16
|
exports.slugify = slugify;
|
|
12
17
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -33,6 +38,63 @@ function readTextIfExists(filePath) {
|
|
|
33
38
|
function writeJsonl(filePath, event) {
|
|
34
39
|
appendLine(filePath, JSON.stringify(event));
|
|
35
40
|
}
|
|
41
|
+
function prepareAtomicTextFileWrite(projectRoot, relativePath, content) {
|
|
42
|
+
const targetPath = resolveProjectRelativeWritePath(projectRoot, relativePath);
|
|
43
|
+
ensureDir(node_path_1.default.dirname(targetPath));
|
|
44
|
+
const previousExists = node_fs_1.default.existsSync(targetPath);
|
|
45
|
+
const previousContent = previousExists ? node_fs_1.default.readFileSync(targetPath, 'utf8') : '';
|
|
46
|
+
const tempPath = uniqueTempPath(targetPath, 'write');
|
|
47
|
+
node_fs_1.default.writeFileSync(tempPath, content, { encoding: 'utf8', flag: 'wx' });
|
|
48
|
+
return { relativePath, targetPath, tempPath, content, previousExists, previousContent };
|
|
49
|
+
}
|
|
50
|
+
function commitPreparedAtomicTextFileWrite(write) {
|
|
51
|
+
node_fs_1.default.renameSync(write.tempPath, write.targetPath);
|
|
52
|
+
}
|
|
53
|
+
function cleanupPreparedAtomicTextFileWrite(write) {
|
|
54
|
+
if (node_fs_1.default.existsSync(write.tempPath))
|
|
55
|
+
node_fs_1.default.rmSync(write.tempPath, { force: true });
|
|
56
|
+
}
|
|
57
|
+
function rollbackPreparedAtomicTextFileWrite(write) {
|
|
58
|
+
cleanupPreparedAtomicTextFileWrite(write);
|
|
59
|
+
if (write.previousExists) {
|
|
60
|
+
const rollbackTempPath = uniqueTempPath(write.targetPath, 'rollback');
|
|
61
|
+
node_fs_1.default.writeFileSync(rollbackTempPath, write.previousContent, { encoding: 'utf8', flag: 'wx' });
|
|
62
|
+
node_fs_1.default.renameSync(rollbackTempPath, write.targetPath);
|
|
63
|
+
}
|
|
64
|
+
else if (node_fs_1.default.existsSync(write.targetPath)) {
|
|
65
|
+
node_fs_1.default.rmSync(write.targetPath, { force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function atomicWriteTextFile(projectRoot, relativePath, content) {
|
|
69
|
+
const prepared = prepareAtomicTextFileWrite(projectRoot, relativePath, content);
|
|
70
|
+
try {
|
|
71
|
+
commitPreparedAtomicTextFileWrite(prepared);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
cleanupPreparedAtomicTextFileWrite(prepared);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function uniqueTempPath(targetPath, purpose) {
|
|
79
|
+
const dir = node_path_1.default.dirname(targetPath);
|
|
80
|
+
const base = node_path_1.default.basename(targetPath);
|
|
81
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
82
|
+
const suffix = `${process.pid}-${Date.now()}-${attempt}-${Math.random().toString(16).slice(2)}`;
|
|
83
|
+
const candidate = node_path_1.default.join(dir, `.hadara-atomic-${purpose}-${suffix}-${base}`);
|
|
84
|
+
if (!node_fs_1.default.existsSync(candidate))
|
|
85
|
+
return candidate;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Could not allocate temporary path for ${targetPath}`);
|
|
88
|
+
}
|
|
89
|
+
function resolveProjectRelativeWritePath(projectRoot, relativePath) {
|
|
90
|
+
const rootPath = node_path_1.default.resolve(projectRoot);
|
|
91
|
+
const targetPath = node_path_1.default.resolve(rootPath, relativePath);
|
|
92
|
+
const relative = node_path_1.default.relative(rootPath, targetPath);
|
|
93
|
+
if (relative.startsWith('..') || node_path_1.default.isAbsolute(relative)) {
|
|
94
|
+
throw new Error(`Refusing to write outside project: ${relativePath}`);
|
|
95
|
+
}
|
|
96
|
+
return targetPath;
|
|
97
|
+
}
|
|
36
98
|
function slugify(input) {
|
|
37
99
|
return input
|
|
38
100
|
.normalize('NFKD')
|
package/dist/core/schema.js
CHANGED
|
@@ -30,6 +30,7 @@ const evidence_list_schema_json_1 = __importDefault(require("../schemas/evidence
|
|
|
30
30
|
const evidence_migration_preview_schema_json_1 = __importDefault(require("../schemas/evidence-migration-preview.schema.json"));
|
|
31
31
|
const event_schema_json_1 = __importDefault(require("../schemas/event.schema.json"));
|
|
32
32
|
const feature_smoke_schema_json_1 = __importDefault(require("../schemas/feature-smoke.schema.json"));
|
|
33
|
+
const harness_validate_schema_json_1 = __importDefault(require("../schemas/harness-validate.schema.json"));
|
|
33
34
|
const handoff_suggestion_schema_json_1 = __importDefault(require("../schemas/handoff-suggestion.schema.json"));
|
|
34
35
|
const install_plan_schema_json_1 = __importDefault(require("../schemas/install-plan.schema.json"));
|
|
35
36
|
const next_action_schema_json_1 = __importDefault(require("../schemas/next-action.schema.json"));
|
|
@@ -95,6 +96,7 @@ const registeredSchemas = {
|
|
|
95
96
|
'hadara.evidence.migration_preview.v1': evidence_migration_preview_schema_json_1.default,
|
|
96
97
|
'hadara.event.v1': event_schema_json_1.default,
|
|
97
98
|
'hadara.featureSmoke.v1': feature_smoke_schema_json_1.default,
|
|
99
|
+
'hadara.harness.validate.v1': harness_validate_schema_json_1.default,
|
|
98
100
|
'hadara.handoff.suggestion.v1': handoff_suggestion_schema_json_1.default,
|
|
99
101
|
'hadara.install.plan.v1': install_plan_schema_json_1.default,
|
|
100
102
|
'hadara.next_action.v1': next_action_schema_json_1.default,
|