coding-agent-harness 1.0.5 → 1.0.6
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/CONTRIBUTING.md +2 -2
- package/README.md +63 -3
- package/README.zh-CN.md +52 -3
- package/SKILL.md +43 -43
- package/dist/build-dist.mjs +189 -0
- package/dist/check-dist-observation.mjs +428 -0
- package/dist/check-harness.mjs +489 -0
- package/dist/check-import-graph.mjs +511 -0
- package/dist/check-runtime-emit.mjs +304 -0
- package/dist/check-type-boundaries.mjs +139 -0
- package/dist/commands/dashboard-command.mjs +80 -0
- package/dist/commands/migration-command.mjs +152 -0
- package/dist/commands/preset-command.mjs +91 -0
- package/dist/commands/task-command.mjs +324 -0
- package/dist/harness.mjs +304 -0
- package/dist/lib/capability-registry.mjs +643 -0
- package/dist/lib/check-module-parallel.mjs +227 -0
- package/dist/lib/check-profiles.mjs +414 -0
- package/dist/lib/check-task-contracts.mjs +54 -0
- package/dist/lib/core-shared.mjs +254 -0
- package/dist/lib/dashboard-data.mjs +608 -0
- package/dist/lib/dashboard-workbench.mjs +334 -0
- package/dist/lib/dashboard-writer.mjs +200 -0
- package/dist/lib/git-status-summary.mjs +45 -0
- package/dist/lib/governance-index-generator.mjs +236 -0
- package/dist/lib/governance-sync.mjs +617 -0
- package/dist/lib/governance-table-boundary.mjs +161 -0
- package/{scripts → dist}/lib/harness-core.mjs +2 -0
- package/dist/lib/harness-paths.mjs +338 -0
- package/dist/lib/lesson-maintenance.mjs +139 -0
- package/dist/lib/markdown-utils.mjs +193 -0
- package/dist/lib/migration-planner.mjs +439 -0
- package/dist/lib/migration-support.mjs +317 -0
- package/dist/lib/phase-kind.mjs +46 -0
- package/dist/lib/preset-audit-contracts.mjs +40 -0
- package/dist/lib/preset-engine.mjs +516 -0
- package/dist/lib/preset-registry.mjs +831 -0
- package/dist/lib/preset-resource-contracts.mjs +83 -0
- package/dist/lib/review-confirm-git-gate.mjs +244 -0
- package/dist/lib/status-builder.mjs +87 -0
- package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
- package/dist/lib/structure-migration.mjs +404 -0
- package/dist/lib/subagent-authorization-audit.mjs +198 -0
- package/dist/lib/task-audit-metadata.mjs +376 -0
- package/dist/lib/task-audit-migration.mjs +355 -0
- package/dist/lib/task-completion-consistency.mjs +29 -0
- package/dist/lib/task-index.mjs +133 -0
- package/dist/lib/task-lesson-candidates.mjs +239 -0
- package/dist/lib/task-lesson-sedimentation.mjs +300 -0
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
- package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
- package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
- package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
- package/dist/lib/task-lifecycle/template-files.mjs +52 -0
- package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
- package/dist/lib/task-lifecycle.mjs +611 -0
- package/dist/lib/task-metadata.mjs +116 -0
- package/dist/lib/task-review-model.mjs +474 -0
- package/dist/lib/task-scanner.mjs +439 -0
- package/dist/lib/task-tombstone-commands.mjs +125 -0
- package/dist/postinstall.mjs +14 -0
- package/dist/run-built-tests.mjs +84 -0
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/overview.md +12 -12
- package/docs-release/architecture/overview.zh-CN.md +12 -12
- package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
- package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
- package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +1 -4
- package/docs-release/guides/agent-installation.en-US.md +14 -8
- package/docs-release/guides/agent-installation.md +14 -8
- package/docs-release/guides/contributing.md +3 -3
- package/docs-release/guides/contributing.zh-CN.md +3 -3
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
- package/docs-release/guides/document-audience-and-surfaces.md +10 -10
- package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
- package/docs-release/guides/migration-playbook.en-US.md +63 -1
- package/docs-release/guides/migration-playbook.md +59 -1
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
- package/docs-release/guides/parent-control-repository-pattern.md +25 -25
- package/docs-release/guides/preset-development.md +2 -2
- package/docs-release/guides/repository-operating-models.en-US.md +21 -21
- package/docs-release/guides/repository-operating-models.md +21 -21
- package/docs-release/guides/task-state-machine.en-US.md +5 -5
- package/docs-release/guides/task-state-machine.md +5 -5
- package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
- package/examples/minimal-project/AGENTS.md +2 -2
- package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
- package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
- package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
- package/package.json +20 -12
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/standard-task/preset.yaml +2 -2
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +14 -14
- package/references/cadence-ledger.md +1 -1
- package/references/ci-cd-standard.md +1 -1
- package/references/delivery-operating-model-standard.md +4 -4
- package/references/docs-directory-standard.md +65 -159
- package/references/external-source-intake-standard.md +10 -10
- package/references/harness-ledger.md +5 -5
- package/references/legacy-12-phase-bootstrap.md +2 -2
- package/references/lessons-governance.md +15 -15
- package/references/long-running-task-standard.md +6 -6
- package/references/module-parallel-standard.md +34 -34
- package/references/planning-loop.md +6 -6
- package/references/project-onboarding-audit.md +4 -4
- package/references/regression-system.md +2 -2
- package/references/repo-governance-standard.md +4 -4
- package/references/review-routing-standard.md +1 -1
- package/references/ssot-governance.md +19 -19
- package/references/taskr-gap-analysis.md +5 -5
- package/references/walkthrough-closeout.md +14 -14
- package/references/worktree-parallel.md +3 -3
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
- package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
- package/templates/AGENTS.md.template +26 -26
- package/templates/architecture/README.md +4 -4
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/20-overview.js +11 -5
- package/templates/dashboard/assets/app-src/40-modules.js +1 -1
- package/templates/dashboard/assets/app.js +12 -6
- package/templates/dashboard/assets/i18n.js +4 -2
- package/templates/development/README.md +10 -10
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +2 -2
- package/templates/development/external-source-packs/README.md +4 -4
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +2 -2
- package/templates/integrations/event-contract.md +2 -2
- package/templates/integrations/third-party/vendor-template.md +2 -2
- package/templates/integrations/webhook-contract.md +2 -2
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/planning/INDEX.md +1 -0
- package/templates/planning/module_session_prompt.md +1 -1
- package/templates/planning/task_plan.md +1 -1
- package/templates/planning/walkthrough.md +47 -0
- package/templates/reference/docs-library-standard.md +8 -8
- package/templates/reference/external-source-intake-standard.md +15 -15
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/ssot/Module-Registry.md +1 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +26 -26
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +4 -4
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +10 -10
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +2 -2
- package/templates-zh-CN/development/external-source-packs/README.md +4 -4
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +2 -2
- package/templates-zh-CN/integrations/event-contract.md +2 -2
- package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
- package/templates-zh-CN/integrations/webhook-contract.md +2 -2
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/planning/module_session_prompt.md +11 -11
- package/templates-zh-CN/planning/walkthrough.md +47 -0
- package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +28 -28
- package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
- package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
- package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +1 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
- package/templates-zh-CN/ssot/Module-Registry.md +3 -3
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
- package/tsconfig.dist.json +16 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtime.json +24 -0
- package/examples/minimal-project/.harness-capabilities.json +0 -8
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
- package/scripts/check-harness.mjs +0 -508
- package/scripts/commands/dashboard-command.mjs +0 -67
- package/scripts/commands/migration-command.mjs +0 -126
- package/scripts/commands/preset-command.mjs +0 -73
- package/scripts/commands/task-command.mjs +0 -328
- package/scripts/harness.mjs +0 -291
- package/scripts/lib/capability-registry.mjs +0 -587
- package/scripts/lib/check-module-parallel.mjs +0 -230
- package/scripts/lib/check-profiles.mjs +0 -372
- package/scripts/lib/check-task-contracts.mjs +0 -55
- package/scripts/lib/core-shared.mjs +0 -249
- package/scripts/lib/dashboard-data.mjs +0 -520
- package/scripts/lib/dashboard-workbench.mjs +0 -336
- package/scripts/lib/dashboard-writer.mjs +0 -202
- package/scripts/lib/git-status-summary.mjs +0 -46
- package/scripts/lib/governance-index-generator.mjs +0 -174
- package/scripts/lib/governance-sync.mjs +0 -611
- package/scripts/lib/governance-table-boundary.mjs +0 -175
- package/scripts/lib/lesson-maintenance.mjs +0 -152
- package/scripts/lib/markdown-utils.mjs +0 -191
- package/scripts/lib/migration-planner.mjs +0 -476
- package/scripts/lib/migration-support.mjs +0 -312
- package/scripts/lib/phase-kind.mjs +0 -50
- package/scripts/lib/preset-audit-contracts.mjs +0 -37
- package/scripts/lib/preset-engine.mjs +0 -494
- package/scripts/lib/preset-registry.mjs +0 -776
- package/scripts/lib/preset-resource-contracts.mjs +0 -83
- package/scripts/lib/review-confirm-git-gate.mjs +0 -248
- package/scripts/lib/status-builder.mjs +0 -88
- package/scripts/lib/subagent-authorization-audit.mjs +0 -196
- package/scripts/lib/task-audit-metadata.mjs +0 -385
- package/scripts/lib/task-audit-migration.mjs +0 -350
- package/scripts/lib/task-completion-consistency.mjs +0 -26
- package/scripts/lib/task-index.mjs +0 -93
- package/scripts/lib/task-lesson-candidates.mjs +0 -242
- package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
- package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
- package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
- package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
- package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
- package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
- package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
- package/scripts/lib/task-lifecycle.mjs +0 -616
- package/scripts/lib/task-metadata.mjs +0 -118
- package/scripts/lib/task-review-model.mjs +0 -455
- package/scripts/lib/task-scanner.mjs +0 -503
- package/scripts/lib/task-tombstone-commands.mjs +0 -140
- package/scripts/postinstall.mjs +0 -14
- package/templates/walkthrough/Closeout-SSoT.md +0 -43
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
- /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { normalizeTarget, readFileSafe, toPosix } from "./core-shared.mjs";
|
|
4
|
-
import { listTaskPlanPaths, taskIdForDirectory } from "./task-scanner.mjs";
|
|
5
|
-
import {
|
|
6
|
-
extractLegacyBlock,
|
|
7
|
-
fieldsFromMarkdownBlock,
|
|
8
|
-
humanReviewConfirmationHeadingPattern,
|
|
9
|
-
isConcreteAuditField,
|
|
10
|
-
legacyExtraFieldsJson,
|
|
11
|
-
parseTaskAuditMetadata,
|
|
12
|
-
replaceTaskAuditMetadata,
|
|
13
|
-
scaffoldProvenanceHeadingPattern,
|
|
14
|
-
stripLegacyAuditBlocks,
|
|
15
|
-
taskAuditFieldOrder,
|
|
16
|
-
} from "./task-audit-metadata.mjs";
|
|
17
|
-
import { firstColumn, markdownTableRows } from "./markdown-utils.mjs";
|
|
18
|
-
|
|
19
|
-
const scaffoldFieldMap = new Map([
|
|
20
|
-
["created by", "Created By"],
|
|
21
|
-
["command", "Command Shape"],
|
|
22
|
-
["command shape", "Command Shape"],
|
|
23
|
-
["created at", "Created At"],
|
|
24
|
-
["budget", "Budget"],
|
|
25
|
-
["template source", "Template Source"],
|
|
26
|
-
["exception reason", "Exception Reason"],
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const reviewFieldMap = new Map([
|
|
30
|
-
["confirmation id", "Confirmation ID"],
|
|
31
|
-
["confirmed at", "Confirmed At"],
|
|
32
|
-
["reviewer", "Reviewer"],
|
|
33
|
-
["reviewer email", "Reviewer Email"],
|
|
34
|
-
["confirm text", "Confirm Text"],
|
|
35
|
-
["evidence", "Evidence Checked"],
|
|
36
|
-
["evidence checked", "Evidence Checked"],
|
|
37
|
-
["commit sha", "Review Commit SHA"],
|
|
38
|
-
["review commit sha", "Review Commit SHA"],
|
|
39
|
-
["audit status", "Audit Status"],
|
|
40
|
-
["message", "Message"],
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
const requiredScaffold = ["Created By", "Created At", "Command Shape", "Budget", "Template Source"];
|
|
44
|
-
const requiredReview = ["Confirmation ID", "Confirmed At", "Reviewer", "Reviewer Email", "Confirm Text", "Evidence Checked", "Review Commit SHA", "Audit Status"];
|
|
45
|
-
|
|
46
|
-
export function planTaskAuditIndexMigration(targetInput) {
|
|
47
|
-
const target = normalizeTarget(targetInput);
|
|
48
|
-
const actions = [];
|
|
49
|
-
const failures = [];
|
|
50
|
-
for (const taskPlanPath of listTaskPlanPaths(target)) {
|
|
51
|
-
const taskDir = path.dirname(taskPlanPath);
|
|
52
|
-
const taskId = taskIdForDirectory(target, taskDir);
|
|
53
|
-
const indexPath = path.join(taskDir, "INDEX.md");
|
|
54
|
-
const briefPath = path.join(taskDir, "brief.md");
|
|
55
|
-
const reviewPath = path.join(taskDir, "review.md");
|
|
56
|
-
const indexContent = readFileSafe(indexPath);
|
|
57
|
-
const briefContent = readFileSafe(briefPath);
|
|
58
|
-
const reviewContent = readFileSafe(reviewPath);
|
|
59
|
-
const scaffoldBlock = extractLegacyBlock(briefContent, scaffoldProvenanceHeadingPattern);
|
|
60
|
-
const reviewBlock = extractLegacyBlock(reviewContent, humanReviewConfirmationHeadingPattern);
|
|
61
|
-
const audit = parseTaskAuditMetadata(indexContent, { required: true });
|
|
62
|
-
if (!scaffoldBlock && !reviewBlock && audit.issues.length === 0) continue;
|
|
63
|
-
const parsed = parseLegacyAudit({ taskId, indexContent, scaffoldBlock, reviewBlock });
|
|
64
|
-
if (parsed.failures.length) {
|
|
65
|
-
failures.push(...parsed.failures.map((failure) => ({ taskId, path: `TARGET:${toPosix(path.relative(target.projectRoot, taskDir))}`, failure })));
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
actions.push({
|
|
69
|
-
taskId,
|
|
70
|
-
path: `TARGET:${toPosix(path.relative(target.projectRoot, taskDir))}`,
|
|
71
|
-
legacyBlocks: [scaffoldBlock ? "brief.md#Scaffold Provenance" : "", reviewBlock ? "review.md#Human Review Confirmation" : ""].filter(Boolean),
|
|
72
|
-
fields: parsed.fields,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
operation: "migrate-task-audit-index",
|
|
77
|
-
target: target.projectRoot,
|
|
78
|
-
result: failures.length ? "blocked" : "planned",
|
|
79
|
-
summary: {
|
|
80
|
-
tasks: listTaskPlanPaths(target).length,
|
|
81
|
-
actions: actions.length,
|
|
82
|
-
failures: failures.length,
|
|
83
|
-
legacyAuditBlocks: actions.reduce((sum, action) => sum + action.legacyBlocks.length, 0) + failures.length,
|
|
84
|
-
},
|
|
85
|
-
actions,
|
|
86
|
-
failures,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function applyTaskAuditIndexMigration(targetInput) {
|
|
91
|
-
const target = normalizeTarget(targetInput);
|
|
92
|
-
const plan = planTaskAuditIndexMigration(targetInput);
|
|
93
|
-
if (plan.failures.length) {
|
|
94
|
-
const error = new Error(`Task audit INDEX migration failed during plan: ${plan.failures.map((failure) => `${failure.taskId}: ${failure.failure}`).join("; ")}`);
|
|
95
|
-
error.plan = plan;
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
const writes = [];
|
|
99
|
-
for (const action of plan.actions) {
|
|
100
|
-
const taskDir = path.join(target.projectRoot, action.path.replace(/^TARGET:/, ""));
|
|
101
|
-
const indexPath = path.join(taskDir, "INDEX.md");
|
|
102
|
-
const briefPath = path.join(taskDir, "brief.md");
|
|
103
|
-
const reviewPath = path.join(taskDir, "review.md");
|
|
104
|
-
const indexContent = readFileSafe(indexPath);
|
|
105
|
-
const briefContent = readFileSafe(briefPath);
|
|
106
|
-
const reviewContent = readFileSafe(reviewPath);
|
|
107
|
-
writes.push({
|
|
108
|
-
indexPath,
|
|
109
|
-
briefPath,
|
|
110
|
-
reviewPath,
|
|
111
|
-
before: { indexContent, briefContent, reviewContent },
|
|
112
|
-
indexContent: replaceTaskAuditMetadata(indexContent, action.fields).replace(/\n?$/, "\n"),
|
|
113
|
-
briefContent: stripLegacyAuditBlocks(briefContent).replace(/\n?$/, "\n"),
|
|
114
|
-
reviewContent: stripLegacyAuditBlocks(reviewContent).replace(/\n?$/, "\n"),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
for (const write of writes) {
|
|
119
|
-
fs.writeFileSync(write.indexPath, write.indexContent);
|
|
120
|
-
fs.writeFileSync(write.briefPath, write.briefContent);
|
|
121
|
-
fs.writeFileSync(write.reviewPath, write.reviewContent);
|
|
122
|
-
}
|
|
123
|
-
verifyAppliedWrites(writes);
|
|
124
|
-
} catch (cause) {
|
|
125
|
-
for (const write of writes) {
|
|
126
|
-
fs.writeFileSync(write.indexPath, write.before.indexContent);
|
|
127
|
-
fs.writeFileSync(write.briefPath, write.before.briefContent);
|
|
128
|
-
fs.writeFileSync(write.reviewPath, write.before.reviewContent);
|
|
129
|
-
}
|
|
130
|
-
const error = new Error(`Task audit INDEX migration apply failed and was rolled back: ${cause.message}`);
|
|
131
|
-
error.cause = cause;
|
|
132
|
-
error.plan = plan;
|
|
133
|
-
throw error;
|
|
134
|
-
}
|
|
135
|
-
return { ...plan, result: "applied" };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function verifyAppliedWrites(writes) {
|
|
139
|
-
for (const write of writes) {
|
|
140
|
-
const audit = parseTaskAuditMetadata(readFileSafe(write.indexPath), { required: true });
|
|
141
|
-
if (audit.issues.length) throw new Error(`${write.indexPath}: ${audit.issues.map((issue) => issue.message).join("; ")}`);
|
|
142
|
-
if (scaffoldProvenanceHeadingPattern.test(readFileSafe(write.briefPath))) throw new Error(`${write.briefPath}: legacy Scaffold Provenance remains`);
|
|
143
|
-
if (humanReviewConfirmationHeadingPattern.test(readFileSafe(write.reviewPath))) throw new Error(`${write.reviewPath}: legacy Human Review Confirmation remains`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function parseLegacyAudit({ taskId, indexContent, scaffoldBlock, reviewBlock }) {
|
|
148
|
-
const audit = parseTaskAuditMetadata(indexContent);
|
|
149
|
-
const fields = {};
|
|
150
|
-
for (const field of taskAuditFieldOrder) fields[field] = audit.fields.get(field.toLowerCase()) || "n/a";
|
|
151
|
-
const failures = [];
|
|
152
|
-
const extraEntries = [];
|
|
153
|
-
applyHistoricalBackfillDefaults(fields, taskId);
|
|
154
|
-
normalizeExistingAuditFields(fields, taskId, extraEntries);
|
|
155
|
-
|
|
156
|
-
if (scaffoldBlock) {
|
|
157
|
-
const scaffold = fieldsFromMarkdownBlock(scaffoldBlock.body);
|
|
158
|
-
for (const [legacyKey, canonical] of scaffoldFieldMap) {
|
|
159
|
-
const value = scaffold.get(legacyKey);
|
|
160
|
-
if (isConcreteAuditField(value)) fields[canonical] = value;
|
|
161
|
-
}
|
|
162
|
-
for (const field of requiredScaffold) {
|
|
163
|
-
if (!isConcreteAuditField(fields[field])) failures.push(`Scaffold Provenance missing ${field}`);
|
|
164
|
-
}
|
|
165
|
-
for (const [key, value] of scaffold) {
|
|
166
|
-
if (!scaffoldFieldMap.has(key) && isConcreteAuditField(value)) extraEntries.push([titleField(key), value]);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (reviewBlock) {
|
|
171
|
-
const { fields: review, shape: reviewShape } = parseLegacyReviewFields(reviewBlock.body);
|
|
172
|
-
const concreteConfirmation = concreteReviewConfirmationValues(review);
|
|
173
|
-
if (concreteConfirmation.length === 0) {
|
|
174
|
-
fields["Human Review Status"] = "not-confirmed";
|
|
175
|
-
fields["Migration Status"] = "migrated";
|
|
176
|
-
fields["Migration Notes"] = "removed placeholder-only legacy Human Review Confirmation";
|
|
177
|
-
} else {
|
|
178
|
-
for (const [legacyKey, canonical] of reviewFieldMap) {
|
|
179
|
-
const value = review.get(legacyKey);
|
|
180
|
-
if (isConcreteAuditField(value)) fields[canonical] = value;
|
|
181
|
-
}
|
|
182
|
-
const legacyTaskKey = review.get("task key") || "";
|
|
183
|
-
if (isConcreteAuditField(legacyTaskKey) && legacyTaskKey !== taskId && !taskId.endsWith(`/${legacyTaskKey}`)) failures.push(`Human Review Confirmation Task Key mismatch: ${legacyTaskKey}`);
|
|
184
|
-
if (reviewShape === "field-value") {
|
|
185
|
-
for (const field of requiredReview) {
|
|
186
|
-
if (!isConcreteAuditField(fields[field])) failures.push(`Human Review Confirmation missing ${field}`);
|
|
187
|
-
}
|
|
188
|
-
if (isConcreteAuditField(fields["Audit Status"]) && String(fields["Audit Status"]).trim().toLowerCase() !== "committed") failures.push(`Human Review Confirmation invalid Audit Status: ${fields["Audit Status"]}`);
|
|
189
|
-
} else {
|
|
190
|
-
for (const field of ["Confirmed At", "Reviewer"]) {
|
|
191
|
-
if (!isConcreteAuditField(fields[field])) failures.push(`Human Review Confirmation missing ${field}`);
|
|
192
|
-
}
|
|
193
|
-
for (const field of requiredReview) {
|
|
194
|
-
if (!isConcreteAuditField(fields[field])) {
|
|
195
|
-
fields[field] = "legacy-unavailable";
|
|
196
|
-
extraEntries.push([`Missing ${field}`, "legacy-unavailable"]);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
fields["Audit Status"] = "committed";
|
|
200
|
-
}
|
|
201
|
-
if (reviewShape === "field-value" && isConcreteAuditField(fields["Review Commit SHA"]) && !/^[0-9a-f]{7,40}$/i.test(fields["Review Commit SHA"])) failures.push("Human Review Confirmation invalid Review Commit SHA");
|
|
202
|
-
fields["Human Review Status"] = "confirmed";
|
|
203
|
-
fields["Audit Source"] = "migrated-legacy-review";
|
|
204
|
-
fields["Audit Status"] = String(fields["Audit Status"] || "").toLowerCase() === "committed" ? "committed" : fields["Audit Status"];
|
|
205
|
-
fields["Migration Status"] = "migrated";
|
|
206
|
-
fields["Migrated From"] = "review.md#Human Review Confirmation";
|
|
207
|
-
fields["Migration Notes"] = reviewShape === "field-value"
|
|
208
|
-
? "migrated legacy review confirmation; native INDEX git audit not required"
|
|
209
|
-
: "migrated loose legacy review confirmation; missing native audit fields recorded as legacy-unavailable";
|
|
210
|
-
for (const [key, value] of review) {
|
|
211
|
-
if (!reviewFieldMap.has(key) && key !== "task key" && isConcreteAuditField(value)) extraEntries.push([titleField(key), value]);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (extraEntries.length) {
|
|
217
|
-
const mergedExtra = mergeLegacyExtraFields(fields["Legacy Extra Fields"], extraEntries);
|
|
218
|
-
if (mergedExtra.failure) failures.push(mergedExtra.failure);
|
|
219
|
-
else fields["Legacy Extra Fields"] = mergedExtra.json;
|
|
220
|
-
}
|
|
221
|
-
return { fields, failures };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function mergeLegacyExtraFields(existingValue, entries) {
|
|
225
|
-
let merged = {};
|
|
226
|
-
if (isConcreteAuditField(existingValue)) {
|
|
227
|
-
try {
|
|
228
|
-
merged = JSON.parse(existingValue);
|
|
229
|
-
if (!merged || Array.isArray(merged) || typeof merged !== "object") throw new Error("not an object");
|
|
230
|
-
} catch {
|
|
231
|
-
return { failure: "Legacy Extra Fields contains invalid JSON" };
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const next = legacyExtraFieldsJson(entries);
|
|
235
|
-
try {
|
|
236
|
-
merged = { ...merged, ...JSON.parse(next) };
|
|
237
|
-
} catch {
|
|
238
|
-
return { failure: "Legacy Extra Fields migration generated invalid JSON" };
|
|
239
|
-
}
|
|
240
|
-
return { json: JSON.stringify(merged) };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function applyHistoricalBackfillDefaults(fields, taskId) {
|
|
244
|
-
const createdAt = String(taskId || "").match(/\d{4}-\d{2}-\d{2}/)?.[0] || "legacy-unavailable";
|
|
245
|
-
const defaults = {
|
|
246
|
-
"Created By": "historical-backfill",
|
|
247
|
-
"Created At": createdAt,
|
|
248
|
-
"Command Shape": "legacy-unavailable",
|
|
249
|
-
"Budget": "legacy-unavailable",
|
|
250
|
-
"Template Source": "legacy-task-index-migration",
|
|
251
|
-
"Task Creator": "legacy-unavailable",
|
|
252
|
-
"Task Creator Source": "legacy-unavailable",
|
|
253
|
-
"Human Review Status": "not-confirmed",
|
|
254
|
-
"Audit Source": "migrated-index-backfill",
|
|
255
|
-
"Audit Status": "migrated",
|
|
256
|
-
"Migration Status": "migrated",
|
|
257
|
-
};
|
|
258
|
-
for (const [field, value] of Object.entries(defaults)) {
|
|
259
|
-
if (!isConcreteAuditField(fields[field])) fields[field] = value;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function normalizeExistingAuditFields(fields, taskId, extraEntries) {
|
|
264
|
-
const allowedCreatedBy = new Set(["harness-new-task", "manual-exception", "historical-backfill"]);
|
|
265
|
-
const allowedBudget = new Set(["simple", "standard", "complex", "legacy-unavailable"]);
|
|
266
|
-
const allowedCreatorSource = new Set(["git-config", "git-config-missing", "git-unavailable", "legacy-unavailable"]);
|
|
267
|
-
const allowedReviewStatus = new Set(["not-confirmed", "confirmed"]);
|
|
268
|
-
const allowedAuditStatus = new Set(["created", "committed", "migrated"]);
|
|
269
|
-
if (!allowedCreatedBy.has(normalizeToken(fields["Created By"]))) replacePreserving(fields, extraEntries, "Created By", "historical-backfill");
|
|
270
|
-
if (normalizeToken(fields["Created At"]) !== "legacy-unavailable" && !isValidDateOnly(fields["Created At"])) replacePreserving(fields, extraEntries, "Created At", String(taskId || "").match(/\d{4}-\d{2}-\d{2}/)?.[0] || "legacy-unavailable");
|
|
271
|
-
if (!allowedBudget.has(normalizeToken(fields["Budget"]))) replacePreserving(fields, extraEntries, "Budget", "legacy-unavailable");
|
|
272
|
-
const creatorSource = normalizeToken(fields["Task Creator Source"]);
|
|
273
|
-
if (!allowedCreatorSource.has(creatorSource)) {
|
|
274
|
-
const normalized = /^git-config(?:-|$)/.test(creatorSource) ? "git-config" : "legacy-unavailable";
|
|
275
|
-
replacePreserving(fields, extraEntries, "Task Creator Source", normalized);
|
|
276
|
-
}
|
|
277
|
-
if (!allowedReviewStatus.has(normalizeToken(fields["Human Review Status"]))) replacePreserving(fields, extraEntries, "Human Review Status", "not-confirmed");
|
|
278
|
-
if (!allowedAuditStatus.has(normalizeToken(fields["Audit Status"]))) {
|
|
279
|
-
const replacement = normalizeToken(fields["Human Review Status"]) === "confirmed"
|
|
280
|
-
? "committed"
|
|
281
|
-
: normalizeToken(fields["Audit Source"]) === "native-index"
|
|
282
|
-
? "created"
|
|
283
|
-
: "migrated";
|
|
284
|
-
replacePreserving(fields, extraEntries, "Audit Status", replacement);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function replacePreserving(fields, extraEntries, field, replacement) {
|
|
289
|
-
if (isConcreteAuditField(fields[field])) extraEntries.push([`Original ${field}`, fields[field]]);
|
|
290
|
-
fields[field] = replacement;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function parseLegacyReviewFields(body) {
|
|
294
|
-
const fields = fieldsFromMarkdownBlock(body);
|
|
295
|
-
const rows = markdownTableRows(body);
|
|
296
|
-
let shape = hasFieldValueTable(rows) ? "field-value" : "loose";
|
|
297
|
-
for (const row of rows.slice(1).filter((candidate) => !candidate.every((cell) => /^:?-{3,}:?$/.test(cell)))) {
|
|
298
|
-
const header = rows[0] || [];
|
|
299
|
-
const confirmedAtIndex = firstColumn(header, ["Confirmed At"]);
|
|
300
|
-
const reviewerIndex = firstColumn(header, ["Reviewer"]);
|
|
301
|
-
const messageIndex = firstColumn(header, ["Message"]);
|
|
302
|
-
const evidenceIndex = firstColumn(header, ["Evidence", "Evidence Checked"]);
|
|
303
|
-
if (confirmedAtIndex >= 0 && row[confirmedAtIndex]) fields.set("confirmed at", row[confirmedAtIndex]);
|
|
304
|
-
if (reviewerIndex >= 0 && row[reviewerIndex]) fields.set("reviewer", row[reviewerIndex]);
|
|
305
|
-
if (messageIndex >= 0 && row[messageIndex]) fields.set("message", row[messageIndex]);
|
|
306
|
-
if (evidenceIndex >= 0 && row[evidenceIndex]) fields.set("evidence checked", row[evidenceIndex]);
|
|
307
|
-
}
|
|
308
|
-
if (!rows.length && /^\s*[-*]\s*[^::|]+?\s*[::]/m.test(String(body || ""))) shape = "loose";
|
|
309
|
-
return { fields, shape };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function hasFieldValueTable(rows) {
|
|
313
|
-
const header = rows[0] || [];
|
|
314
|
-
return firstColumn(header, ["Field", "字段"]) >= 0 && firstColumn(header, ["Value", "值"]) >= 0;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function concreteReviewConfirmationValues(fields) {
|
|
318
|
-
const confirmationKeys = [
|
|
319
|
-
"confirmation id",
|
|
320
|
-
"confirmed at",
|
|
321
|
-
"reviewer",
|
|
322
|
-
"reviewer email",
|
|
323
|
-
"confirm text",
|
|
324
|
-
"evidence",
|
|
325
|
-
"evidence checked",
|
|
326
|
-
"commit sha",
|
|
327
|
-
"review commit sha",
|
|
328
|
-
];
|
|
329
|
-
return confirmationKeys.map((key) => fields.get(key)).filter(isConcreteAuditField);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function titleField(value) {
|
|
333
|
-
return String(value || "")
|
|
334
|
-
.split(/\s+/)
|
|
335
|
-
.filter(Boolean)
|
|
336
|
-
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
337
|
-
.join(" ");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function normalizeToken(value) {
|
|
341
|
-
return String(value || "").replace(/`/g, "").trim().toLowerCase().replaceAll("_", "-").replace(/\s+/g, "-");
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function isValidDateOnly(value) {
|
|
345
|
-
const raw = String(value || "").trim();
|
|
346
|
-
const match = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
347
|
-
if (!match) return false;
|
|
348
|
-
const date = new Date(`${raw}T00:00:00.000Z`);
|
|
349
|
-
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === raw;
|
|
350
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { implementationPhases } from "./phase-kind.mjs";
|
|
2
|
-
|
|
3
|
-
export function validateTaskCompletionConsistency(tasks) {
|
|
4
|
-
const failures = [];
|
|
5
|
-
const warnings = [];
|
|
6
|
-
for (const task of tasks) {
|
|
7
|
-
if (task.state !== "done") continue;
|
|
8
|
-
const phases = task.phases || [];
|
|
9
|
-
const executionPhases = implementationPhases(phases);
|
|
10
|
-
if (phases.length > 0 && executionPhases.length === 0) {
|
|
11
|
-
const message = `${task.visualMapPath} done task has no non-skipped Visual Map execution phase`;
|
|
12
|
-
if (task.closeoutStatus === "closed") failures.push(message);
|
|
13
|
-
else warnings.push(message);
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
const incompletePhases = executionPhases.filter(
|
|
17
|
-
(phase) => phase.state !== "skipped" && (phase.state !== "done" || phase.completion !== 100),
|
|
18
|
-
);
|
|
19
|
-
if (incompletePhases.length === 0) continue;
|
|
20
|
-
const phaseList = incompletePhases.map((phase) => `${phase.id}:${phase.state}:${phase.completion}%`).join(", ");
|
|
21
|
-
const message = `${task.visualMapPath} done task has incomplete Visual Map phases: ${phaseList}`;
|
|
22
|
-
if (task.closeoutStatus === "closed") failures.push(message);
|
|
23
|
-
else warnings.push(message);
|
|
24
|
-
}
|
|
25
|
-
return { failures, warnings };
|
|
26
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import {
|
|
5
|
-
normalizeTarget,
|
|
6
|
-
readFileSafe,
|
|
7
|
-
toPosix,
|
|
8
|
-
} from "./core-shared.mjs";
|
|
9
|
-
import { collectTasks } from "./task-scanner.mjs";
|
|
10
|
-
import { taskScannerVersion } from "./task-review-model.mjs";
|
|
11
|
-
|
|
12
|
-
export function buildTaskIndex(targetInput) {
|
|
13
|
-
const target = normalizeTarget(targetInput);
|
|
14
|
-
const tasks = collectTasks(target);
|
|
15
|
-
assertUniqueTaskKeys(tasks);
|
|
16
|
-
return {
|
|
17
|
-
schemaVersion: "task-index/v1",
|
|
18
|
-
scannerVersion: taskScannerVersion,
|
|
19
|
-
sourceRoot: "TARGET:.",
|
|
20
|
-
generatedAt: new Date().toISOString(),
|
|
21
|
-
sourceFileHashes: Object.fromEntries(tasks.map((task) => [task.taskKey || task.id, hashTaskSources(target, task)])),
|
|
22
|
-
tasks: tasks.map((task) => ({
|
|
23
|
-
taskKey: task.taskKey || task.id,
|
|
24
|
-
id: task.id,
|
|
25
|
-
title: task.title,
|
|
26
|
-
currentPath: task.currentPath || task.path,
|
|
27
|
-
originalPath: task.originalPath || task.path,
|
|
28
|
-
aliases: task.aliases || [],
|
|
29
|
-
identitySource: task.identitySource || "path-derived-legacy",
|
|
30
|
-
state: task.state,
|
|
31
|
-
lifecycleState: task.lifecycleState,
|
|
32
|
-
kind: task.taskKind || "general",
|
|
33
|
-
preset: task.taskPreset || "none",
|
|
34
|
-
presetVersion: task.presetVersion || "",
|
|
35
|
-
evidenceBundle: task.evidenceBundle || "",
|
|
36
|
-
reviewStatus: task.reviewStatus,
|
|
37
|
-
reviewSubmitted: task.reviewSubmitted === true,
|
|
38
|
-
reviewPath: task.reviewPath || "",
|
|
39
|
-
closeoutStatus: task.closeoutStatus || "",
|
|
40
|
-
walkthroughPath: task.walkthroughPath || "",
|
|
41
|
-
module: task.module || "",
|
|
42
|
-
inferredModule: task.inferredModule || "",
|
|
43
|
-
completion: task.completion || 0,
|
|
44
|
-
lessonCandidateStatus: task.lessonCandidateStatus || "",
|
|
45
|
-
lessonCandidateRows: task.lessonCandidateRows || [],
|
|
46
|
-
lessonCandidateIssues: task.lessonCandidateIssues || [],
|
|
47
|
-
risks: task.risks || [],
|
|
48
|
-
residual: residual(task),
|
|
49
|
-
materialsReady: task.materialsReady === true,
|
|
50
|
-
materialIssues: task.materialIssues || [],
|
|
51
|
-
queues: task.taskQueues || [],
|
|
52
|
-
queueReasons: task.queueReasons || [],
|
|
53
|
-
supersedes: task.supersedes || [],
|
|
54
|
-
supersededBy: task.supersededBy || "",
|
|
55
|
-
deletionState: task.deletionState || "active",
|
|
56
|
-
hiddenByDefault: task.hiddenByDefault === true,
|
|
57
|
-
repairPrompt: task.repairPrompt || "",
|
|
58
|
-
})),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function residual(task) {
|
|
63
|
-
if (Array.isArray(task.stateConflicts) && task.stateConflicts.length) return `state-conflicts:${task.stateConflicts.length}`;
|
|
64
|
-
if (Array.isArray(task.materialIssues) && task.materialIssues.length) return `material-issues:${task.materialIssues.length}`;
|
|
65
|
-
if (Array.isArray(task.lessonCandidateIssues) && task.lessonCandidateIssues.length) return `lesson-issues:${task.lessonCandidateIssues.length}`;
|
|
66
|
-
return "none";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function assertUniqueTaskKeys(tasks) {
|
|
70
|
-
const seen = new Map();
|
|
71
|
-
for (const task of tasks) {
|
|
72
|
-
const taskKey = task.taskKey || task.id;
|
|
73
|
-
if (seen.has(taskKey)) {
|
|
74
|
-
const first = seen.get(taskKey);
|
|
75
|
-
throw new Error(`Duplicate task key in task index: ${taskKey} (${first.currentPath || first.path} and ${task.currentPath || task.path})`);
|
|
76
|
-
}
|
|
77
|
-
seen.set(taskKey, task);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function hashTaskSources(target, task) {
|
|
82
|
-
const hash = crypto.createHash("sha256");
|
|
83
|
-
const taskRoot = path.join(target.projectRoot, String(task.path || "").replace(/^TARGET:/, ""));
|
|
84
|
-
for (const fileName of ["task_plan.md", "brief.md", "visual_map.md", "progress.md", "review.md", "findings.md", "lesson_candidates.md", "long-running-task-contract.md"]) {
|
|
85
|
-
const filePath = path.join(taskRoot, fileName);
|
|
86
|
-
if (!fs.existsSync(filePath)) continue;
|
|
87
|
-
hash.update(toPosix(path.relative(target.projectRoot, filePath)));
|
|
88
|
-
hash.update("\0");
|
|
89
|
-
hash.update(readFileSafe(filePath));
|
|
90
|
-
hash.update("\0");
|
|
91
|
-
}
|
|
92
|
-
return hash.digest("hex");
|
|
93
|
-
}
|