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,616 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import {
|
|
5
|
-
visualMapFile,
|
|
6
|
-
legacyVisualRoadmapFile,
|
|
7
|
-
lessonCandidatesFile,
|
|
8
|
-
allowedTaskStates,
|
|
9
|
-
allowedTaskBudgets,
|
|
10
|
-
allowedPhaseStates,
|
|
11
|
-
allowedEvidenceStatus,
|
|
12
|
-
normalizeTarget,
|
|
13
|
-
normalizeLocale,
|
|
14
|
-
toPosix,
|
|
15
|
-
readFileSafe,
|
|
16
|
-
readBundledTemplate,
|
|
17
|
-
todayDate,
|
|
18
|
-
localDate,
|
|
19
|
-
datePrefix,
|
|
20
|
-
normalizeTaskId,
|
|
21
|
-
renderTaskTemplate,
|
|
22
|
-
} from "./core-shared.mjs";
|
|
23
|
-
import { readCapabilityRegistry } from "./capability-registry.mjs";
|
|
24
|
-
import { readPresetPackage } from "./preset-registry.mjs";
|
|
25
|
-
import {
|
|
26
|
-
assertPresetWriteScope,
|
|
27
|
-
buildPresetContext,
|
|
28
|
-
evaluateTemplateValues,
|
|
29
|
-
resolvePresetInputs,
|
|
30
|
-
renderPresetResourceIndex,
|
|
31
|
-
renderPresetTaskTemplate,
|
|
32
|
-
} from "./preset-engine.mjs";
|
|
33
|
-
import {
|
|
34
|
-
collectTasks,
|
|
35
|
-
listTaskPlanPaths,
|
|
36
|
-
parseTaskBudget,
|
|
37
|
-
taskIdForDirectory,
|
|
38
|
-
} from "./task-scanner.mjs";
|
|
39
|
-
import { getColumn, firstColumn, updateMarkdownTableRow } from "./markdown-utils.mjs";
|
|
40
|
-
import { validateLifecycleTransition, validateReviewEntryGate } from "./task-lifecycle/review-gates.mjs";
|
|
41
|
-
import { advanceLifecyclePhase, autoRecordNoLessonCandidateDecision } from "./task-lifecycle/phase-sync.mjs";
|
|
42
|
-
import { confirmTaskReview as confirmTaskReviewWithContext } from "./task-lifecycle/review-confirm.mjs";
|
|
43
|
-
import { appendProgressLog } from "./task-lifecycle/text-utils.mjs";
|
|
44
|
-
import { buildScaffoldProvenance } from "./task-lifecycle/scaffold-provenance.mjs";
|
|
45
|
-
import { buildCreationTaskAudit } from "./task-audit-metadata.mjs";
|
|
46
|
-
import {
|
|
47
|
-
renderAgentReviewSubmission,
|
|
48
|
-
replaceAgentReviewSubmission,
|
|
49
|
-
} from "./task-lifecycle/review-submission.mjs";
|
|
50
|
-
import {
|
|
51
|
-
appendLongRunningContractFile,
|
|
52
|
-
moduleTemplateFiles,
|
|
53
|
-
taskFilesForBudget,
|
|
54
|
-
} from "./task-lifecycle/template-files.mjs";
|
|
55
|
-
import {
|
|
56
|
-
planCreateTaskChanges,
|
|
57
|
-
refreshPresetCommandAudit,
|
|
58
|
-
} from "./task-lifecycle/create-task-helpers.mjs";
|
|
59
|
-
import {
|
|
60
|
-
beginGovernanceSync,
|
|
61
|
-
commitGovernanceSync,
|
|
62
|
-
governanceRelativePaths,
|
|
63
|
-
releaseGovernanceSync,
|
|
64
|
-
syncModuleStepGovernance,
|
|
65
|
-
syncTaskGovernance,
|
|
66
|
-
} from "./governance-sync.mjs";
|
|
67
|
-
|
|
68
|
-
function taskRoot(target, taskId, { moduleKey = "" } = {}) {
|
|
69
|
-
const normalizedTaskId = normalizeTaskId(taskId);
|
|
70
|
-
if (moduleKey) return path.join(target.docsRoot, "09-PLANNING/MODULES", normalizeTaskId(moduleKey), normalizedTaskId);
|
|
71
|
-
return path.join(target.docsRoot, "09-PLANNING/TASKS", normalizedTaskId);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function resolveTaskDirectory(target, taskRef) {
|
|
75
|
-
const raw = String(taskRef || "").replace(/^docs\/09-PLANNING\//, "").replace(/^\/+/, "");
|
|
76
|
-
if (!raw) throw new Error("Missing task id");
|
|
77
|
-
const direct = raw.startsWith("TASKS/") || raw.startsWith("MODULES/") ? path.join(target.docsRoot, "09-PLANNING", raw) : "";
|
|
78
|
-
if (direct && fs.existsSync(path.join(direct, "task_plan.md"))) return direct;
|
|
79
|
-
const normalized = normalizeTaskId(raw);
|
|
80
|
-
const candidates = listTaskPlanPaths(target)
|
|
81
|
-
.map((taskPlanPath) => path.dirname(taskPlanPath))
|
|
82
|
-
.filter((taskDir) => {
|
|
83
|
-
const id = taskIdForDirectory(target, taskDir);
|
|
84
|
-
const dirName = path.basename(taskDir);
|
|
85
|
-
return id === raw || id.endsWith(`/${raw}`) || dirName === normalized;
|
|
86
|
-
});
|
|
87
|
-
if (candidates.length === 1) return candidates[0];
|
|
88
|
-
if (candidates.length > 1) {
|
|
89
|
-
const options = candidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
|
|
90
|
-
throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
|
|
91
|
-
}
|
|
92
|
-
// Try bare slug resolution: match normalized slug against dated directories
|
|
93
|
-
if (!datePrefix.test(normalized)) {
|
|
94
|
-
const datedCandidates = listTaskPlanPaths(target)
|
|
95
|
-
.map((taskPlanPath) => path.dirname(taskPlanPath))
|
|
96
|
-
.filter((taskDir) => {
|
|
97
|
-
const dirName = path.basename(taskDir);
|
|
98
|
-
return datePrefix.test(dirName) && dirName.replace(datePrefix, "") === normalized;
|
|
99
|
-
});
|
|
100
|
-
if (datedCandidates.length === 1) return datedCandidates[0];
|
|
101
|
-
if (datedCandidates.length > 1) {
|
|
102
|
-
const options = datedCandidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
|
|
103
|
-
throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const legacy = taskRoot(target, normalized);
|
|
107
|
-
if (fs.existsSync(path.join(legacy, "task_plan.md"))) return legacy;
|
|
108
|
-
throw new Error(`Task not found: ${taskRef}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function findTaskByDirectory(target, taskDir) {
|
|
112
|
-
const id = taskIdForDirectory(target, taskDir);
|
|
113
|
-
return collectTasks(target).find((task) => task.id === id) || null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function stateLabel(state, locale) {
|
|
117
|
-
if (normalizeLocale(locale) !== "zh-CN") return state;
|
|
118
|
-
return (
|
|
119
|
-
{
|
|
120
|
-
not_started: "未开始",
|
|
121
|
-
planned: "未开始",
|
|
122
|
-
in_progress: "进行中",
|
|
123
|
-
review: "审查中",
|
|
124
|
-
blocked: "已阻塞",
|
|
125
|
-
done: "已完成",
|
|
126
|
-
}[state] || state
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function normalizeTaskBudgetInput(budget) {
|
|
131
|
-
const normalized = String(budget || "standard").trim().toLowerCase().replaceAll("_", "-");
|
|
132
|
-
if (allowedTaskBudgets.has(normalized)) return normalized;
|
|
133
|
-
throw new Error(`Invalid task budget: ${budget}. Expected one of: simple, standard, complex`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function normalizeTaskPresetInput(preset, { targetInput = "" } = {}) {
|
|
137
|
-
const normalized = String(preset || "none").trim().toLowerCase().replaceAll("_", "-");
|
|
138
|
-
if (!normalized || normalized === "none") return "none";
|
|
139
|
-
return readPresetPackage(normalized, { targetInput }).id;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function updateProgressState(content, state, locale) {
|
|
143
|
-
const label = stateLabel(state, locale);
|
|
144
|
-
if (/^##\s*状态[::][^\n]*/im.test(content)) {
|
|
145
|
-
return content.replace(/^##\s*状态[::][^\n]*/im, `## 状态:${label}`);
|
|
146
|
-
}
|
|
147
|
-
if (/^##\s*(?:Current Status|Status)\s*\n+\s*[^\n]+/im.test(content)) {
|
|
148
|
-
return content.replace(/^##\s*(Current Status|Status)\s*\n+\s*[^\n]+/im, `## $1\n\n${label}`);
|
|
149
|
-
}
|
|
150
|
-
return `${content.trimEnd()}\n\n## Status\n\n${label}\n`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function ensureDatePrefix(slug) {
|
|
154
|
-
if (datePrefix.test(slug)) return slug;
|
|
155
|
-
return `${localDate()}-${slug}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function bareSlug(datedId) {
|
|
159
|
-
if (datePrefix.test(datedId)) return datedId.replace(datePrefix, "");
|
|
160
|
-
return datedId;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function automaticTaskSlug(seed) {
|
|
164
|
-
return normalizeTaskId(seed || "task").slice(0, 48).replace(/-+$/g, "") || "task";
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function randomTaskSuffix() {
|
|
168
|
-
return crypto.randomBytes(4).toString("hex");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey, automaticTaskId }) {
|
|
172
|
-
if (!automaticTaskId) {
|
|
173
|
-
const rawNormalized = normalizeTaskId(taskId || (presetPackage?.task?.defaultTaskId || ""));
|
|
174
|
-
const normalizedTaskId = ensureDatePrefix(rawNormalized);
|
|
175
|
-
if (!normalizedTaskId) throw new Error("Missing task id");
|
|
176
|
-
return { normalizedTaskId, semanticSlug: bareSlug(normalizedTaskId) };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const semanticSlug = automaticTaskSlug(title || presetPackage?.task?.defaultTaskId || "task");
|
|
180
|
-
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
181
|
-
const normalizedTaskId = `${localDate()}-${semanticSlug}-${randomTaskSuffix()}`;
|
|
182
|
-
if (!fs.existsSync(taskRoot(target, normalizedTaskId, { moduleKey }))) return { normalizedTaskId, semanticSlug };
|
|
183
|
-
}
|
|
184
|
-
throw new Error(`Unable to allocate automatic task id for: ${semanticSlug}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function createTask(targetInput, taskId, { title = "", locale = "en-US", dryRun = false, moduleKey = "", budget = "standard", longRunning = false, preset = "", fromSession = "", presetArgs = [], automaticTaskId = false } = {}) {
|
|
188
|
-
const requestedPreset = preset || (moduleKey ? "module" : "");
|
|
189
|
-
const normalizedPreset = normalizeTaskPresetInput(requestedPreset, { targetInput });
|
|
190
|
-
const presetPackage = normalizedPreset === "none" ? null : readPresetPackage(normalizedPreset, { targetInput });
|
|
191
|
-
const presetInputs = presetPackage ? resolvePresetInputs(presetPackage, { cliArgs: presetArgs, fromSession, targetInput }) : null;
|
|
192
|
-
const target = normalizeTarget(presetInputs?.targetInput || targetInput);
|
|
193
|
-
if (presetInputs?.targetInput && targetInput && targetInput !== "." && path.resolve(targetInput) !== path.resolve(presetInputs.targetInput)) {
|
|
194
|
-
throw new Error(`--from-session target mismatch: session target is ${presetInputs.targetInput}`);
|
|
195
|
-
}
|
|
196
|
-
const normalizedBudget = normalizeTaskBudgetInput(budget);
|
|
197
|
-
if (presetPackage && !presetPackage.compatibleBudgets.includes(normalizedBudget)) throw new Error(`${normalizedPreset} preset requires --budget ${presetPackage.compatibleBudgets.join("|")}`);
|
|
198
|
-
if (presetPackage?.task?.projectLevelOnly === true && moduleKey) throw new Error(`${normalizedPreset} preset is project-level and cannot be combined with --module`);
|
|
199
|
-
if (presetPackage?.task?.requiresFromSession === true && !fromSession) throw new Error(`${normalizedPreset} preset requires --from-session`);
|
|
200
|
-
const normalizedModuleKey = moduleKey ? normalizeTaskId(moduleKey) : "";
|
|
201
|
-
const identity = resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey: normalizedModuleKey, automaticTaskId });
|
|
202
|
-
const normalizedTaskId = identity.normalizedTaskId;
|
|
203
|
-
const semanticSlug = identity.semanticSlug;
|
|
204
|
-
const normalizedLocale = normalizeLocale(locale || readCapabilityRegistry(target).locale);
|
|
205
|
-
const taskTitle = title || (normalizedPreset === "legacy-migration" ? "Harness v1 legacy migration" : semanticSlug);
|
|
206
|
-
const directory = taskRoot(target, normalizedTaskId, { moduleKey: normalizedModuleKey });
|
|
207
|
-
if (fs.existsSync(directory)) throw new Error(`Task already exists: ${normalizedTaskId}`);
|
|
208
|
-
const scaffoldProvenance = buildScaffoldProvenance({
|
|
209
|
-
taskId,
|
|
210
|
-
normalizedTaskId,
|
|
211
|
-
title,
|
|
212
|
-
locale: normalizedLocale,
|
|
213
|
-
budget: normalizedBudget,
|
|
214
|
-
longRunning,
|
|
215
|
-
moduleKey: normalizedModuleKey,
|
|
216
|
-
preset: normalizedPreset,
|
|
217
|
-
fromSession,
|
|
218
|
-
targetInput: presetInputs?.targetInput || targetInput,
|
|
219
|
-
automaticTaskId,
|
|
220
|
-
});
|
|
221
|
-
const baseTaskAudit = buildCreationTaskAudit(scaffoldProvenance, { projectRoot: target.projectRoot });
|
|
222
|
-
const evaluatedPresetValues = presetPackage ? evaluateTemplateValues(presetPackage, presetInputs.inputs, { taskId: normalizedTaskId, taskTitle, moduleKey: normalizedModuleKey }) : null;
|
|
223
|
-
const presetContext = presetPackage
|
|
224
|
-
? buildPresetContext({ ...presetPackage, task: { ...(presetPackage.task || {}), kind: presetPackage.task?.kind || "general" } }, {
|
|
225
|
-
target,
|
|
226
|
-
taskDir: directory,
|
|
227
|
-
taskId: normalizedTaskId,
|
|
228
|
-
taskTitle,
|
|
229
|
-
resolvedInputs: presetInputs.inputs,
|
|
230
|
-
evaluatedValues: evaluatedPresetValues,
|
|
231
|
-
})
|
|
232
|
-
: null;
|
|
233
|
-
const task = {
|
|
234
|
-
id: taskIdForDirectory(target, directory),
|
|
235
|
-
shortId: normalizedTaskId,
|
|
236
|
-
title: taskTitle,
|
|
237
|
-
module: normalizedModuleKey || null,
|
|
238
|
-
path: `TARGET:${toPosix(path.relative(target.projectRoot, directory))}`,
|
|
239
|
-
locale: normalizedLocale,
|
|
240
|
-
budget: normalizedBudget,
|
|
241
|
-
kind: presetContext?.kind || "general",
|
|
242
|
-
preset: normalizedPreset,
|
|
243
|
-
presetVersion: presetContext?.presetVersion || "",
|
|
244
|
-
presetAudit: presetContext?.audit || null,
|
|
245
|
-
migrationTargetLevel: presetContext?.migrationTargetLevel || "",
|
|
246
|
-
migrationAchievedLevel: presetContext?.migrationAchievedLevel || "",
|
|
247
|
-
evidenceBundle: presetContext?.evidenceBundle || "",
|
|
248
|
-
longRunning,
|
|
249
|
-
};
|
|
250
|
-
const plannedChanges = planCreateTaskChanges({
|
|
251
|
-
target,
|
|
252
|
-
directory,
|
|
253
|
-
normalizedModuleKey,
|
|
254
|
-
normalizedLocale,
|
|
255
|
-
normalizedBudget,
|
|
256
|
-
longRunning,
|
|
257
|
-
presetContext,
|
|
258
|
-
task,
|
|
259
|
-
});
|
|
260
|
-
const plannedGovernance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun: true });
|
|
261
|
-
const plannedWriteScopes = governanceRelativePaths([...plannedChanges, ...plannedGovernance.changes]);
|
|
262
|
-
const changes = [];
|
|
263
|
-
const governanceContext = beginGovernanceSync(target, { operation: `new-task ${normalizedTaskId}`, dryRun, allowDirtyWorktree: true, allowedRelativePaths: plannedWriteScopes });
|
|
264
|
-
try {
|
|
265
|
-
if (normalizedModuleKey) {
|
|
266
|
-
const moduleDirectory = path.dirname(directory);
|
|
267
|
-
for (const [destination, source] of moduleTemplateFiles({ locale: normalizedLocale })) {
|
|
268
|
-
const destinationPath = path.join(moduleDirectory, destination);
|
|
269
|
-
if (fs.existsSync(destinationPath)) continue;
|
|
270
|
-
changes.push({
|
|
271
|
-
destination: toPosix(path.relative(target.projectRoot, destinationPath)),
|
|
272
|
-
source,
|
|
273
|
-
action: dryRun ? "would-create" : "create",
|
|
274
|
-
});
|
|
275
|
-
if (presetPackage) assertPresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)));
|
|
276
|
-
if (dryRun) continue;
|
|
277
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
278
|
-
fs.writeFileSync(
|
|
279
|
-
destinationPath,
|
|
280
|
-
renderTaskTemplate(readBundledTemplate(source), {
|
|
281
|
-
taskId: normalizedModuleKey,
|
|
282
|
-
title: normalizedModuleKey,
|
|
283
|
-
locale: normalizedLocale,
|
|
284
|
-
budget: normalizedBudget,
|
|
285
|
-
moduleKey: normalizedModuleKey,
|
|
286
|
-
preset: normalizedPreset,
|
|
287
|
-
presetVersion: presetContext?.presetVersion || "",
|
|
288
|
-
evidenceBundle: presetContext?.evidenceBundle || "",
|
|
289
|
-
longRunning,
|
|
290
|
-
scaffoldProvenance,
|
|
291
|
-
taskAudit: buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot }),
|
|
292
|
-
}),
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const files = appendLongRunningContractFile(taskFilesForBudget({ budget: normalizedBudget, locale: normalizedLocale }), {
|
|
297
|
-
locale: normalizedLocale,
|
|
298
|
-
longRunning,
|
|
299
|
-
});
|
|
300
|
-
for (const [destination, source] of files) {
|
|
301
|
-
const destinationPath = path.join(directory, destination);
|
|
302
|
-
changes.push({
|
|
303
|
-
destination: toPosix(path.relative(target.projectRoot, destinationPath)),
|
|
304
|
-
source,
|
|
305
|
-
action: dryRun ? "would-create" : "create",
|
|
306
|
-
});
|
|
307
|
-
if (presetPackage) assertPresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)));
|
|
308
|
-
if (dryRun) continue;
|
|
309
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
310
|
-
fs.writeFileSync(
|
|
311
|
-
destinationPath,
|
|
312
|
-
renderPresetTaskTemplate(destination, renderTaskTemplate(readBundledTemplate(source), {
|
|
313
|
-
taskId: normalizedTaskId,
|
|
314
|
-
title: taskTitle,
|
|
315
|
-
locale: normalizedLocale,
|
|
316
|
-
budget: normalizedBudget,
|
|
317
|
-
moduleKey: normalizedModuleKey,
|
|
318
|
-
preset: normalizedPreset,
|
|
319
|
-
presetVersion: presetContext?.presetVersion || "",
|
|
320
|
-
evidenceBundle: presetContext?.evidenceBundle || "",
|
|
321
|
-
longRunning,
|
|
322
|
-
scaffoldProvenance: {
|
|
323
|
-
...scaffoldProvenance,
|
|
324
|
-
templateSource: source,
|
|
325
|
-
},
|
|
326
|
-
taskAudit: destination === "INDEX.md"
|
|
327
|
-
? buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot })
|
|
328
|
-
: baseTaskAudit,
|
|
329
|
-
}), presetContext),
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
if (presetContext) {
|
|
333
|
-
for (const evidence of presetContext.evidenceFiles) {
|
|
334
|
-
const destinationPath = path.join(target.projectRoot, evidence.relativePath);
|
|
335
|
-
changes.push({
|
|
336
|
-
destination: toPosix(evidence.relativePath),
|
|
337
|
-
source: evidence.source,
|
|
338
|
-
action: dryRun ? "would-create" : "create",
|
|
339
|
-
});
|
|
340
|
-
assertPresetWriteScope(presetPackage, toPosix(evidence.relativePath));
|
|
341
|
-
if (dryRun) continue;
|
|
342
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
343
|
-
fs.writeFileSync(destinationPath, evidence.content);
|
|
344
|
-
}
|
|
345
|
-
for (const resource of presetContext.resourceFiles || []) {
|
|
346
|
-
const destinationPath = path.join(target.projectRoot, resource.relativePath);
|
|
347
|
-
changes.push({
|
|
348
|
-
destination: toPosix(resource.relativePath),
|
|
349
|
-
source: resource.source,
|
|
350
|
-
action: dryRun ? "would-create" : "create",
|
|
351
|
-
});
|
|
352
|
-
assertPresetWriteScope(presetPackage, toPosix(resource.relativePath));
|
|
353
|
-
if (dryRun) continue;
|
|
354
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
355
|
-
fs.writeFileSync(destinationPath, resource.content);
|
|
356
|
-
}
|
|
357
|
-
for (const [kind, rows] of Object.entries(presetContext.resourceIndexRows || {})) {
|
|
358
|
-
if (!rows.length) continue;
|
|
359
|
-
const destination = kind === "references" ? "references/INDEX.md" : "artifacts/INDEX.md";
|
|
360
|
-
const destinationPath = path.join(directory, destination);
|
|
361
|
-
const relativePath = toPosix(path.relative(target.projectRoot, destinationPath));
|
|
362
|
-
changes.push({
|
|
363
|
-
destination: relativePath,
|
|
364
|
-
source: `preset-${kind}-index`,
|
|
365
|
-
action: dryRun ? "would-update" : "update",
|
|
366
|
-
});
|
|
367
|
-
assertPresetWriteScope(presetPackage, relativePath);
|
|
368
|
-
if (dryRun) continue;
|
|
369
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
370
|
-
const existing = fs.existsSync(destinationPath) ? fs.readFileSync(destinationPath, "utf8") : "";
|
|
371
|
-
fs.writeFileSync(destinationPath, renderPresetResourceIndex(existing, kind, rows));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const governance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun });
|
|
375
|
-
changes.push(...governance.changes);
|
|
376
|
-
const commandWriteScopes = governanceRelativePaths(changes);
|
|
377
|
-
if (presetContext) {
|
|
378
|
-
refreshPresetCommandAudit(target, presetContext, { commandWriteScopes, dryRun });
|
|
379
|
-
task.presetAudit = presetContext.audit;
|
|
380
|
-
}
|
|
381
|
-
const commit = commitGovernanceSync(governanceContext, commandWriteScopes, {
|
|
382
|
-
message: `chore(harness): register task ${task.id}`,
|
|
383
|
-
});
|
|
384
|
-
return {
|
|
385
|
-
dryRun,
|
|
386
|
-
task,
|
|
387
|
-
changes,
|
|
388
|
-
governance: { ...governance, commit },
|
|
389
|
-
};
|
|
390
|
-
} finally {
|
|
391
|
-
releaseGovernanceSync(governanceContext);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", state = "", message = "", evidence = "" } = {}) {
|
|
396
|
-
const target = normalizeTarget(targetInput);
|
|
397
|
-
const taskDir = resolveTaskDirectory(target, taskId);
|
|
398
|
-
const progressPath = path.join(taskDir, "progress.md");
|
|
399
|
-
const registry = readCapabilityRegistry(target);
|
|
400
|
-
const normalizedState = state ? String(state).toLowerCase().replaceAll("-", "_") : "";
|
|
401
|
-
if (normalizedState && !allowedTaskStates.has(normalizedState)) throw new Error(`Invalid task state: ${state}`);
|
|
402
|
-
const currentTask = findTaskByDirectory(target, taskDir);
|
|
403
|
-
const canonicalTaskId = taskIdForDirectory(target, taskDir);
|
|
404
|
-
const budget = parseTaskBudget(readFileSafe(path.join(taskDir, "task_plan.md")));
|
|
405
|
-
validateLifecycleTransition({
|
|
406
|
-
event,
|
|
407
|
-
currentState: currentTask?.state || "unknown",
|
|
408
|
-
budget,
|
|
409
|
-
reviewContent: readFileSafe(path.join(taskDir, "review.md")),
|
|
410
|
-
indexContent: readFileSafe(path.join(taskDir, "INDEX.md")),
|
|
411
|
-
reviewTaskKey: canonicalTaskId,
|
|
412
|
-
projectRoot: target.projectRoot,
|
|
413
|
-
taskDir,
|
|
414
|
-
});
|
|
415
|
-
if (event === "task-review") validateReviewEntryGate(taskDir, budget);
|
|
416
|
-
const governanceContext = beginGovernanceSync(target, { operation: `${event} ${canonicalTaskId}` });
|
|
417
|
-
try {
|
|
418
|
-
let content = readFileSafe(progressPath);
|
|
419
|
-
if (normalizedState) content = updateProgressState(content, normalizedState, registry.locale);
|
|
420
|
-
content = appendProgressLog(content, { event, message, evidence });
|
|
421
|
-
fs.writeFileSync(progressPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
422
|
-
const allowedPaths = [toPosix(path.relative(target.projectRoot, progressPath))];
|
|
423
|
-
const advancedPhasePath = advanceLifecyclePhase(target, taskDir, event);
|
|
424
|
-
if (advancedPhasePath) allowedPaths.push(advancedPhasePath);
|
|
425
|
-
if (event === "task-review") {
|
|
426
|
-
const reviewPath = path.join(taskDir, "review.md");
|
|
427
|
-
const reviewContent = readFileSafe(reviewPath);
|
|
428
|
-
fs.writeFileSync(
|
|
429
|
-
reviewPath,
|
|
430
|
-
replaceAgentReviewSubmission(
|
|
431
|
-
reviewContent,
|
|
432
|
-
renderAgentReviewSubmission({
|
|
433
|
-
target,
|
|
434
|
-
taskDir,
|
|
435
|
-
canonicalTaskId,
|
|
436
|
-
message,
|
|
437
|
-
evidence,
|
|
438
|
-
}),
|
|
439
|
-
),
|
|
440
|
-
);
|
|
441
|
-
allowedPaths.push(toPosix(path.relative(target.projectRoot, reviewPath)));
|
|
442
|
-
const lessonDecisionPath = autoRecordNoLessonCandidateDecision(target, taskDir);
|
|
443
|
-
if (lessonDecisionPath) allowedPaths.push(lessonDecisionPath);
|
|
444
|
-
}
|
|
445
|
-
const task =
|
|
446
|
-
findTaskByDirectory(target, taskDir) ||
|
|
447
|
-
{
|
|
448
|
-
id: canonicalTaskId,
|
|
449
|
-
shortId: path.basename(taskDir),
|
|
450
|
-
title: canonicalTaskId,
|
|
451
|
-
path: `TARGET:${toPosix(path.relative(target.projectRoot, taskDir))}`,
|
|
452
|
-
state: normalizedState || currentTask?.state || "unknown",
|
|
453
|
-
};
|
|
454
|
-
const governanceState = normalizedState || task.state || currentTask?.state || "planned";
|
|
455
|
-
const governance = syncTaskGovernance(target, task, { event, state: governanceState, message, dryRun: false });
|
|
456
|
-
const commit = commitGovernanceSync(governanceContext, [...allowedPaths, ...governanceRelativePaths(governance.changes)], {
|
|
457
|
-
message: `chore(harness): advance task ${canonicalTaskId} to ${governanceState}`,
|
|
458
|
-
});
|
|
459
|
-
return {
|
|
460
|
-
event,
|
|
461
|
-
task,
|
|
462
|
-
governance: { ...governance, commit },
|
|
463
|
-
};
|
|
464
|
-
} finally {
|
|
465
|
-
releaseGovernanceSync(governanceContext);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export function confirmTaskReview(targetInput, taskId, { reviewer = "Human Reviewer", message = "", confirmText = "", evidence = "" } = {}) {
|
|
470
|
-
const target = normalizeTarget(targetInput);
|
|
471
|
-
const taskDir = resolveTaskDirectory(target, taskId);
|
|
472
|
-
return confirmTaskReviewWithContext({ target, taskDir, findTaskByDirectory }, { reviewer, message, confirmText, evidence });
|
|
473
|
-
}
|
|
474
|
-
export function updateTaskPhase(targetInput, taskId, phaseId, { state = "", completion = "", evidenceStatus = "" } = {}) {
|
|
475
|
-
const target = normalizeTarget(targetInput);
|
|
476
|
-
const taskDir = resolveTaskDirectory(target, taskId);
|
|
477
|
-
const visualMapPath = path.join(taskDir, visualMapFile);
|
|
478
|
-
const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
|
|
479
|
-
if (!fs.existsSync(visualMapPath)) {
|
|
480
|
-
if (fs.existsSync(legacyPath)) throw new Error(`Task has legacy visual_roadmap.md only; rewrite it to visual_map.md before task-phase: ${taskId}`);
|
|
481
|
-
throw new Error(`Task visual map not found: ${taskId}`);
|
|
482
|
-
}
|
|
483
|
-
let content = readFileSafe(visualMapPath);
|
|
484
|
-
const normalizedState = state ? String(state).toLowerCase().replaceAll("-", "_") : "";
|
|
485
|
-
if (normalizedState && !allowedPhaseStates.has(normalizedState)) throw new Error(`Invalid phase state: ${state}`);
|
|
486
|
-
const normalizedEvidence = evidenceStatus ? String(evidenceStatus).toLowerCase() : "";
|
|
487
|
-
if (normalizedEvidence && !allowedEvidenceStatus.has(normalizedEvidence)) throw new Error(`Invalid evidence status: ${evidenceStatus}`);
|
|
488
|
-
const nextCompletion = completion === "" ? "" : Number.parseInt(String(completion), 10);
|
|
489
|
-
if (nextCompletion !== "" && (!Number.isInteger(nextCompletion) || nextCompletion < 0 || nextCompletion > 100)) {
|
|
490
|
-
throw new Error(`Invalid completion: ${completion}`);
|
|
491
|
-
}
|
|
492
|
-
const phaseUpdate = updateMarkdownTableRow(content, /^Phase ID$/i, (header, row) => {
|
|
493
|
-
const idIndex = getColumn(header, "Phase ID");
|
|
494
|
-
if ((row[idIndex] || "") !== phaseId) return null;
|
|
495
|
-
const next = [...row];
|
|
496
|
-
const stateIndex = getColumn(header, "State");
|
|
497
|
-
const completionIndex = getColumn(header, "Completion");
|
|
498
|
-
const evidenceIndex = getColumn(header, "Evidence Status");
|
|
499
|
-
if (normalizedState && stateIndex >= 0) next[stateIndex] = normalizedState;
|
|
500
|
-
if (nextCompletion !== "" && completionIndex >= 0) next[completionIndex] = String(nextCompletion);
|
|
501
|
-
if (normalizedEvidence && evidenceIndex >= 0) next[evidenceIndex] = normalizedEvidence;
|
|
502
|
-
return next;
|
|
503
|
-
});
|
|
504
|
-
if (!phaseUpdate.matched) throw new Error(`Phase not found: ${phaseId}`);
|
|
505
|
-
const governanceContext = beginGovernanceSync(target, { operation: `task-phase ${taskId} ${phaseId}` });
|
|
506
|
-
try {
|
|
507
|
-
content = phaseUpdate.content;
|
|
508
|
-
fs.writeFileSync(visualMapPath, content);
|
|
509
|
-
const commit = commitGovernanceSync(governanceContext, [toPosix(path.relative(target.projectRoot, visualMapPath))], {
|
|
510
|
-
message: `chore(harness): update task phase ${taskId} ${phaseId}`,
|
|
511
|
-
});
|
|
512
|
-
return { event: "task-phase", task: findTaskByDirectory(target, taskDir), phaseId, governance: { commit } };
|
|
513
|
-
} finally {
|
|
514
|
-
releaseGovernanceSync(governanceContext);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" } = {}) {
|
|
519
|
-
const target = normalizeTarget(targetInput);
|
|
520
|
-
const normalizedModuleKey = normalizeTaskId(moduleKey);
|
|
521
|
-
const normalizedState = String(state || "done").toLowerCase().replaceAll("_", "-");
|
|
522
|
-
if (!["planned", "in-progress", "done", "blocked", "superseded"].includes(normalizedState)) throw new Error(`Invalid module step state: ${state}`);
|
|
523
|
-
const modulePlanPath = path.join(target.docsRoot, "09-PLANNING/MODULES", normalizedModuleKey, "module_plan.md");
|
|
524
|
-
if (!fs.existsSync(modulePlanPath)) throw new Error(`Module plan not found: ${normalizedModuleKey}`);
|
|
525
|
-
let content = readFileSafe(modulePlanPath);
|
|
526
|
-
const stepUpdate = updateMarkdownTableRow(content, /^(Step ID|步骤 ID)$/i, (header, row) => {
|
|
527
|
-
const idIndex = firstColumn(header, ["Step ID", "步骤 ID"]);
|
|
528
|
-
if ((row[idIndex] || "") !== stepId) return null;
|
|
529
|
-
const next = [...row];
|
|
530
|
-
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
531
|
-
if (statusIndex >= 0) next[statusIndex] = normalizedState;
|
|
532
|
-
return next;
|
|
533
|
-
});
|
|
534
|
-
if (!stepUpdate.matched) throw new Error(`Module step not found: ${stepId}`);
|
|
535
|
-
const governanceContext = beginGovernanceSync(target, { operation: `module-step ${normalizedModuleKey} ${stepId}` });
|
|
536
|
-
try {
|
|
537
|
-
content = stepUpdate.content;
|
|
538
|
-
fs.writeFileSync(modulePlanPath, content);
|
|
539
|
-
|
|
540
|
-
const registryPath = path.join(target.docsRoot, "09-PLANNING/Module-Registry.md");
|
|
541
|
-
if (fs.existsSync(registryPath)) {
|
|
542
|
-
let registry = readFileSafe(registryPath);
|
|
543
|
-
const registryUpdate = updateMarkdownTableRow(registry, /^(ID|模块 Key)$/i, (header, row) => {
|
|
544
|
-
const moduleIndex = firstColumn(header, ["Module", "模块", "模块 Key"]);
|
|
545
|
-
const taskPlanIndex = getColumn(header, "Task Plan");
|
|
546
|
-
const matchesModule = normalizeTaskId(row[moduleIndex] || "") === normalizedModuleKey;
|
|
547
|
-
const matchesPlan = taskPlanIndex >= 0 && String(row[taskPlanIndex] || "").includes(`/MODULES/${normalizedModuleKey}/`);
|
|
548
|
-
if (!matchesModule && !matchesPlan) return null;
|
|
549
|
-
const next = [...row];
|
|
550
|
-
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
551
|
-
const updatedIndex = firstColumn(header, ["Updated", "更新时间"]);
|
|
552
|
-
const currentStepIndex = firstColumn(header, ["Current Step", "当前步骤"]);
|
|
553
|
-
const chineseRegistry = header.some((cell) => /模块 Key|模块名称|状态|更新时间/.test(cell));
|
|
554
|
-
if (statusIndex >= 0) {
|
|
555
|
-
next[statusIndex] = normalizedState === "done"
|
|
556
|
-
? chineseRegistry ? "completed" : "merged"
|
|
557
|
-
: normalizedState === "in-progress" ? chineseRegistry ? "in-progress" : "active" : normalizedState;
|
|
558
|
-
}
|
|
559
|
-
if (currentStepIndex >= 0) next[currentStepIndex] = stepId;
|
|
560
|
-
if (updatedIndex >= 0) next[updatedIndex] = todayDate();
|
|
561
|
-
return next;
|
|
562
|
-
});
|
|
563
|
-
registry = registryUpdate.content;
|
|
564
|
-
fs.writeFileSync(registryPath, registry);
|
|
565
|
-
}
|
|
566
|
-
const governance = syncModuleStepGovernance(target, { moduleKey: normalizedModuleKey, stepId, state: normalizedState });
|
|
567
|
-
const commit = commitGovernanceSync(
|
|
568
|
-
governanceContext,
|
|
569
|
-
[
|
|
570
|
-
toPosix(path.relative(target.projectRoot, modulePlanPath)),
|
|
571
|
-
toPosix(path.relative(target.projectRoot, registryPath)),
|
|
572
|
-
...governanceRelativePaths(governance.changes),
|
|
573
|
-
],
|
|
574
|
-
{ message: `chore(harness): update module ${normalizedModuleKey} step ${stepId}` },
|
|
575
|
-
);
|
|
576
|
-
return { event: "module-step", moduleKey: normalizedModuleKey, stepId, state: normalizedState, governance: { ...governance, commit } };
|
|
577
|
-
} finally {
|
|
578
|
-
releaseGovernanceSync(governanceContext);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
export function listLifecycleTasks(targetInput, { state = "", moduleKey = "", queue = "", preset = "", review = "", lesson = "", search = "", missingMaterials = false } = {}) {
|
|
583
|
-
const target = normalizeTarget(targetInput);
|
|
584
|
-
let tasks = collectTasks(target);
|
|
585
|
-
if (state) tasks = tasks.filter((task) => task.state === String(state).toLowerCase().replaceAll("-", "_"));
|
|
586
|
-
if (moduleKey) tasks = tasks.filter((task) => task.module === normalizeTaskId(moduleKey));
|
|
587
|
-
if (queue) {
|
|
588
|
-
const normalizedQueue = queryToken(queue);
|
|
589
|
-
tasks = tasks.filter((task) => (task.taskQueues || []).map(queryToken).includes(normalizedQueue));
|
|
590
|
-
}
|
|
591
|
-
if (preset) tasks = tasks.filter((task) => queryToken(task.taskPreset || "none") === queryToken(preset));
|
|
592
|
-
if (review) tasks = tasks.filter((task) => queryToken(task.reviewStatus || "") === queryToken(review));
|
|
593
|
-
if (lesson) {
|
|
594
|
-
const needle = queryToken(lesson);
|
|
595
|
-
tasks = tasks.filter((task) => [task.lessonCandidateStatus, task.lessonCandidateReviewDecision, task.lessonCandidatePromotionState].some((value) => queryToken(value) === needle));
|
|
596
|
-
}
|
|
597
|
-
if (missingMaterials) tasks = tasks.filter((task) => !task.materialsReady);
|
|
598
|
-
if (search) {
|
|
599
|
-
const needle = String(search).toLowerCase();
|
|
600
|
-
tasks = tasks.filter((task) => [
|
|
601
|
-
task.id,
|
|
602
|
-
task.taskKey,
|
|
603
|
-
task.shortId,
|
|
604
|
-
task.title,
|
|
605
|
-
task.currentPath,
|
|
606
|
-
task.taskPlanPath,
|
|
607
|
-
task.module,
|
|
608
|
-
task.inferredModule,
|
|
609
|
-
].some((value) => String(value || "").toLowerCase().includes(needle)));
|
|
610
|
-
}
|
|
611
|
-
return { tasks };
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function queryToken(value) {
|
|
615
|
-
return String(value || "").trim().toLowerCase().replaceAll("_", "-");
|
|
616
|
-
}
|