coding-agent-harness 1.0.7 → 1.1.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/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +9 -5
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +32 -6
- package/dist/check-dist-observation.mjs +73 -28
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +88 -0
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +67 -8
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +65 -4
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +111 -53
- package/dist/harness.mjs +6 -303
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +5 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +68 -29
- package/dist/lib/preset-registry.mjs +374 -72
- package/dist/lib/preset-runner.mjs +560 -0
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +4 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +117 -159
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +38 -17
- package/dist/lib/task-scanner.mjs +75 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +187 -18
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +2 -1
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- 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 +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +19 -11
- package/postinstall.mjs +37 -0
- 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/release-closeout/checks/check-release-package.mjs +29 -0
- package/presets/release-closeout/preset.yaml +100 -0
- package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
- package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
- package/presets/release-closeout/templates/findings.seed.md +5 -0
- package/presets/release-closeout/templates/review.seed.md +3 -0
- package/presets/release-closeout/templates/task_plan.append.md +24 -0
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/pull-request-standard.md +2 -2
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/pull-request-standard.md +2 -2
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- 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 +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- 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/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/pull-request-standard.md +1 -1
- 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 +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- 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 +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import crypto from "node:crypto";
|
|
5
|
-
import { visualMapFile, legacyVisualRoadmapFile,
|
|
4
|
+
import { visualMapFile, legacyVisualRoadmapFile, allowedTaskStates, allowedTaskBudgets, allowedPhaseStates, allowedEvidenceStatus, normalizeTarget, normalizeLocale, toPosix, readFileSafe, readBundledTemplate, todayDate, localDate, datePrefix, normalizeTaskId, renderTaskTemplate } from "./core-shared.mjs";
|
|
6
5
|
import { readCapabilityRegistry } from "./capability-registry.mjs";
|
|
7
6
|
import { readPresetPackage } from "./preset-registry.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { renderPresetResourceIndex } from "./preset-engine.mjs";
|
|
8
|
+
import { parseTaskBudget } from "./task-metadata.mjs";
|
|
9
|
+
import { createScannerTaskRepository } from "./task-repository.mjs";
|
|
10
10
|
import { getColumn, firstColumn, updateMarkdownTableRow } from "./markdown-utils.mjs";
|
|
11
11
|
import { validateLifecycleTransition, validateReviewEntryGate } from "./task-lifecycle/review-gates.mjs";
|
|
12
12
|
import { advanceLifecyclePhase, autoRecordNoLessonCandidateDecision } from "./task-lifecycle/phase-sync.mjs";
|
|
13
|
-
import { confirmTaskReview as confirmTaskReviewWithContext } from "./task-lifecycle/review-confirm.mjs";
|
|
13
|
+
import { confirmTaskReview as confirmTaskReviewWithContext, finalizeDeferredTaskReviewConfirmation as finalizeDeferredTaskReviewConfirmationWithContext } from "./task-lifecycle/review-confirm.mjs";
|
|
14
14
|
import { appendProgressLog, markWalkthroughClosed } from "./task-lifecycle/text-utils.mjs";
|
|
15
15
|
import { buildScaffoldProvenance } from "./task-lifecycle/scaffold-provenance.mjs";
|
|
16
16
|
import { buildCreationTaskAudit } from "./task-audit-metadata.mjs";
|
|
17
|
-
import { renderAgentReviewSubmission, replaceAgentReviewSubmission
|
|
18
|
-
import { appendLongRunningContractFile, moduleTemplateFiles, taskFilesForBudget
|
|
19
|
-
import { planCreateTaskChanges, refreshPresetCommandAudit, resolveImplicitCreateTarget
|
|
20
|
-
import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync, syncModuleStepGovernance, syncTaskGovernance
|
|
21
|
-
import {
|
|
17
|
+
import { renderAgentReviewSubmission, replaceAgentReviewSubmission } from "./task-lifecycle/review-submission.mjs";
|
|
18
|
+
import { appendLongRunningContractFile, moduleTemplateFiles, taskFilesForBudget } from "./task-lifecycle/template-files.mjs";
|
|
19
|
+
import { planCreateTaskChanges, refreshPresetCommandAudit, resolveImplicitCreateTarget } from "./task-lifecycle/create-task-helpers.mjs";
|
|
20
|
+
import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync, syncModuleStepGovernance, syncTaskGovernance } from "./governance-sync.mjs";
|
|
21
|
+
import { normalizeHarnessModuleKey, prepareModuleRegistration, prepareModuleStepRegistrationUpdate, readHarnessModules, registeredHarnessModule } from "./module-registry.mjs";
|
|
22
|
+
import { assertLifecyclePresetWriteScope, buildLifecyclePresetContext, evaluatePresetValues, renderLifecyclePresetTaskTemplate, resolveLifecyclePresetInputs } from "./task-lifecycle/preset-interop.mjs";
|
|
23
|
+
import { taskIdFromDirectory } from "./harness-paths.mjs";
|
|
24
|
+
function asLifecycleTarget(target) {
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
27
|
+
function changeDestinations(changes) {
|
|
28
|
+
return changes.map((change) => change.destination).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
function lifecycleGateEvent(event) {
|
|
31
|
+
return String(event || "task-log");
|
|
32
|
+
}
|
|
33
|
+
function findReviewTaskByDirectory(target, taskDir) {
|
|
34
|
+
return findTaskByDirectory(asLifecycleTarget(normalizeTarget(target.projectRoot)), taskDir);
|
|
35
|
+
}
|
|
22
36
|
function taskRoot(target, taskId, { moduleKey = "" } = {}) {
|
|
23
37
|
const normalizedTaskId = normalizeTaskId(taskId);
|
|
24
38
|
if (moduleKey) {
|
|
@@ -30,59 +44,18 @@ function taskRoot(target, taskId, { moduleKey = "" } = {}) {
|
|
|
30
44
|
return path.join(target.harness.tasksRoot, normalizedTaskId);
|
|
31
45
|
}
|
|
32
46
|
export function resolveTaskDirectory(target, taskRef) {
|
|
33
|
-
|
|
34
|
-
.replace(/^coding-agent-harness\/planning\//, "")
|
|
35
|
-
.replace(/^planning\//, "")
|
|
36
|
-
.replace(new RegExp(`^${legacyPlanningPrefix()}\\/`), "")
|
|
37
|
-
.replace(/^\/+/, "");
|
|
38
|
-
if (!raw)
|
|
39
|
-
throw new Error("Missing task id");
|
|
40
|
-
const direct = directTaskRefPath(target, raw);
|
|
41
|
-
if (direct && fs.existsSync(path.join(direct, "task_plan.md")))
|
|
42
|
-
return direct;
|
|
43
|
-
const normalized = normalizeTaskId(raw);
|
|
44
|
-
const candidates = listTaskPlanPaths(target)
|
|
45
|
-
.map((taskPlanPath) => path.dirname(taskPlanPath))
|
|
46
|
-
.filter((taskDir) => {
|
|
47
|
-
const id = taskIdForDirectory(target, taskDir);
|
|
48
|
-
const dirName = path.basename(taskDir);
|
|
49
|
-
return id === raw || id.endsWith(`/${raw}`) || dirName === normalized;
|
|
50
|
-
});
|
|
51
|
-
if (candidates.length === 1)
|
|
52
|
-
return candidates[0];
|
|
53
|
-
if (candidates.length > 1) {
|
|
54
|
-
const options = candidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
|
|
55
|
-
throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
|
|
56
|
-
}
|
|
57
|
-
// Try bare slug resolution: match normalized slug against dated directories
|
|
58
|
-
if (!datePrefix.test(normalized)) {
|
|
59
|
-
const datedCandidates = listTaskPlanPaths(target)
|
|
60
|
-
.map((taskPlanPath) => path.dirname(taskPlanPath))
|
|
61
|
-
.filter((taskDir) => {
|
|
62
|
-
const dirName = path.basename(taskDir);
|
|
63
|
-
return datePrefix.test(dirName) && dirName.replace(datePrefix, "") === normalized;
|
|
64
|
-
});
|
|
65
|
-
if (datedCandidates.length === 1)
|
|
66
|
-
return datedCandidates[0];
|
|
67
|
-
if (datedCandidates.length > 1) {
|
|
68
|
-
const options = datedCandidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
|
|
69
|
-
throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const legacy = taskRoot(target, normalized);
|
|
73
|
-
if (fs.existsSync(path.join(legacy, "task_plan.md")))
|
|
74
|
-
return legacy;
|
|
75
|
-
throw new Error(`Task not found: ${taskRef}`);
|
|
76
|
-
}
|
|
77
|
-
function directTaskRefPath(target, raw) {
|
|
78
|
-
return taskRefPath(target.harness, raw);
|
|
47
|
+
return createScannerTaskRepository(target).resolve({ id: taskRef }).directory;
|
|
79
48
|
}
|
|
80
|
-
function
|
|
81
|
-
return
|
|
49
|
+
function taskIdForDirectory(target, taskDir) {
|
|
50
|
+
return taskIdFromDirectory(target.harness, taskDir);
|
|
82
51
|
}
|
|
83
52
|
function findTaskByDirectory(target, taskDir) {
|
|
84
|
-
|
|
85
|
-
|
|
53
|
+
try {
|
|
54
|
+
return createScannerTaskRepository(target).get({ path: taskDir });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
86
59
|
}
|
|
87
60
|
function stateLabel(state, locale) {
|
|
88
61
|
if (normalizeLocale(locale) !== "zh-CN")
|
|
@@ -150,13 +123,13 @@ function resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey,
|
|
|
150
123
|
}
|
|
151
124
|
throw new Error(`Unable to allocate automatic task id for: ${semanticSlug}`);
|
|
152
125
|
}
|
|
153
|
-
export function createTask(targetInput, taskId, { title = "", locale = "en-US", dryRun = false, moduleKey = "", budget = "standard", longRunning = false, preset = "", fromSession = "", presetArgs = [], automaticTaskId = false } = {}) {
|
|
126
|
+
export function createTask(targetInput, taskId, { title = "", locale = "en-US", dryRun = false, moduleKey = "", budget = "standard", longRunning = false, preset = "", fromSession = "", presetArgs = [], automaticTaskId = false, deferCommit = false, allowDirtyRelativePaths = [], registerModule = false, moduleRegistration = {} } = {}) {
|
|
154
127
|
const requestedPreset = preset || (moduleKey ? "module" : "");
|
|
155
128
|
const presetTargetInput = resolveImplicitCreateTarget(targetInput, fromSession);
|
|
156
129
|
const normalizedPreset = normalizeTaskPresetInput(requestedPreset, { targetInput: presetTargetInput });
|
|
157
130
|
const presetPackage = normalizedPreset === "none" ? null : readPresetPackage(normalizedPreset, { targetInput: presetTargetInput });
|
|
158
|
-
const presetInputs = presetPackage ?
|
|
159
|
-
const target = normalizeTarget(presetInputs?.targetInput || presetTargetInput || targetInput);
|
|
131
|
+
const presetInputs = presetPackage ? resolveLifecyclePresetInputs(presetPackage, { cliArgs: presetArgs, fromSession, targetInput: presetTargetInput }) : null;
|
|
132
|
+
const target = asLifecycleTarget(normalizeTarget(presetInputs?.targetInput || presetTargetInput || targetInput));
|
|
160
133
|
if (presetInputs?.targetInput && targetInput && targetInput !== "." && path.resolve(targetInput) !== path.resolve(presetInputs.targetInput)) {
|
|
161
134
|
throw new Error(`--from-session target mismatch: session target is ${presetInputs.targetInput}`);
|
|
162
135
|
}
|
|
@@ -167,7 +140,15 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
167
140
|
throw new Error(`${normalizedPreset} preset is project-level and cannot be combined with --module`);
|
|
168
141
|
if (presetPackage?.task?.requiresFromSession === true && !fromSession)
|
|
169
142
|
throw new Error(`${normalizedPreset} preset requires --from-session`);
|
|
170
|
-
const normalizedModuleKey = moduleKey ?
|
|
143
|
+
const normalizedModuleKey = moduleKey ? normalizeHarnessModuleKey(moduleKey) : "";
|
|
144
|
+
const plannedModuleRegistration = normalizedModuleKey && !registeredHarnessModule(target, normalizedModuleKey)
|
|
145
|
+
? registerModule
|
|
146
|
+
? prepareModuleRegistration(target, normalizedModuleKey, moduleRegistration, { dryRun: true })
|
|
147
|
+
: null
|
|
148
|
+
: null;
|
|
149
|
+
if (normalizedModuleKey && !registeredHarnessModule(target, normalizedModuleKey) && !plannedModuleRegistration) {
|
|
150
|
+
throw new Error(`Unknown module: ${normalizedModuleKey}. Register it first with: harness module register ${normalizedModuleKey} --title <title> --prefix <PREFIX> --scope <path> ${target.projectRoot}`);
|
|
151
|
+
}
|
|
171
152
|
const identity = resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey: normalizedModuleKey, automaticTaskId });
|
|
172
153
|
const normalizedTaskId = identity.normalizedTaskId;
|
|
173
154
|
const semanticSlug = identity.semanticSlug;
|
|
@@ -190,9 +171,9 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
190
171
|
automaticTaskId,
|
|
191
172
|
});
|
|
192
173
|
const baseTaskAudit = buildCreationTaskAudit(scaffoldProvenance, { projectRoot: target.projectRoot });
|
|
193
|
-
const evaluatedPresetValues = presetPackage ?
|
|
194
|
-
const presetContext = presetPackage
|
|
195
|
-
?
|
|
174
|
+
const evaluatedPresetValues = presetPackage && presetInputs ? evaluatePresetValues(presetPackage, presetInputs.inputs, { taskId: normalizedTaskId, taskTitle, moduleKey: normalizedModuleKey, target }) : null;
|
|
175
|
+
const presetContext = presetPackage && presetInputs && evaluatedPresetValues
|
|
176
|
+
? buildLifecyclePresetContext({ ...presetPackage, task: { ...(presetPackage.task || {}), kind: presetPackage.task?.kind || "general" } }, {
|
|
196
177
|
target,
|
|
197
178
|
taskDir: directory,
|
|
198
179
|
taskId: normalizedTaskId,
|
|
@@ -225,14 +206,17 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
225
206
|
normalizedLocale,
|
|
226
207
|
normalizedBudget,
|
|
227
208
|
longRunning,
|
|
228
|
-
presetContext,
|
|
229
|
-
task,
|
|
209
|
+
presetContext: presetContext || undefined,
|
|
230
210
|
});
|
|
231
211
|
const plannedGovernance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun: true });
|
|
232
|
-
const plannedWriteScopes = governanceRelativePaths([...plannedChanges, ...plannedGovernance.changes]
|
|
212
|
+
const plannedWriteScopes = [...governanceRelativePaths(plannedModuleRegistration?.changes || []), ...changeDestinations(plannedChanges), ...governanceRelativePaths(plannedGovernance.changes)];
|
|
233
213
|
const changes = [];
|
|
234
|
-
const governanceContext = beginGovernanceSync(target, { operation: `new-task ${normalizedTaskId}`, dryRun, allowDirtyWorktree: true, allowedRelativePaths: plannedWriteScopes });
|
|
214
|
+
const governanceContext = beginGovernanceSync(target, { operation: `new-task ${normalizedTaskId}`, dryRun, allowDirtyWorktree: true, allowedRelativePaths: [...plannedWriteScopes, ...(allowDirtyRelativePaths || [])], allowDirtyWriteScope: deferCommit });
|
|
235
215
|
try {
|
|
216
|
+
if (plannedModuleRegistration) {
|
|
217
|
+
const moduleRegistrationResult = prepareModuleRegistration(target, normalizedModuleKey, moduleRegistration, { dryRun });
|
|
218
|
+
changes.push(...moduleRegistrationResult.changes);
|
|
219
|
+
}
|
|
236
220
|
if (normalizedModuleKey) {
|
|
237
221
|
const moduleDirectory = target.harness.version === 2
|
|
238
222
|
? path.join(target.harness.modulesRoot, normalizedModuleKey)
|
|
@@ -247,7 +231,7 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
247
231
|
action: dryRun ? "would-create" : "create",
|
|
248
232
|
});
|
|
249
233
|
if (presetPackage)
|
|
250
|
-
|
|
234
|
+
assertLifecyclePresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)), target);
|
|
251
235
|
if (dryRun)
|
|
252
236
|
continue;
|
|
253
237
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
@@ -263,6 +247,7 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
263
247
|
longRunning,
|
|
264
248
|
scaffoldProvenance,
|
|
265
249
|
taskAudit: buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot }),
|
|
250
|
+
target,
|
|
266
251
|
}));
|
|
267
252
|
}
|
|
268
253
|
}
|
|
@@ -278,11 +263,11 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
278
263
|
action: dryRun ? "would-create" : "create",
|
|
279
264
|
});
|
|
280
265
|
if (presetPackage)
|
|
281
|
-
|
|
266
|
+
assertLifecyclePresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)), target);
|
|
282
267
|
if (dryRun)
|
|
283
268
|
continue;
|
|
284
269
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
285
|
-
fs.writeFileSync(destinationPath,
|
|
270
|
+
fs.writeFileSync(destinationPath, renderLifecyclePresetTaskTemplate(destination, renderTaskTemplate(readBundledTemplate(source), {
|
|
286
271
|
taskId: normalizedTaskId,
|
|
287
272
|
title: taskTitle,
|
|
288
273
|
locale: normalizedLocale,
|
|
@@ -299,17 +284,19 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
299
284
|
taskAudit: destination === "INDEX.md"
|
|
300
285
|
? buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot })
|
|
301
286
|
: baseTaskAudit,
|
|
287
|
+
target,
|
|
302
288
|
}), presetContext));
|
|
303
289
|
}
|
|
304
290
|
if (presetContext) {
|
|
305
|
-
for (const evidence of presetContext.evidenceFiles) {
|
|
291
|
+
for (const evidence of presetContext.evidenceFiles || []) {
|
|
306
292
|
const destinationPath = path.join(target.projectRoot, evidence.relativePath);
|
|
307
293
|
changes.push({
|
|
308
294
|
destination: toPosix(evidence.relativePath),
|
|
309
295
|
source: evidence.source,
|
|
310
296
|
action: dryRun ? "would-create" : "create",
|
|
311
297
|
});
|
|
312
|
-
|
|
298
|
+
if (presetPackage)
|
|
299
|
+
assertLifecyclePresetWriteScope(presetPackage, toPosix(evidence.relativePath), target);
|
|
313
300
|
if (dryRun)
|
|
314
301
|
continue;
|
|
315
302
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
@@ -322,7 +309,8 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
322
309
|
source: resource.source,
|
|
323
310
|
action: dryRun ? "would-create" : "create",
|
|
324
311
|
});
|
|
325
|
-
|
|
312
|
+
if (presetPackage)
|
|
313
|
+
assertLifecyclePresetWriteScope(presetPackage, toPosix(resource.relativePath), target);
|
|
326
314
|
if (dryRun)
|
|
327
315
|
continue;
|
|
328
316
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
@@ -339,7 +327,8 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
339
327
|
source: `preset-${kind}-index`,
|
|
340
328
|
action: dryRun ? "would-update" : "update",
|
|
341
329
|
});
|
|
342
|
-
|
|
330
|
+
if (presetPackage)
|
|
331
|
+
assertLifecyclePresetWriteScope(presetPackage, relativePath, target);
|
|
343
332
|
if (dryRun)
|
|
344
333
|
continue;
|
|
345
334
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
@@ -349,14 +338,12 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
349
338
|
}
|
|
350
339
|
const governance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun });
|
|
351
340
|
changes.push(...governance.changes);
|
|
352
|
-
const commandWriteScopes = governanceRelativePaths(changes);
|
|
341
|
+
const commandWriteScopes = [...changeDestinations(changes), ...governanceRelativePaths(governance.changes)];
|
|
353
342
|
if (presetContext) {
|
|
354
343
|
refreshPresetCommandAudit(target, presetContext, { commandWriteScopes, dryRun });
|
|
355
344
|
task.presetAudit = presetContext.audit;
|
|
356
345
|
}
|
|
357
|
-
const commit = commitGovernanceSync(governanceContext, commandWriteScopes, {
|
|
358
|
-
message: `chore(harness): register task ${task.id}`,
|
|
359
|
-
});
|
|
346
|
+
const commit = deferCommit ? { committed: false, reason: "deferred", allowedPaths: commandWriteScopes } : commitGovernanceSync(governanceContext, commandWriteScopes, { message: `chore(harness): register task ${task.id}` });
|
|
360
347
|
return {
|
|
361
348
|
dryRun,
|
|
362
349
|
task,
|
|
@@ -369,18 +356,20 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
|
|
|
369
356
|
}
|
|
370
357
|
}
|
|
371
358
|
export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", state = "", message = "", evidence = "" } = {}) {
|
|
372
|
-
const target = normalizeTarget(targetInput);
|
|
359
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
360
|
+
const normalizedEvent = lifecycleGateEvent(event);
|
|
373
361
|
const taskDir = resolveTaskDirectory(target, taskId);
|
|
374
362
|
const progressPath = path.join(taskDir, "progress.md");
|
|
375
363
|
const registry = readCapabilityRegistry(target);
|
|
376
364
|
const normalizedState = state ? String(state).toLowerCase().replaceAll("-", "_") : "";
|
|
377
365
|
if (normalizedState && !allowedTaskStates.has(normalizedState))
|
|
378
366
|
throw new Error(`Invalid task state: ${state}`);
|
|
367
|
+
assertLifecycleEventStateConsistency(normalizedEvent, normalizedState);
|
|
379
368
|
const currentTask = findTaskByDirectory(target, taskDir);
|
|
380
369
|
const canonicalTaskId = taskIdForDirectory(target, taskDir);
|
|
381
370
|
const budget = parseTaskBudget(readFileSafe(path.join(taskDir, "task_plan.md")));
|
|
382
371
|
validateLifecycleTransition({
|
|
383
|
-
event,
|
|
372
|
+
event: normalizedEvent,
|
|
384
373
|
currentState: currentTask?.state || "unknown",
|
|
385
374
|
budget,
|
|
386
375
|
reviewContent: readFileSafe(path.join(taskDir, "review.md")),
|
|
@@ -395,11 +384,11 @@ export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", s
|
|
|
395
384
|
try {
|
|
396
385
|
let content = readFileSafe(progressPath);
|
|
397
386
|
if (normalizedState)
|
|
398
|
-
content = updateProgressState(content, normalizedState, registry.locale);
|
|
387
|
+
content = updateProgressState(content, normalizedState, registry.locale || "en-US");
|
|
399
388
|
content = appendProgressLog(content, { event, message, evidence });
|
|
400
389
|
fs.writeFileSync(progressPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
401
390
|
const allowedPaths = [toPosix(path.relative(target.projectRoot, progressPath))];
|
|
402
|
-
const advancedPhasePath = advanceLifecyclePhase(target, taskDir,
|
|
391
|
+
const advancedPhasePath = advanceLifecyclePhase(target, taskDir, normalizedEvent);
|
|
403
392
|
if (advancedPhasePath)
|
|
404
393
|
allowedPaths.push(advancedPhasePath);
|
|
405
394
|
if (event === "task-review") {
|
|
@@ -446,13 +435,26 @@ export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", s
|
|
|
446
435
|
releaseGovernanceSync(governanceContext);
|
|
447
436
|
}
|
|
448
437
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
438
|
+
function assertLifecycleEventStateConsistency(event, state) {
|
|
439
|
+
if (!state)
|
|
440
|
+
return;
|
|
441
|
+
if (state === "done" && event !== "task-complete") {
|
|
442
|
+
throw new Error(`State done must be written through task-complete, not ${event}.`);
|
|
443
|
+
}
|
|
444
|
+
if (state === "review" && event !== "task-review") {
|
|
445
|
+
throw new Error(`State review must be written through task-review, not ${event}.`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
export function confirmTaskReview(targetInput, taskId, { reviewer = "Human Reviewer", message = "", confirmText = "", evidence = "", deferCommit = false } = {}) {
|
|
449
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
450
|
+
return confirmTaskReviewWithContext({ target, taskDir: resolveTaskDirectory(target, taskId), findTaskByDirectory: findReviewTaskByDirectory }, { reviewer, message, confirmText, evidence, deferCommit });
|
|
451
|
+
}
|
|
452
|
+
export function finalizeDeferredTaskReviewConfirmation(targetInput, taskId, { commitSha = "" } = {}) {
|
|
453
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
454
|
+
return finalizeDeferredTaskReviewConfirmationWithContext({ target, taskDir: resolveTaskDirectory(target, taskId), findTaskByDirectory: findReviewTaskByDirectory }, { commitSha });
|
|
453
455
|
}
|
|
454
456
|
export function updateTaskPhase(targetInput, taskId, phaseId, { state = "", completion = "", evidenceStatus = "" } = {}) {
|
|
455
|
-
const target = normalizeTarget(targetInput);
|
|
457
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
456
458
|
const taskDir = resolveTaskDirectory(target, taskId);
|
|
457
459
|
const visualMapPath = path.join(taskDir, visualMapFile);
|
|
458
460
|
const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
|
|
@@ -504,8 +506,8 @@ export function updateTaskPhase(targetInput, taskId, phaseId, { state = "", comp
|
|
|
504
506
|
}
|
|
505
507
|
}
|
|
506
508
|
export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" } = {}) {
|
|
507
|
-
const target = normalizeTarget(targetInput);
|
|
508
|
-
const normalizedModuleKey =
|
|
509
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
510
|
+
const normalizedModuleKey = normalizeHarnessModuleKey(moduleKey);
|
|
509
511
|
const normalizedState = String(state || "done").toLowerCase().replaceAll("_", "-");
|
|
510
512
|
if (!["planned", "in-progress", "done", "blocked", "superseded"].includes(normalizedState))
|
|
511
513
|
throw new Error(`Invalid module step state: ${state}`);
|
|
@@ -529,39 +531,11 @@ export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" }
|
|
|
529
531
|
try {
|
|
530
532
|
content = stepUpdate.content;
|
|
531
533
|
fs.writeFileSync(modulePlanPath, content);
|
|
532
|
-
const
|
|
533
|
-
if (fs.existsSync(registryPath)) {
|
|
534
|
-
let registry = readFileSafe(registryPath);
|
|
535
|
-
const registryUpdate = updateMarkdownTableRow(registry, /^(ID|模块 Key)$/i, (header, row) => {
|
|
536
|
-
const moduleIndex = firstColumn(header, ["Module", "模块", "模块 Key"]);
|
|
537
|
-
const taskPlanIndex = getColumn(header, "Task Plan");
|
|
538
|
-
const matchesModule = normalizeTaskId(row[moduleIndex] || "") === normalizedModuleKey;
|
|
539
|
-
const matchesPlan = taskPlanIndex >= 0 && String(row[taskPlanIndex] || "").includes(`/MODULES/${normalizedModuleKey}/`);
|
|
540
|
-
if (!matchesModule && !matchesPlan)
|
|
541
|
-
return null;
|
|
542
|
-
const next = [...row];
|
|
543
|
-
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
544
|
-
const updatedIndex = firstColumn(header, ["Updated", "更新时间"]);
|
|
545
|
-
const currentStepIndex = firstColumn(header, ["Current Step", "当前步骤"]);
|
|
546
|
-
const chineseRegistry = header.some((cell) => /模块 Key|模块名称|状态|更新时间/.test(cell));
|
|
547
|
-
if (statusIndex >= 0) {
|
|
548
|
-
next[statusIndex] = normalizedState === "done"
|
|
549
|
-
? chineseRegistry ? "completed" : "merged"
|
|
550
|
-
: normalizedState === "in-progress" ? chineseRegistry ? "in-progress" : "active" : normalizedState;
|
|
551
|
-
}
|
|
552
|
-
if (currentStepIndex >= 0)
|
|
553
|
-
next[currentStepIndex] = stepId;
|
|
554
|
-
if (updatedIndex >= 0)
|
|
555
|
-
next[updatedIndex] = todayDate();
|
|
556
|
-
return next;
|
|
557
|
-
});
|
|
558
|
-
registry = registryUpdate.content;
|
|
559
|
-
fs.writeFileSync(registryPath, registry);
|
|
560
|
-
}
|
|
534
|
+
const moduleRegistration = prepareModuleStepRegistrationUpdate(target, normalizedModuleKey, { stepId, state: normalizedState });
|
|
561
535
|
const governance = syncModuleStepGovernance(target, { moduleKey: normalizedModuleKey, stepId, state: normalizedState });
|
|
562
536
|
const commit = commitGovernanceSync(governanceContext, [
|
|
563
537
|
toPosix(path.relative(target.projectRoot, modulePlanPath)),
|
|
564
|
-
|
|
538
|
+
...governanceRelativePaths(moduleRegistration.changes),
|
|
565
539
|
...governanceRelativePaths(governance.changes),
|
|
566
540
|
], { message: `chore(harness): update module ${normalizedModuleKey} step ${stepId}` });
|
|
567
541
|
return { event: "module-step", moduleKey: normalizedModuleKey, stepId, state: normalizedState, governance: { ...governance, commit } };
|
|
@@ -570,42 +544,26 @@ export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" }
|
|
|
570
544
|
releaseGovernanceSync(governanceContext);
|
|
571
545
|
}
|
|
572
546
|
}
|
|
573
|
-
export function listLifecycleTasks(targetInput, { state = "", moduleKey = "", queue = "", preset = "", review = "", lesson = "", search = "", missingMaterials = false } = {}) {
|
|
574
|
-
const target = normalizeTarget(targetInput);
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
tasks = tasks.filter((task) => [task.lessonCandidateStatus, task.lessonCandidateReviewDecision, task.lessonCandidatePromotionState].some((value) => queryToken(value) === needle));
|
|
547
|
+
export function listLifecycleTasks(targetInput, { state = "", moduleKey = "", queue = "", preset = "", review = "", lesson = "", search = "", missingMaterials = false, includeArchived = false } = {}) {
|
|
548
|
+
const target = asLifecycleTarget(normalizeTarget(targetInput));
|
|
549
|
+
const tasks = createScannerTaskRepository(target).list({
|
|
550
|
+
includeArchived,
|
|
551
|
+
state,
|
|
552
|
+
module: moduleKey ? normalizeHarnessModuleKey(moduleKey) : "",
|
|
553
|
+
queue,
|
|
554
|
+
preset,
|
|
555
|
+
review,
|
|
556
|
+
lesson,
|
|
557
|
+
missingMaterials,
|
|
558
|
+
search,
|
|
559
|
+
});
|
|
560
|
+
let modules = [];
|
|
561
|
+
try {
|
|
562
|
+
const registry = readHarnessModules(target);
|
|
563
|
+
modules = Object.entries(registry.items || {}).map(([key, module]) => ({ key, ...module }));
|
|
591
564
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (search) {
|
|
595
|
-
const needle = String(search).toLowerCase();
|
|
596
|
-
tasks = tasks.filter((task) => [
|
|
597
|
-
task.id,
|
|
598
|
-
task.taskKey,
|
|
599
|
-
task.shortId,
|
|
600
|
-
task.title,
|
|
601
|
-
task.currentPath,
|
|
602
|
-
task.taskPlanPath,
|
|
603
|
-
task.module,
|
|
604
|
-
task.inferredModule,
|
|
605
|
-
].some((value) => String(value || "").toLowerCase().includes(needle)));
|
|
565
|
+
catch {
|
|
566
|
+
modules = [];
|
|
606
567
|
}
|
|
607
|
-
return { tasks };
|
|
608
|
-
}
|
|
609
|
-
function queryToken(value) {
|
|
610
|
-
return String(value || "").trim().toLowerCase().replaceAll("_", "-");
|
|
568
|
+
return { tasks, modules };
|
|
611
569
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Dynamic metadata parsing stays behavior-first until the metadata domain model PR.
|
|
3
|
-
import { allowedTaskStates,
|
|
2
|
+
import { allowedTaskStates, taskContractMarker, } from "./core-shared.mjs";
|
|
4
3
|
import { tableAfterHeading, firstColumn } from "./markdown-utils.mjs";
|
|
5
4
|
export function parseTaskState(progressContent) {
|
|
6
5
|
return parseTaskStateInfo(progressContent).state;
|
|
@@ -12,7 +11,7 @@ export function parseTaskBudget(taskPlanContent) {
|
|
|
12
11
|
return "standard";
|
|
13
12
|
const raw = match[1].replace(/`/g, "").trim().toLowerCase();
|
|
14
13
|
const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
|
|
15
|
-
if (
|
|
14
|
+
if (isTaskBudget(normalized))
|
|
16
15
|
return normalized;
|
|
17
16
|
if (["long-running", "longrunning", "module-parallel"].includes(normalized))
|
|
18
17
|
return "complex";
|
|
@@ -76,7 +75,7 @@ export function parseTaskStateInfo(progressContent) {
|
|
|
76
75
|
["pending", "planned"],
|
|
77
76
|
]);
|
|
78
77
|
const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
79
|
-
return
|
|
78
|
+
return isKnownTaskState(normalized)
|
|
80
79
|
? { state: normalized, source: "explicit", raw }
|
|
81
80
|
: { state: "unknown", source: "invalid", raw };
|
|
82
81
|
}
|
|
@@ -85,7 +84,7 @@ function inferLegacyTaskState(progressContent) {
|
|
|
85
84
|
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
86
85
|
if (statusIndex < 0 || rows.length === 0)
|
|
87
86
|
return { state: "unknown", source: "missing", raw: "" };
|
|
88
|
-
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(
|
|
87
|
+
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(isKnownTaskState);
|
|
89
88
|
if (states.includes("blocked"))
|
|
90
89
|
return { state: "blocked", source: "legacy-table", raw: "blocked" };
|
|
91
90
|
if (states.includes("in_progress"))
|
|
@@ -114,3 +113,9 @@ function normalizeLegacyState(value) {
|
|
|
114
113
|
return "planned";
|
|
115
114
|
return "";
|
|
116
115
|
}
|
|
116
|
+
function isKnownTaskState(value) {
|
|
117
|
+
return typeof value === "string" && allowedTaskStates.has(value);
|
|
118
|
+
}
|
|
119
|
+
function isTaskBudget(value) {
|
|
120
|
+
return value === "simple" || value === "standard" || value === "complex";
|
|
121
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { validateTaskPresetAuditSnapshot } from "./preset-audit-contracts.mjs";
|
|
4
|
+
import { validatePresetResourcesForTask } from "./preset-resource-contracts.mjs";
|
|
5
|
+
export function validateRegularTaskPresetContract(target, task, presetPackage) {
|
|
6
|
+
const failures = [];
|
|
7
|
+
const warnings = [];
|
|
8
|
+
const driftWarnings = [];
|
|
9
|
+
if (presetPackage.task?.kind && task.taskKind !== presetPackage.task.kind) {
|
|
10
|
+
driftWarnings.push(`${task.path} ${task.taskPreset} preset Task Kind mismatch: expected ${presetPackage.task.kind}, got ${task.taskKind || "(missing)"}`);
|
|
11
|
+
}
|
|
12
|
+
if (String(task.presetVersion || "") !== String(presetPackage.version)) {
|
|
13
|
+
driftWarnings.push(`${task.path} ${task.taskPreset} preset version drift: task ${task.presetVersion || "(missing)"}, current ${presetPackage.version}`);
|
|
14
|
+
}
|
|
15
|
+
if (presetNeedsEvidenceBundle(presetPackage)) {
|
|
16
|
+
if (!task.evidenceBundle)
|
|
17
|
+
failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle`);
|
|
18
|
+
else if (!fs.existsSync(path.join(target.projectRoot, String(task.evidenceBundle).replace(/^TARGET:/, "").replace(/^\/+/, "")))) {
|
|
19
|
+
failures.push(`${task.path} ${task.taskPreset} preset Evidence Bundle missing: ${task.evidenceBundle}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const issue of validateTaskPresetAuditSnapshot(target, task, presetPackage)) {
|
|
23
|
+
if (issue.includes("preset manifest hash mismatch"))
|
|
24
|
+
driftWarnings.push(issue);
|
|
25
|
+
else
|
|
26
|
+
failures.push(issue);
|
|
27
|
+
}
|
|
28
|
+
const resourceIssues = validatePresetResourcesForTask(target, task, presetPackage);
|
|
29
|
+
if (driftWarnings.length) {
|
|
30
|
+
warnings.push(...driftWarnings.map(toPresetDriftWarning));
|
|
31
|
+
warnings.push(...resourceIssues.map(toPresetDriftWarning));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
failures.push(...resourceIssues);
|
|
35
|
+
}
|
|
36
|
+
return { failures, warnings };
|
|
37
|
+
}
|
|
38
|
+
function presetNeedsEvidenceBundle(presetPackage) {
|
|
39
|
+
return Boolean(presetPackage.evidence?.bundleDir ||
|
|
40
|
+
presetPackage.audit?.evidenceFiles?.length ||
|
|
41
|
+
Object.keys(presetPackage.evidence?.files || {}).length);
|
|
42
|
+
}
|
|
43
|
+
function toPresetDriftWarning(issue) {
|
|
44
|
+
return `preset-drift-warning: creation-time preset provenance drift; ${issue}`;
|
|
45
|
+
}
|