hadara 0.3.0-rc.1 → 0.3.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.
- package/README.md +15 -20
- 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/managed-sections.js +13 -1
- package/dist/services/operational-debt.js +4 -4
- package/dist/services/protocol-migration.js +109 -17
- package/dist/services/release-publish.js +4 -2
- package/dist/task/task-close.js +10 -1
- package/dist/task/task-finish.js +53 -11
- package/package.json +1 -1
package/dist/harness/validate.js
CHANGED
|
@@ -63,7 +63,13 @@ function validateTaskCapsule(projectRoot, taskId, options = {}) {
|
|
|
63
63
|
severity: 'error',
|
|
64
64
|
code: 'MISSING_TASK_FILE',
|
|
65
65
|
message: `Required Task Capsule file is missing: ${fileName}`,
|
|
66
|
-
path: relativePath
|
|
66
|
+
path: relativePath,
|
|
67
|
+
fixHint: `Create ${fileName} using the Task Capsule scaffold for this task.`,
|
|
68
|
+
remediationHint: {
|
|
69
|
+
path: relativePath,
|
|
70
|
+
requiredChange: `Create the missing required Task Capsule file ${fileName}.`,
|
|
71
|
+
blocking: true
|
|
72
|
+
}
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -103,7 +109,15 @@ function validateTaskMarkdown(projectRoot, task, issues) {
|
|
|
103
109
|
severity: 'error',
|
|
104
110
|
code: 'TASK_SECTION_MISSING',
|
|
105
111
|
message: `TASK.md is missing required section: ${heading}`,
|
|
106
|
-
path: relativePath
|
|
112
|
+
path: relativePath,
|
|
113
|
+
heading,
|
|
114
|
+
fixHint: `Add the ${heading} section to TASK.md and fill it with task-specific content.`,
|
|
115
|
+
remediationHint: {
|
|
116
|
+
path: relativePath,
|
|
117
|
+
heading,
|
|
118
|
+
requiredChange: `Add the missing ${heading} section to TASK.md.`,
|
|
119
|
+
blocking: true
|
|
120
|
+
}
|
|
107
121
|
});
|
|
108
122
|
}
|
|
109
123
|
}
|
|
@@ -142,7 +156,15 @@ function validateMarkdownFile(projectRoot, task, issues, fileName, checks) {
|
|
|
142
156
|
severity: 'error',
|
|
143
157
|
code: check.code,
|
|
144
158
|
message: `${fileName} is missing standard Task Capsule format marker: ${check.anyText.join(' or ')}`,
|
|
145
|
-
path: relativePath
|
|
159
|
+
path: relativePath,
|
|
160
|
+
fixHint: `Restore the standard ${fileName} Task Capsule frame: ${check.anyText.join(' or ')}.`,
|
|
161
|
+
example: check.anyText[0],
|
|
162
|
+
remediationHint: {
|
|
163
|
+
path: relativePath,
|
|
164
|
+
requiredChange: `Restore the standard ${fileName} format marker.`,
|
|
165
|
+
example: check.anyText[0],
|
|
166
|
+
blocking: true
|
|
167
|
+
}
|
|
146
168
|
});
|
|
147
169
|
}
|
|
148
170
|
}
|
|
@@ -159,7 +181,17 @@ function validateEvidenceMarkdown(projectRoot, task, issues) {
|
|
|
159
181
|
severity: 'error',
|
|
160
182
|
code: 'EVIDENCE_TABLE_INVALID',
|
|
161
183
|
message: 'EVIDENCE.md must contain the standard evidence table header.',
|
|
162
|
-
path: relativePath
|
|
184
|
+
path: relativePath,
|
|
185
|
+
heading: 'Evidence',
|
|
186
|
+
fixHint: 'Restore the standard EVIDENCE.md table header.',
|
|
187
|
+
example: '| Time | Kind | Summary | Result | Visibility | JSONL |',
|
|
188
|
+
remediationHint: {
|
|
189
|
+
path: relativePath,
|
|
190
|
+
heading: 'Evidence',
|
|
191
|
+
requiredChange: 'Restore the standard EVIDENCE.md table header.',
|
|
192
|
+
example: '| Time | Kind | Summary | Result | Visibility | JSONL |',
|
|
193
|
+
blocking: true
|
|
194
|
+
}
|
|
163
195
|
});
|
|
164
196
|
}
|
|
165
197
|
}
|
|
@@ -327,7 +359,17 @@ function validateTaskMetadataComplete(projectRoot, task, issues) {
|
|
|
327
359
|
severity: 'error',
|
|
328
360
|
code: 'TASK_METADATA_PLACEHOLDER',
|
|
329
361
|
message: `Done-level validation requires TASK.md metadata field(s) to be concrete dates, not TBD: ${missing.join(', ')}.`,
|
|
330
|
-
path: relativePath
|
|
362
|
+
path: relativePath,
|
|
363
|
+
heading: 'Metadata',
|
|
364
|
+
fixHint: 'Replace TASK.md Created and Updated metadata placeholders with YYYY-MM-DD dates.',
|
|
365
|
+
example: '| Created | 2026-06-12 |',
|
|
366
|
+
remediationHint: {
|
|
367
|
+
path: relativePath,
|
|
368
|
+
heading: 'Metadata',
|
|
369
|
+
requiredChange: 'Replace Created and Updated metadata placeholders with concrete YYYY-MM-DD dates.',
|
|
370
|
+
example: '| Created | 2026-06-12 |',
|
|
371
|
+
blocking: true
|
|
372
|
+
}
|
|
331
373
|
});
|
|
332
374
|
return;
|
|
333
375
|
}
|
|
@@ -336,7 +378,17 @@ function validateTaskMetadataComplete(projectRoot, task, issues) {
|
|
|
336
378
|
severity: 'error',
|
|
337
379
|
code: 'TASK_METADATA_DATE_INVALID',
|
|
338
380
|
message: 'Done-level validation requires TASK.md Created and Updated metadata to use YYYY-MM-DD dates.',
|
|
339
|
-
path: relativePath
|
|
381
|
+
path: relativePath,
|
|
382
|
+
heading: 'Metadata',
|
|
383
|
+
fixHint: 'Use YYYY-MM-DD values for TASK.md Created and Updated metadata.',
|
|
384
|
+
example: '| Updated | 2026-06-12 |',
|
|
385
|
+
remediationHint: {
|
|
386
|
+
path: relativePath,
|
|
387
|
+
heading: 'Metadata',
|
|
388
|
+
requiredChange: 'Use YYYY-MM-DD values for Created and Updated metadata.',
|
|
389
|
+
example: '| Updated | 2026-06-12 |',
|
|
390
|
+
blocking: true
|
|
391
|
+
}
|
|
340
392
|
});
|
|
341
393
|
}
|
|
342
394
|
}
|
|
@@ -351,7 +403,17 @@ function validateTaskStatusDone(projectRoot, task, issues) {
|
|
|
351
403
|
severity: 'error',
|
|
352
404
|
code: 'TASK_STATUS_NOT_DONE',
|
|
353
405
|
message: 'Done-level validation requires TASK.md status to be Done.',
|
|
354
|
-
path: relativePath
|
|
406
|
+
path: relativePath,
|
|
407
|
+
heading: 'Status',
|
|
408
|
+
fixHint: 'Run `hadara task finish --task <task-id> --execute --json` or set the TASK.md status section to Done after the capsule is actually complete.',
|
|
409
|
+
example: '## Status\n\nDone',
|
|
410
|
+
remediationHint: {
|
|
411
|
+
path: relativePath,
|
|
412
|
+
heading: 'Status',
|
|
413
|
+
requiredChange: 'Set TASK.md status to Done after the capsule is complete.',
|
|
414
|
+
example: '## Status\n\nDone',
|
|
415
|
+
blocking: true
|
|
416
|
+
}
|
|
355
417
|
});
|
|
356
418
|
}
|
|
357
419
|
}
|
|
@@ -366,7 +428,17 @@ function validateTaskStatusHistoryDone(projectRoot, task, issues) {
|
|
|
366
428
|
severity: 'error',
|
|
367
429
|
code: 'TASK_STATUS_HISTORY_NOT_DONE',
|
|
368
430
|
message: 'Done-level validation requires TASK.md Status History to end with Done.',
|
|
369
|
-
path: relativePath
|
|
431
|
+
path: relativePath,
|
|
432
|
+
heading: 'Status History',
|
|
433
|
+
fixHint: 'Run `hadara task finish --task <task-id> --execute --json` so Status History ends with a Done row.',
|
|
434
|
+
example: '| 2026-06-12 | Done | Finished task capsule. | `hadara task finish --execute` |',
|
|
435
|
+
remediationHint: {
|
|
436
|
+
path: relativePath,
|
|
437
|
+
heading: 'Status History',
|
|
438
|
+
requiredChange: 'Append or repair the latest Status History row so it records Done.',
|
|
439
|
+
example: '| 2026-06-12 | Done | Finished task capsule. | `hadara task finish --execute` |',
|
|
440
|
+
blocking: true
|
|
441
|
+
}
|
|
370
442
|
});
|
|
371
443
|
}
|
|
372
444
|
}
|
|
@@ -391,7 +463,17 @@ function validateAcceptanceDone(projectRoot, task, issues) {
|
|
|
391
463
|
severity: 'error',
|
|
392
464
|
code: 'ACCEPTANCE_INCOMPLETE',
|
|
393
465
|
message: 'Done-level validation requires all acceptance criteria to be complete.',
|
|
394
|
-
path: relativePath
|
|
466
|
+
path: relativePath,
|
|
467
|
+
heading: 'Acceptance Criteria',
|
|
468
|
+
fixHint: 'Mark each acceptance criterion complete with concrete evidence, or replace placeholder checklist rows with completed task-specific criteria.',
|
|
469
|
+
example: '| AC-1 | Scope is implemented. | Done | evidence id or summary |',
|
|
470
|
+
remediationHint: {
|
|
471
|
+
path: relativePath,
|
|
472
|
+
heading: 'Acceptance Criteria',
|
|
473
|
+
requiredChange: 'Complete every acceptance criterion with evidence before closing.',
|
|
474
|
+
example: '| AC-1 | Scope is implemented. | Done | evidence id or summary |',
|
|
475
|
+
blocking: true
|
|
476
|
+
}
|
|
395
477
|
});
|
|
396
478
|
}
|
|
397
479
|
}
|
|
@@ -453,7 +535,17 @@ function validateDoneLevelScaffoldContent(projectRoot, task, issues) {
|
|
|
453
535
|
severity: 'error',
|
|
454
536
|
code: check.code,
|
|
455
537
|
message: check.message,
|
|
456
|
-
path: toPortablePath(node_path_1.default.relative(projectRoot, filePath))
|
|
538
|
+
path: toPortablePath(node_path_1.default.relative(projectRoot, filePath)),
|
|
539
|
+
heading: scaffoldHeadingForFile(check.fileName),
|
|
540
|
+
fixHint: scaffoldFixHint(check.fileName),
|
|
541
|
+
example: scaffoldExample(check.fileName),
|
|
542
|
+
remediationHint: {
|
|
543
|
+
path: toPortablePath(node_path_1.default.relative(projectRoot, filePath)),
|
|
544
|
+
heading: scaffoldHeadingForFile(check.fileName),
|
|
545
|
+
requiredChange: scaffoldRequiredChange(check.fileName),
|
|
546
|
+
example: scaffoldExample(check.fileName),
|
|
547
|
+
blocking: true
|
|
548
|
+
}
|
|
457
549
|
});
|
|
458
550
|
}
|
|
459
551
|
}
|
|
@@ -468,7 +560,15 @@ function validateEvidenceIndexHasRecords(projectRoot, task, issues) {
|
|
|
468
560
|
severity: 'error',
|
|
469
561
|
code: 'EVIDENCE_REQUIRED',
|
|
470
562
|
message: 'Done-level validation requires at least one evidence.jsonl record.',
|
|
471
|
-
path: relativePath
|
|
563
|
+
path: relativePath,
|
|
564
|
+
fixHint: 'Record validation evidence with `hadara evidence add-command --task <task-id> --summary "..." --result passed --json`.',
|
|
565
|
+
example: 'hadara evidence add-command --task T-0001 --summary "Focused tests passed." --result passed --json',
|
|
566
|
+
remediationHint: {
|
|
567
|
+
path: relativePath,
|
|
568
|
+
requiredChange: 'Append at least one substantive evidence record through the canonical evidence writer.',
|
|
569
|
+
example: 'hadara evidence add-command --task T-0001 --summary "Focused tests passed." --result passed --json',
|
|
570
|
+
blocking: true
|
|
571
|
+
}
|
|
472
572
|
});
|
|
473
573
|
}
|
|
474
574
|
}
|
|
@@ -481,7 +581,15 @@ function validateEvidenceSemanticGates(projectRoot, task, issues) {
|
|
|
481
581
|
severity: issue.severity === 'warning' ? 'warning' : 'error',
|
|
482
582
|
code: issue.code,
|
|
483
583
|
message: issue.message,
|
|
484
|
-
path: issue.path
|
|
584
|
+
path: issue.path,
|
|
585
|
+
fixHint: issue.severity === 'warning' ? 'Review evidence quality and add public validation evidence if needed.' : 'Add substantive passed validation evidence before closing.',
|
|
586
|
+
remediationHint: issue.path
|
|
587
|
+
? {
|
|
588
|
+
path: issue.path,
|
|
589
|
+
requiredChange: issue.severity === 'warning' ? 'Review evidence quality for the task.' : 'Add substantive passed validation evidence for the task.',
|
|
590
|
+
blocking: issue.severity !== 'warning'
|
|
591
|
+
}
|
|
592
|
+
: undefined
|
|
485
593
|
});
|
|
486
594
|
}
|
|
487
595
|
}
|
|
@@ -497,7 +605,17 @@ function validateEvidenceMarkdownSingleTable(projectRoot, task, issues) {
|
|
|
497
605
|
severity: 'error',
|
|
498
606
|
code: 'EVIDENCE_TABLE_DUPLICATE_HEADER',
|
|
499
607
|
message: `Done-level validation requires EVIDENCE.md to contain exactly one evidence table header; found ${tableHeaderCount}.`,
|
|
500
|
-
path: relativePath
|
|
608
|
+
path: relativePath,
|
|
609
|
+
heading: 'Evidence',
|
|
610
|
+
fixHint: 'Remove duplicate EVIDENCE.md table headers and keep one canonical evidence table.',
|
|
611
|
+
example: '| Time | Kind | Summary | Result | Visibility | JSONL |',
|
|
612
|
+
remediationHint: {
|
|
613
|
+
path: relativePath,
|
|
614
|
+
heading: 'Evidence',
|
|
615
|
+
requiredChange: 'Remove duplicate EVIDENCE.md table headers and keep one canonical table.',
|
|
616
|
+
example: '| Time | Kind | Summary | Result | Visibility | JSONL |',
|
|
617
|
+
blocking: true
|
|
618
|
+
}
|
|
501
619
|
});
|
|
502
620
|
}
|
|
503
621
|
}
|
|
@@ -513,7 +631,17 @@ function validateHandoffDone(projectRoot, task, issues) {
|
|
|
513
631
|
severity: 'error',
|
|
514
632
|
code: 'HANDOFF_PLACEHOLDER',
|
|
515
633
|
message: 'Done-level validation requires non-placeholder handoff sections.',
|
|
516
|
-
path: relativePath
|
|
634
|
+
path: relativePath,
|
|
635
|
+
heading: 'Last Completed / Next Recommended Step',
|
|
636
|
+
fixHint: 'Replace HANDOFF.md placeholder rows with concrete last-completed work and next-step guidance.',
|
|
637
|
+
example: '| Continue with T-0002. | T-0001 is closed-valid. | docs/TASK_BOARD.md |',
|
|
638
|
+
remediationHint: {
|
|
639
|
+
path: relativePath,
|
|
640
|
+
heading: 'Last Completed / Next Recommended Step',
|
|
641
|
+
requiredChange: 'Replace handoff placeholders with concrete last-completed and next recommended step rows.',
|
|
642
|
+
example: '| Continue with T-0002. | T-0001 is closed-valid. | docs/TASK_BOARD.md |',
|
|
643
|
+
blocking: true
|
|
644
|
+
}
|
|
517
645
|
});
|
|
518
646
|
}
|
|
519
647
|
}
|
|
@@ -526,7 +654,17 @@ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
|
|
|
526
654
|
severity: 'error',
|
|
527
655
|
code: 'TASK_BOARD_MISSING',
|
|
528
656
|
message: 'Done-level validation requires docs/TASK_BOARD.md to contain the completed task row.',
|
|
529
|
-
path: relativePath
|
|
657
|
+
path: relativePath,
|
|
658
|
+
heading: 'TASK_BOARD',
|
|
659
|
+
fixHint: 'Restore docs/TASK_BOARD.md with the canonical task table and a row for this task.',
|
|
660
|
+
example: '| ID | Title | Status | Capsule | Notes |',
|
|
661
|
+
remediationHint: {
|
|
662
|
+
path: relativePath,
|
|
663
|
+
heading: 'TASK_BOARD',
|
|
664
|
+
requiredChange: 'Restore docs/TASK_BOARD.md with the canonical task table and completed task row.',
|
|
665
|
+
example: '| ID | Title | Status | Capsule | Notes |',
|
|
666
|
+
blocking: true
|
|
667
|
+
}
|
|
530
668
|
});
|
|
531
669
|
return;
|
|
532
670
|
}
|
|
@@ -536,7 +674,17 @@ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
|
|
|
536
674
|
severity: 'error',
|
|
537
675
|
code: 'TASK_BOARD_ROW_MISSING',
|
|
538
676
|
message: `Done-level validation requires docs/TASK_BOARD.md to contain exactly one row for ${task.id}.`,
|
|
539
|
-
path: relativePath
|
|
677
|
+
path: relativePath,
|
|
678
|
+
heading: 'TASK_BOARD',
|
|
679
|
+
fixHint: `Add a docs/TASK_BOARD.md row for ${task.id}, or rerun the task workflow command that should synchronize it.`,
|
|
680
|
+
example: `| ${task.id} | ${task.title} | Done | ${toPortablePath(node_path_1.default.relative(projectRoot, task.dir))} | |`,
|
|
681
|
+
remediationHint: {
|
|
682
|
+
path: relativePath,
|
|
683
|
+
heading: 'TASK_BOARD',
|
|
684
|
+
requiredChange: `Add exactly one Task Board row for ${task.id}.`,
|
|
685
|
+
example: `| ${task.id} | ${task.title} | Done | ${toPortablePath(node_path_1.default.relative(projectRoot, task.dir))} | |`,
|
|
686
|
+
blocking: true
|
|
687
|
+
}
|
|
540
688
|
});
|
|
541
689
|
return;
|
|
542
690
|
}
|
|
@@ -545,7 +693,15 @@ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
|
|
|
545
693
|
severity: 'error',
|
|
546
694
|
code: 'TASK_BOARD_ROW_DUPLICATE',
|
|
547
695
|
message: `docs/TASK_BOARD.md contains ${rows.length} rows for ${task.id}; expected exactly one.`,
|
|
548
|
-
path: relativePath
|
|
696
|
+
path: relativePath,
|
|
697
|
+
heading: 'TASK_BOARD',
|
|
698
|
+
fixHint: `Remove duplicate docs/TASK_BOARD.md rows for ${task.id} so exactly one row remains.`,
|
|
699
|
+
remediationHint: {
|
|
700
|
+
path: relativePath,
|
|
701
|
+
heading: 'TASK_BOARD',
|
|
702
|
+
requiredChange: `Remove duplicate Task Board rows for ${task.id}.`,
|
|
703
|
+
blocking: true
|
|
704
|
+
}
|
|
549
705
|
});
|
|
550
706
|
return;
|
|
551
707
|
}
|
|
@@ -556,7 +712,17 @@ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
|
|
|
556
712
|
severity: 'error',
|
|
557
713
|
code: 'TASK_BOARD_STATUS_NOT_DONE',
|
|
558
714
|
message: `Done-level validation requires docs/TASK_BOARD.md status for ${task.id} to be Done.`,
|
|
559
|
-
path: relativePath
|
|
715
|
+
path: relativePath,
|
|
716
|
+
heading: 'TASK_BOARD',
|
|
717
|
+
fixHint: `Run \`hadara task finish --task ${task.id} --execute --json\` or update the Task Board status cell for ${task.id} to Done.`,
|
|
718
|
+
example: `| ${task.id} | ${row.title} | Done | ${row.capsule} | ${row.notes} |`,
|
|
719
|
+
remediationHint: {
|
|
720
|
+
path: relativePath,
|
|
721
|
+
heading: 'TASK_BOARD',
|
|
722
|
+
requiredChange: `Set the Task Board status cell for ${task.id} to Done.`,
|
|
723
|
+
example: `| ${task.id} | ${row.title} | Done | ${row.capsule} | ${row.notes} |`,
|
|
724
|
+
blocking: true
|
|
725
|
+
}
|
|
560
726
|
});
|
|
561
727
|
}
|
|
562
728
|
if (row.capsule !== expectedCapsule) {
|
|
@@ -564,7 +730,17 @@ function validateTaskBoardDone(projectRoot, task, issues, checkedFiles) {
|
|
|
564
730
|
severity: 'error',
|
|
565
731
|
code: 'TASK_BOARD_CAPSULE_MISMATCH',
|
|
566
732
|
message: `docs/TASK_BOARD.md capsule for ${task.id} is ${row.capsule || '(empty)'}, expected ${expectedCapsule}.`,
|
|
567
|
-
path: relativePath
|
|
733
|
+
path: relativePath,
|
|
734
|
+
heading: 'TASK_BOARD',
|
|
735
|
+
fixHint: `Update the Task Board capsule cell for ${task.id} to ${expectedCapsule}.`,
|
|
736
|
+
example: `| ${task.id} | ${row.title} | ${row.status} | ${expectedCapsule} | ${row.notes} |`,
|
|
737
|
+
remediationHint: {
|
|
738
|
+
path: relativePath,
|
|
739
|
+
heading: 'TASK_BOARD',
|
|
740
|
+
requiredChange: `Set the Task Board capsule cell for ${task.id} to ${expectedCapsule}.`,
|
|
741
|
+
example: `| ${task.id} | ${row.title} | ${row.status} | ${expectedCapsule} | ${row.notes} |`,
|
|
742
|
+
blocking: true
|
|
743
|
+
}
|
|
568
744
|
});
|
|
569
745
|
}
|
|
570
746
|
}
|
|
@@ -634,6 +810,43 @@ function isMetadataPlaceholder(value) {
|
|
|
634
810
|
function isIsoDate(value) {
|
|
635
811
|
return /^\d{4}-\d{2}-\d{2}$/.test(value.trim());
|
|
636
812
|
}
|
|
813
|
+
function scaffoldHeadingForFile(fileName) {
|
|
814
|
+
const headings = {
|
|
815
|
+
'TASK.md': 'Goal / Scope / Out of Scope',
|
|
816
|
+
'PLAN.md': 'Plan',
|
|
817
|
+
'CONTEXT.md': 'Context',
|
|
818
|
+
'FILES.md': 'Files',
|
|
819
|
+
'ACCEPTANCE.md': 'Acceptance Criteria',
|
|
820
|
+
'TESTS.md': 'Tests',
|
|
821
|
+
'RISKS.md': 'Risks',
|
|
822
|
+
'DECISIONS.md': 'Decisions',
|
|
823
|
+
'EVIDENCE.md': 'Evidence'
|
|
824
|
+
};
|
|
825
|
+
return headings[fileName];
|
|
826
|
+
}
|
|
827
|
+
function scaffoldRequiredChange(fileName) {
|
|
828
|
+
const heading = scaffoldHeadingForFile(fileName);
|
|
829
|
+
return heading
|
|
830
|
+
? `Replace scaffold placeholder content in ${fileName} under ${heading} with task-specific content.`
|
|
831
|
+
: `Replace scaffold placeholder content in ${fileName} with task-specific content.`;
|
|
832
|
+
}
|
|
833
|
+
function scaffoldFixHint(fileName) {
|
|
834
|
+
return `${scaffoldRequiredChange(fileName)} Mark rows Done only after the work and evidence are real.`;
|
|
835
|
+
}
|
|
836
|
+
function scaffoldExample(fileName) {
|
|
837
|
+
const examples = {
|
|
838
|
+
'TASK.md': '| Preserve Task Board notes. | `task finish` must not erase human-authored cells. |',
|
|
839
|
+
'PLAN.md': '| 1 | Implement focused fix. | Done | evidence id or summary |',
|
|
840
|
+
'CONTEXT.md': '| docs/TASK_BOARD.md | Task Board source of truth. | Read |',
|
|
841
|
+
'FILES.md': '| src/task/task-close.ts | Modify | Add hints. | Done |',
|
|
842
|
+
'ACCEPTANCE.md': '| AC-1 | Scope is implemented. | Done | evidence id or summary |',
|
|
843
|
+
'TESTS.md': '| npm run test:focused -- tests/unit/task-ready.test.ts | Validate hints. | Yes | Passed | evidence id |',
|
|
844
|
+
'RISKS.md': '| Consumer rejects additive fields. | Medium | Low | Keep fields optional. | Mitigated |',
|
|
845
|
+
'DECISIONS.md': '| D-1 | Add additive hints only. | Accepted | Preserve issue-code compatibility. | Tests. |',
|
|
846
|
+
'EVIDENCE.md': '| 2026-06-12T00:00:00.000Z | command-log | Focused tests passed. | passed | public | evidence.jsonl |'
|
|
847
|
+
};
|
|
848
|
+
return examples[fileName] ?? 'Replace TBD rows with task-specific content.';
|
|
849
|
+
}
|
|
637
850
|
function toPortablePath(value) {
|
|
638
851
|
return value.split(node_path_1.default.sep).join('/');
|
|
639
852
|
}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"type": "array",
|
|
36
36
|
"items": { "type": "string", "minLength": 1 }
|
|
37
37
|
},
|
|
38
|
+
"tier": { "$ref": "#/$defs/tier" },
|
|
38
39
|
"reason": { "type": "string", "minLength": 1 }
|
|
39
40
|
}
|
|
40
41
|
},
|
|
@@ -45,9 +46,14 @@
|
|
|
45
46
|
"properties": {
|
|
46
47
|
"path": { "type": "string", "minLength": 1 },
|
|
47
48
|
"status": { "type": "string", "enum": ["historical", "superseded", "archived"] },
|
|
49
|
+
"tier": { "$ref": "#/$defs/tier" },
|
|
48
50
|
"reason": { "type": "string", "minLength": 1 }
|
|
49
51
|
}
|
|
50
52
|
},
|
|
53
|
+
"tier": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"enum": ["current-state", "task-work", "conditional-reference", "historical", "excluded"]
|
|
56
|
+
},
|
|
51
57
|
"issue": {
|
|
52
58
|
"type": "object",
|
|
53
59
|
"additionalProperties": true,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "hadara.harness.validate.v1",
|
|
4
|
+
"x-hadara-schema-id": "hadara.harness.validate.v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["schemaVersion", "command", "ok", "level", "task", "checkedFiles", "issues"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schemaVersion": { "const": "hadara.harness.validate.v1" },
|
|
10
|
+
"command": { "const": "harness.validate" },
|
|
11
|
+
"ok": { "type": "boolean" },
|
|
12
|
+
"level": { "enum": ["draft", "done"] },
|
|
13
|
+
"task": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": true,
|
|
16
|
+
"required": ["id", "title", "capsule"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"id": { "type": "string" },
|
|
19
|
+
"title": { "type": "string" },
|
|
20
|
+
"capsule": { "type": "string" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"checkedFiles": { "type": "array", "items": { "type": "string" } },
|
|
24
|
+
"issues": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": { "$ref": "#/$defs/issue" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"$defs": {
|
|
30
|
+
"issue": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": true,
|
|
33
|
+
"required": ["severity", "code", "message"],
|
|
34
|
+
"properties": {
|
|
35
|
+
"severity": { "enum": ["error", "warning"] },
|
|
36
|
+
"code": { "type": "string" },
|
|
37
|
+
"message": { "type": "string" },
|
|
38
|
+
"path": { "type": "string" },
|
|
39
|
+
"heading": { "type": "string" },
|
|
40
|
+
"fixHint": { "type": "string" },
|
|
41
|
+
"example": { "type": "string" },
|
|
42
|
+
"remediationHint": { "$ref": "#/$defs/remediationHint" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"remediationHint": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": true,
|
|
48
|
+
"required": ["path", "requiredChange", "blocking"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"path": { "type": "string" },
|
|
51
|
+
"heading": { "type": "string" },
|
|
52
|
+
"requiredChange": { "type": "string" },
|
|
53
|
+
"example": { "type": "string" },
|
|
54
|
+
"blocking": { "type": "boolean" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -344,6 +344,13 @@
|
|
|
344
344
|
"owner": "feature-smoke/runner",
|
|
345
345
|
"notes": "Documents reduced read-only major feature smoke reports for core service/read-model validation; installed binary and launcher checks are deferred."
|
|
346
346
|
},
|
|
347
|
+
{
|
|
348
|
+
"id": "hadara.harness.validate.v1",
|
|
349
|
+
"path": "src/schemas/harness-validate.schema.json",
|
|
350
|
+
"status": "fixture",
|
|
351
|
+
"owner": "harness/validate",
|
|
352
|
+
"notes": "Documents Task Capsule draft/done validation reports with additive remediation hints."
|
|
353
|
+
},
|
|
347
354
|
{
|
|
348
355
|
"id": "hadara.cleanCheckoutSmoke.v1",
|
|
349
356
|
"path": "src/schemas/clean-checkout-smoke.schema.json",
|
|
@@ -133,9 +133,27 @@
|
|
|
133
133
|
"severity": { "enum": ["error", "warning", "info"] },
|
|
134
134
|
"code": { "type": "string" },
|
|
135
135
|
"message": { "type": "string" },
|
|
136
|
-
"path": { "type": "string" }
|
|
136
|
+
"path": { "type": "string" },
|
|
137
|
+
"heading": { "type": "string" },
|
|
138
|
+
"fixHint": { "type": "string" },
|
|
139
|
+
"example": { "type": "string" },
|
|
140
|
+
"remediationHint": { "$ref": "#/$defs/remediationHint" }
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
}
|
|
144
|
+
},
|
|
145
|
+
"$defs": {
|
|
146
|
+
"remediationHint": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": true,
|
|
149
|
+
"required": ["path", "requiredChange", "blocking"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"path": { "type": "string" },
|
|
152
|
+
"heading": { "type": "string" },
|
|
153
|
+
"requiredChange": { "type": "string" },
|
|
154
|
+
"example": { "type": "string" },
|
|
155
|
+
"blocking": { "type": "boolean" }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
140
158
|
}
|
|
141
159
|
}
|
|
@@ -45,6 +45,35 @@
|
|
|
45
45
|
},
|
|
46
46
|
"nextActions": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
|
|
47
47
|
"primaryNextAction": { "type": "object", "additionalProperties": true },
|
|
48
|
-
"issues": { "type": "array", "items": { "
|
|
48
|
+
"issues": { "type": "array", "items": { "$ref": "#/$defs/issue" } }
|
|
49
|
+
},
|
|
50
|
+
"$defs": {
|
|
51
|
+
"issue": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": true,
|
|
54
|
+
"required": ["severity", "code", "message"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
57
|
+
"code": { "type": "string" },
|
|
58
|
+
"message": { "type": "string" },
|
|
59
|
+
"path": { "type": "string" },
|
|
60
|
+
"heading": { "type": "string" },
|
|
61
|
+
"fixHint": { "type": "string" },
|
|
62
|
+
"example": { "type": "string" },
|
|
63
|
+
"remediationHint": { "$ref": "#/$defs/remediationHint" }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"remediationHint": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"additionalProperties": true,
|
|
69
|
+
"required": ["path", "requiredChange", "blocking"],
|
|
70
|
+
"properties": {
|
|
71
|
+
"path": { "type": "string" },
|
|
72
|
+
"heading": { "type": "string" },
|
|
73
|
+
"requiredChange": { "type": "string" },
|
|
74
|
+
"example": { "type": "string" },
|
|
75
|
+
"blocking": { "type": "boolean" }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
49
78
|
}
|
|
50
79
|
}
|
|
@@ -9,6 +9,7 @@ exports.createDocsRequiredReadingReport = createDocsRequiredReadingReport;
|
|
|
9
9
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const fs_1 = require("../core/fs");
|
|
12
13
|
const docs_registry_1 = require("./docs-registry");
|
|
13
14
|
const EXCLUDED_REQUIRED_READING_STATUSES = new Set(['historical', 'superseded', 'archived']);
|
|
14
15
|
const ARCHIVE_STATUSES = new Set(['historical', 'superseded']);
|
|
@@ -34,13 +35,19 @@ function createDocsMarkReport(projectRoot, options) {
|
|
|
34
35
|
if (options.mode === 'execute' && options.beforeHash && options.beforeHash !== state.beforeHash) {
|
|
35
36
|
issues.push({ severity: 'error', code: 'DOC_CLEANUP_BEFORE_HASH_MISMATCH', path: docs_registry_1.DOCS_REGISTRY_PATH, message: `Registry hash ${state.beforeHash} does not match reviewed hash ${options.beforeHash}.` });
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
let ok = issues.every((issue) => issue.severity !== 'error');
|
|
38
39
|
if (options.mode === 'execute' && ok && state.registry && entry && afterStatus) {
|
|
39
40
|
entry.status = afterStatus;
|
|
40
41
|
entry.notes = options.reason ? `${entry.notes ? `${entry.notes} ` : ''}Cleanup reason: ${options.reason}` : entry.notes;
|
|
41
42
|
if (afterStatus === 'superseded')
|
|
42
43
|
entry.supersededBy = normalizePath(options.by ?? '');
|
|
43
|
-
|
|
44
|
+
try {
|
|
45
|
+
(0, fs_1.atomicWriteTextFile)(projectRoot, docs_registry_1.DOCS_REGISTRY_PATH, (0, docs_registry_1.registryJson)(state.registry));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
ok = false;
|
|
49
|
+
issues.push({ severity: 'error', code: 'DOC_CLEANUP_ATOMIC_WRITE_FAILED', path: docs_registry_1.DOCS_REGISTRY_PATH, message: `Could not write docs registry atomically: ${error instanceof Error ? error.message : String(error)}` });
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
return {
|
|
46
53
|
schemaVersion: 'hadara.docs.mark.v1',
|
|
@@ -91,11 +98,12 @@ function createDocsRequiredReadingReport(projectRoot) {
|
|
|
91
98
|
for (const doc of state.registry?.documents ?? []) {
|
|
92
99
|
if (!doc.requiredReading)
|
|
93
100
|
continue;
|
|
101
|
+
const tier = requiredReadingTier(doc);
|
|
94
102
|
if (EXCLUDED_REQUIRED_READING_STATUSES.has(doc.status)) {
|
|
95
|
-
excluded.push({ path: doc.path, status: doc.status, reason: `${doc.status} docs are not default required reading` });
|
|
103
|
+
excluded.push({ path: doc.path, status: doc.status, tier, reason: `${doc.status} docs are not default required reading` });
|
|
96
104
|
}
|
|
97
105
|
else {
|
|
98
|
-
documents.push({ path: doc.path, status: doc.status, readWhen: doc.readWhen, reason: `${doc.status} ${doc.kind} doc` });
|
|
106
|
+
documents.push({ path: doc.path, status: doc.status, readWhen: doc.readWhen, tier, reason: `${doc.status} ${doc.kind} doc` });
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
return {
|
|
@@ -107,6 +115,19 @@ function createDocsRequiredReadingReport(projectRoot) {
|
|
|
107
115
|
issues
|
|
108
116
|
};
|
|
109
117
|
}
|
|
118
|
+
function requiredReadingTier(doc) {
|
|
119
|
+
if (doc.status === 'superseded' || doc.status === 'archived')
|
|
120
|
+
return 'excluded';
|
|
121
|
+
if (doc.status === 'historical')
|
|
122
|
+
return 'historical';
|
|
123
|
+
if (doc.kind === 'workflow-guide' || doc.kind === 'task-board' || doc.kind === 'task-capsule' || doc.readWhen.some((readWhen) => readWhen === 'task-start' || readWhen === 'task-close')) {
|
|
124
|
+
return 'task-work';
|
|
125
|
+
}
|
|
126
|
+
if (doc.kind === 'project-context' || doc.kind === 'project-state' || doc.kind === 'handoff' || doc.kind === 'protocol' || doc.readWhen.includes('session-start')) {
|
|
127
|
+
return 'current-state';
|
|
128
|
+
}
|
|
129
|
+
return 'conditional-reference';
|
|
130
|
+
}
|
|
110
131
|
function validateTransition(entry, afterStatus, options) {
|
|
111
132
|
const issues = [];
|
|
112
133
|
const reasonRequired = afterStatus === 'historical' || afterStatus === 'superseded';
|