coding-agent-harness 1.0.4 → 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/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +2 -2
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +96 -4
- package/README.zh-CN.md +75 -4
- package/SKILL.md +52 -51
- 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 +3 -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 +48 -47
- 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 +13 -13
- package/docs-release/architecture/overview.zh-CN.md +13 -13
- package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/guides/agent-installation.en-US.md +22 -15
- package/docs-release/guides/agent-installation.md +23 -15
- 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 +28 -4
- 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 +35 -18
- package/docs-release/guides/task-state-machine.md +35 -18
- 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/INDEX.md +60 -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 +22 -13
- 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 +6 -6
- 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/brief.md +11 -0
- 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 +31 -29
- 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/00-state.js +12 -0
- package/templates/dashboard/assets/app-src/10-router.js +3 -0
- package/templates/dashboard/assets/app-src/20-overview.js +13 -3
- package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
- package/templates/dashboard/assets/app-src/40-modules.js +1 -1
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
- package/templates/dashboard/assets/app.css +583 -0
- package/templates/dashboard/assets/app.css.manifest.json +1 -0
- package/templates/dashboard/assets/app.js +585 -11
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/i18n.js +144 -4
- 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 +88 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/module_session_prompt.md +2 -1
- package/templates/planning/review.md +0 -18
- package/templates/planning/task_plan.md +5 -44
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/planning/walkthrough.md +47 -0
- package/templates/reference/docs-library-standard.md +8 -8
- package/templates/reference/execution-workflow-standard.md +29 -2
- 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 +31 -29
- 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/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/module_session_prompt.md +12 -11
- package/templates-zh-CN/planning/review.md +0 -18
- package/templates-zh-CN/planning/task_plan.md +3 -63
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- 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 +32 -7
- 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 -96
- package/scripts/commands/preset-command.mjs +0 -73
- package/scripts/commands/task-command.mjs +0 -327
- package/scripts/harness.mjs +0 -287
- package/scripts/lib/capability-registry.mjs +0 -591
- package/scripts/lib/check-module-parallel.mjs +0 -237
- package/scripts/lib/check-profiles.mjs +0 -418
- package/scripts/lib/check-task-contracts.mjs +0 -47
- package/scripts/lib/core-shared.mjs +0 -196
- package/scripts/lib/dashboard-data.mjs +0 -412
- package/scripts/lib/dashboard-workbench.mjs +0 -257
- package/scripts/lib/dashboard-writer.mjs +0 -198
- 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 -514
- package/scripts/lib/governance-table-boundary.mjs +0 -175
- package/scripts/lib/lesson-maintenance.mjs +0 -152
- package/scripts/lib/markdown-utils.mjs +0 -158
- package/scripts/lib/migration-planner.mjs +0 -478
- package/scripts/lib/migration-support.mjs +0 -312
- package/scripts/lib/preset-audit-contracts.mjs +0 -37
- package/scripts/lib/preset-engine.mjs +0 -497
- package/scripts/lib/preset-registry.mjs +0 -627
- package/scripts/lib/preset-resource-contracts.mjs +0 -83
- package/scripts/lib/review-confirm-git-gate.mjs +0 -248
- package/scripts/lib/subagent-authorization-audit.mjs +0 -196
- package/scripts/lib/task-completion-consistency.mjs +0 -16
- 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/review-confirm.mjs +0 -101
- package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
- package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
- package/scripts/lib/task-lifecycle.mjs +0 -649
- package/scripts/lib/task-review-model.mjs +0 -469
- package/scripts/lib/task-scanner.mjs +0 -576
- 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/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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Dynamic metadata parsing stays behavior-first until the metadata domain model PR.
|
|
3
|
+
import { allowedTaskStates, allowedTaskBudgets, taskContractMarker, } from "./core-shared.mjs";
|
|
4
|
+
import { tableAfterHeading, firstColumn } from "./markdown-utils.mjs";
|
|
5
|
+
export function parseTaskState(progressContent) {
|
|
6
|
+
return parseTaskStateInfo(progressContent).state;
|
|
7
|
+
}
|
|
8
|
+
export function parseTaskBudget(taskPlanContent) {
|
|
9
|
+
const match = String(taskPlanContent || "").match(/^Selected budget\s*[::]\s*([^\n]+)/im) ||
|
|
10
|
+
String(taskPlanContent || "").match(/^选择预算\s*[::]\s*([^\n]+)/im);
|
|
11
|
+
if (!match)
|
|
12
|
+
return "standard";
|
|
13
|
+
const raw = match[1].replace(/`/g, "").trim().toLowerCase();
|
|
14
|
+
const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
|
|
15
|
+
if (allowedTaskBudgets.has(normalized))
|
|
16
|
+
return normalized;
|
|
17
|
+
if (["long-running", "longrunning", "module-parallel"].includes(normalized))
|
|
18
|
+
return "complex";
|
|
19
|
+
return "standard";
|
|
20
|
+
}
|
|
21
|
+
function parseMetadataLine(content, labels) {
|
|
22
|
+
const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
23
|
+
const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
|
|
24
|
+
return match ? match[1].replace(/`/g, "").trim() : "";
|
|
25
|
+
}
|
|
26
|
+
function normalizeMetadataValue(value, fallback = "") {
|
|
27
|
+
const normalized = String(value || "")
|
|
28
|
+
.replace(/`/g, "")
|
|
29
|
+
.trim()
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replaceAll("_", "-")
|
|
32
|
+
.replace(/\s+/g, "-");
|
|
33
|
+
return normalized || fallback;
|
|
34
|
+
}
|
|
35
|
+
export function parseTaskMetadata(taskPlanContent) {
|
|
36
|
+
const content = String(taskPlanContent || "");
|
|
37
|
+
const kind = normalizeMetadataValue(parseMetadataLine(content, ["Task Kind", "任务类型"]), "general");
|
|
38
|
+
const preset = normalizeMetadataValue(parseMetadataLine(content, ["Task Preset", "Preset", "任务预设"]), "none");
|
|
39
|
+
const presetVersion = parseMetadataLine(content, ["Preset Version", "预设版本"]);
|
|
40
|
+
const migrationTargetLevel = normalizeMetadataValue(parseMetadataLine(content, ["Migration Target Level", "Target Level", "迁移目标等级", "目标等级"]), "");
|
|
41
|
+
const migrationAchievedLevel = normalizeMetadataValue(parseMetadataLine(content, ["Migration Achieved Level", "Achieved Level", "迁移实际完成等级", "实际完成等级"]), "");
|
|
42
|
+
const evidenceBundle = parseMetadataLine(content, ["Evidence Bundle", "证据包"]);
|
|
43
|
+
return {
|
|
44
|
+
kind,
|
|
45
|
+
preset,
|
|
46
|
+
presetVersion,
|
|
47
|
+
migrationTargetLevel,
|
|
48
|
+
migrationAchievedLevel,
|
|
49
|
+
evidenceBundle,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function parseTaskContractInfo(taskPlanContent) {
|
|
53
|
+
const content = String(taskPlanContent || "");
|
|
54
|
+
const explicit = content.match(/^Task Contract\s*[::]\s*`?([^`\n]+)`?\s*$/im) ||
|
|
55
|
+
content.match(/^任务合同\s*[::]\s*`?([^`\n]+)`?\s*$/im);
|
|
56
|
+
const version = explicit ? explicit[1].trim() : "";
|
|
57
|
+
return {
|
|
58
|
+
version,
|
|
59
|
+
generated: version === "harness-task/v1" || content.includes(taskContractMarker),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function parseTaskStateInfo(progressContent) {
|
|
63
|
+
const match = progressContent.match(/^##\s*(?:Current Status|Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
64
|
+
if (!match)
|
|
65
|
+
return inferLegacyTaskState(progressContent);
|
|
66
|
+
const raw = match[1].replace(/`/g, "").trim();
|
|
67
|
+
if (!raw || raw.includes("|") || /^[-*]\s+/.test(raw))
|
|
68
|
+
return inferLegacyTaskState(progressContent);
|
|
69
|
+
const aliases = new Map([
|
|
70
|
+
["进行中", "in_progress"],
|
|
71
|
+
["已完成", "done"],
|
|
72
|
+
["未开始", "not_started"],
|
|
73
|
+
["计划中", "planned"],
|
|
74
|
+
["审查中", "review"],
|
|
75
|
+
["已阻塞", "blocked"],
|
|
76
|
+
["pending", "planned"],
|
|
77
|
+
]);
|
|
78
|
+
const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
79
|
+
return allowedTaskStates.has(normalized)
|
|
80
|
+
? { state: normalized, source: "explicit", raw }
|
|
81
|
+
: { state: "unknown", source: "invalid", raw };
|
|
82
|
+
}
|
|
83
|
+
function inferLegacyTaskState(progressContent) {
|
|
84
|
+
const { header, rows } = tableAfterHeading(progressContent, /^(Status|状态)$/i);
|
|
85
|
+
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
86
|
+
if (statusIndex < 0 || rows.length === 0)
|
|
87
|
+
return { state: "unknown", source: "missing", raw: "" };
|
|
88
|
+
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(Boolean);
|
|
89
|
+
if (states.includes("blocked"))
|
|
90
|
+
return { state: "blocked", source: "legacy-table", raw: "blocked" };
|
|
91
|
+
if (states.includes("in_progress"))
|
|
92
|
+
return { state: "in_progress", source: "legacy-table", raw: "in_progress" };
|
|
93
|
+
if (states.includes("review"))
|
|
94
|
+
return { state: "review", source: "legacy-table", raw: "review" };
|
|
95
|
+
if (states.length > 0 && states.every((state) => state === "done"))
|
|
96
|
+
return { state: "done", source: "legacy-table", raw: "done" };
|
|
97
|
+
if (states.some((state) => ["planned", "not_started"].includes(state)))
|
|
98
|
+
return { state: "planned", source: "legacy-table", raw: "planned" };
|
|
99
|
+
return { state: "unknown", source: "missing", raw: "" };
|
|
100
|
+
}
|
|
101
|
+
function normalizeLegacyState(value) {
|
|
102
|
+
const raw = String(value || "").replace(/`/g, "").trim().toLowerCase();
|
|
103
|
+
if (!raw || /^(none|n\/a|na|-|—|–|无)$/.test(raw))
|
|
104
|
+
return "";
|
|
105
|
+
if (/block|阻塞|blocked/.test(raw))
|
|
106
|
+
return "blocked";
|
|
107
|
+
if (/in[-_\s]?progress|doing|active|进行中|当前|working/.test(raw))
|
|
108
|
+
return "in_progress";
|
|
109
|
+
if (/review|审查|审核|验证中/.test(raw))
|
|
110
|
+
return "review";
|
|
111
|
+
if (/done|complete|completed|merged|closed|完成|已完成/.test(raw))
|
|
112
|
+
return "done";
|
|
113
|
+
if (/pending|planned|todo|not[-_\s]?started|未开始|计划/.test(raw))
|
|
114
|
+
return "planned";
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Dynamic review queue modeling stays behavior-first until the metadata domain model PR.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { lessonCandidatesFile, longRunningTaskContractFile, toPosix, visualMapFile, } from "./core-shared.mjs";
|
|
6
|
+
import { firstColumn, splitList, splitMarkdownRow, tableAfterHeading, } from "./markdown-utils.mjs";
|
|
7
|
+
import { implementationPhases, phaseHasRecordedProgress, } from "./phase-kind.mjs";
|
|
8
|
+
import { validateReviewConfirmationGitAudit } from "./review-confirm-git-gate.mjs";
|
|
9
|
+
import { isLessonCandidateDecisionComplete } from "./task-lesson-candidates.mjs";
|
|
10
|
+
import { reviewConfirmationFromTaskAudit } from "./task-audit-metadata.mjs";
|
|
11
|
+
export const taskScannerVersion = "task-scanner/2026-05-25-phase-kind";
|
|
12
|
+
export const reviewFindingColumns = {
|
|
13
|
+
severity: ["Severity", "严重级别", "优先级"],
|
|
14
|
+
finding: ["Finding", "发现"],
|
|
15
|
+
open: ["Open", "是否开放"],
|
|
16
|
+
blocksRelease: ["Blocks Release", "是否阻塞发布", "阻塞发布", "阻塞确认"],
|
|
17
|
+
disposition: ["Disposition", "处置", "处理结论"],
|
|
18
|
+
waiverBy: ["Waiver By", "豁免人"],
|
|
19
|
+
};
|
|
20
|
+
export function normalizeReviewBoolean(value) {
|
|
21
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
22
|
+
if (/^(yes|true|open|是|开放)$/.test(raw))
|
|
23
|
+
return "yes";
|
|
24
|
+
if (/^(no|false|closed|fixed|done|否|关闭|已关闭|已修复)$/.test(raw))
|
|
25
|
+
return "no";
|
|
26
|
+
return raw;
|
|
27
|
+
}
|
|
28
|
+
function parseMetadataLine(content, labels) {
|
|
29
|
+
const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
30
|
+
const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
|
|
31
|
+
return match ? match[1].replace(/`/g, "").trim() : "";
|
|
32
|
+
}
|
|
33
|
+
function normalizeMetadataValue(value, fallback = "") {
|
|
34
|
+
const normalized = String(value || "")
|
|
35
|
+
.replace(/`/g, "")
|
|
36
|
+
.trim()
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replaceAll("_", "-")
|
|
39
|
+
.replace(/\s+/g, "-");
|
|
40
|
+
return normalized || fallback;
|
|
41
|
+
}
|
|
42
|
+
export function parseTaskIdentity(taskPlanContent, fallbackTaskId) {
|
|
43
|
+
const taskKey = parseMetadataLine(taskPlanContent, ["Task Key", "任务主键"]) ||
|
|
44
|
+
parseMetadataLine(taskPlanContent, ["Task ID", "任务 ID"]) ||
|
|
45
|
+
fallbackTaskId;
|
|
46
|
+
return {
|
|
47
|
+
taskKey,
|
|
48
|
+
identitySource: taskKey && taskKey !== fallbackTaskId ? "explicit" : "path-derived-legacy",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function parseTaskTombstone(taskPlanContent) {
|
|
52
|
+
const topLevelSupersedes = splitList(parseMetadataLine(taskPlanContent, ["Supersedes", "合并自"]));
|
|
53
|
+
const match = String(taskPlanContent || "").match(/^##\s*(?:Task Tombstone|任务墓碑)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
|
|
54
|
+
const fields = match ? fieldsFromMarkdownBlock(match[1] || "") : new Map();
|
|
55
|
+
if (fields.size === 0) {
|
|
56
|
+
return {
|
|
57
|
+
deletionState: "active",
|
|
58
|
+
supersededBy: "",
|
|
59
|
+
supersedes: topLevelSupersedes,
|
|
60
|
+
deleteReason: "",
|
|
61
|
+
hiddenByDefault: false,
|
|
62
|
+
reopenEligible: false,
|
|
63
|
+
archiveEligible: false,
|
|
64
|
+
tombstoneSourcePath: "",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const state = normalizeMetadataValue(fields.get("state") || fields.get("状态") || "soft-deleted", "soft-deleted");
|
|
68
|
+
const deletionState = ["soft-deleted", "superseded", "archived"].includes(state) ? state : "soft-deleted";
|
|
69
|
+
return {
|
|
70
|
+
deletionState,
|
|
71
|
+
supersededBy: fields.get("superseded by") || fields.get("替代任务") || "",
|
|
72
|
+
supersedes: [...new Set([...topLevelSupersedes, ...splitList(fields.get("supersedes") || fields.get("合并自") || "")])],
|
|
73
|
+
deleteReason: fields.get("reason") || fields.get("原因") || "",
|
|
74
|
+
hiddenByDefault: true,
|
|
75
|
+
reopenEligible: parseTombstoneBooleanLike(fields.get("reopen eligible") || fields.get("可重新打开")),
|
|
76
|
+
archiveEligible: parseTombstoneBooleanLike(fields.get("archive eligible") || fields.get("可归档")),
|
|
77
|
+
tombstoneSourcePath: "task_plan.md#Task Tombstone",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function parseAgentReviewSubmission(reviewContent, { taskKey = "" } = {}) {
|
|
81
|
+
const match = String(reviewContent || "").match(/^##\s*(?:Agent Review Submission|Agent 审查提交|Agent 提交审查)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
|
|
82
|
+
if (!match)
|
|
83
|
+
return null;
|
|
84
|
+
const fields = fieldsFromMarkdownBlock(match[1] || "");
|
|
85
|
+
const required = [
|
|
86
|
+
"Submission ID",
|
|
87
|
+
"Submitted At",
|
|
88
|
+
"Submitted By",
|
|
89
|
+
"Task Key",
|
|
90
|
+
"Evidence Summary",
|
|
91
|
+
"Open Findings Count",
|
|
92
|
+
"Scanner Version",
|
|
93
|
+
];
|
|
94
|
+
const missing = required.filter((field) => !isConcreteField(fields.get(field.toLowerCase())));
|
|
95
|
+
const submittedTaskKey = fields.get("task key") || "";
|
|
96
|
+
const taskKeyMismatch = Boolean(taskKey && isConcreteField(submittedTaskKey) && !taskKeysMatch(submittedTaskKey, taskKey));
|
|
97
|
+
return {
|
|
98
|
+
submitted: missing.length === 0 && !taskKeyMismatch,
|
|
99
|
+
missingFields: taskKeyMismatch ? [...missing, "Task Key match"] : missing,
|
|
100
|
+
submissionId: fields.get("submission id") || "",
|
|
101
|
+
submittedAt: fields.get("submitted at") || "",
|
|
102
|
+
submittedBy: fields.get("submitted by") || "",
|
|
103
|
+
taskKey: submittedTaskKey,
|
|
104
|
+
taskKeyMismatch,
|
|
105
|
+
materialsChecklistHash: fields.get("materials checklist hash") || "",
|
|
106
|
+
evidenceSummary: fields.get("evidence summary") || "",
|
|
107
|
+
openFindingsCount: Number.parseInt(fields.get("open findings count") || "0", 10) || 0,
|
|
108
|
+
scannerVersion: fields.get("scanner version") || "",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true }) {
|
|
112
|
+
const issues = [];
|
|
113
|
+
const addIssue = (code, message, sourcePath, extra = {}) => {
|
|
114
|
+
issues.push({
|
|
115
|
+
code,
|
|
116
|
+
severity: extra.severity || "P2",
|
|
117
|
+
queue: "missing-materials",
|
|
118
|
+
sourcePath,
|
|
119
|
+
sourceLine: 0,
|
|
120
|
+
owner: extra.owner || "agent",
|
|
121
|
+
message,
|
|
122
|
+
allowedWritePaths: extra.allowedWritePaths || [`${toPosix(path.relative(path.dirname(taskDir), taskDir)) || "."}/**`],
|
|
123
|
+
forbiddenActions: ["human-confirm", "edit-unrelated-task", "fabricate-evidence"],
|
|
124
|
+
validationCommands: ["node dist/harness.mjs check --profile target-project <target>"],
|
|
125
|
+
confidence: extra.confidence || "exact",
|
|
126
|
+
repairable: true,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const requiredFiles = ["task_plan.md", "progress.md"];
|
|
130
|
+
if (budget !== "simple")
|
|
131
|
+
requiredFiles.push("brief.md", visualMapFile, "review.md", lessonCandidatesFile);
|
|
132
|
+
if (budget === "complex" && fs.existsSync(longRunningContractPath))
|
|
133
|
+
requiredFiles.push(longRunningTaskContractFile);
|
|
134
|
+
for (const fileName of requiredFiles) {
|
|
135
|
+
if (!fs.existsSync(path.join(taskDir, fileName)))
|
|
136
|
+
addIssue(`missing-file-${fileName}`, `Required task material is missing: ${fileName}`, `TARGET:${fileName}`);
|
|
137
|
+
}
|
|
138
|
+
if (budget !== "simple") {
|
|
139
|
+
if (brief.source !== "standalone")
|
|
140
|
+
addIssue("missing-brief", "Standard and complex tasks require standalone brief.md.", "TARGET:brief.md");
|
|
141
|
+
if (visualMap.status === "missing")
|
|
142
|
+
addIssue("missing-visual-map", "Standard and complex tasks require canonical visual_map.md.", `TARGET:${visualMapFile}`);
|
|
143
|
+
}
|
|
144
|
+
if (budget !== "simple" && reviewSurfaceRequired) {
|
|
145
|
+
if (!reviewSubmission?.submitted) {
|
|
146
|
+
const message = reviewSubmission?.taskKeyMismatch
|
|
147
|
+
? "Agent Review Submission Task Key does not match this task."
|
|
148
|
+
: "Agent has not submitted a strict Agent Review Submission packet.";
|
|
149
|
+
addIssue(reviewSubmission?.taskKeyMismatch ? "invalid-review-submission-task-key" : "missing-review-submission", message, "TARGET:review.md");
|
|
150
|
+
}
|
|
151
|
+
if (!isLessonCandidateDecisionComplete(lessonCandidates)) {
|
|
152
|
+
addIssue("missing-lesson-decision", `Lesson candidate decision is not complete: ${lessonCandidates.status}.`, `TARGET:${lessonCandidatesFile}`);
|
|
153
|
+
}
|
|
154
|
+
const actionablePhases = implementationPhases(phases || []);
|
|
155
|
+
const hasPhaseEvidence = actionablePhases.some(phaseHasRecordedProgress);
|
|
156
|
+
if ((phases || []).length > 0 && actionablePhases.length === 0) {
|
|
157
|
+
addIssue("missing-execution-phase", "Visual Map has no non-skipped execution phase.", `TARGET:${visualMapFile}`);
|
|
158
|
+
}
|
|
159
|
+
else if (actionablePhases.length > 0 && !hasPhaseEvidence) {
|
|
160
|
+
addIssue("phase-incomplete", "Visual Map has no execution phase progress or evidence yet.", `TARGET:${visualMapFile}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { ready: issues.length === 0, issues };
|
|
164
|
+
}
|
|
165
|
+
export function requiresReviewMaterials({ state = "unknown", lifecycleState = "unknown", closeoutStatus = "missing" } = {}) {
|
|
166
|
+
return (state === "review" ||
|
|
167
|
+
state === "done" ||
|
|
168
|
+
["in_review", "review-blocked", "closing", "closed-review-pending"].includes(lifecycleState) ||
|
|
169
|
+
closeoutStatus === "closed");
|
|
170
|
+
}
|
|
171
|
+
export function deriveTaskQueues({ id, title, state, budget, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target }) {
|
|
172
|
+
const queueReasons = [];
|
|
173
|
+
const pushReason = (reason) => {
|
|
174
|
+
queueReasons.push({
|
|
175
|
+
severity: "P2",
|
|
176
|
+
queue: "blocked",
|
|
177
|
+
sourcePath: "",
|
|
178
|
+
sourceLine: 0,
|
|
179
|
+
owner: "agent",
|
|
180
|
+
allowedWritePaths: [`${toPosix(path.relative(target.projectRoot, taskDir))}/**`],
|
|
181
|
+
forbiddenActions: ["human-confirm", "edit-unrelated-task", "fabricate-evidence"],
|
|
182
|
+
validationCommands: ["node dist/harness.mjs check --profile target-project <target>"],
|
|
183
|
+
confidence: "exact",
|
|
184
|
+
repairable: true,
|
|
185
|
+
...reason,
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
for (const issue of materialIssues || [])
|
|
189
|
+
pushReason(issue);
|
|
190
|
+
for (const risk of (risks || []).filter(isBlockingReviewRisk)) {
|
|
191
|
+
pushReason({
|
|
192
|
+
code: "open-blocking-finding",
|
|
193
|
+
severity: risk.severity || "P1",
|
|
194
|
+
queue: "blocked",
|
|
195
|
+
sourcePath: "TARGET:review.md",
|
|
196
|
+
message: `Open blocking review finding ${risk.id || risk.severity}: ${risk.summary || "Review finding blocks confirmation."}`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
for (const conflict of stateConflicts || []) {
|
|
200
|
+
if (conflict.severity !== "block")
|
|
201
|
+
continue;
|
|
202
|
+
pushReason({
|
|
203
|
+
code: conflict.code,
|
|
204
|
+
severity: "P1",
|
|
205
|
+
queue: "blocked",
|
|
206
|
+
sourcePath: "TARGET:progress.md",
|
|
207
|
+
message: conflict.message,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (reviewSubmission?.submitted && reviewSubmission.scannerVersion !== taskScannerVersion) {
|
|
211
|
+
pushReason({
|
|
212
|
+
code: "stale-review-submission-scanner",
|
|
213
|
+
severity: "P2",
|
|
214
|
+
queue: "blocked",
|
|
215
|
+
sourcePath: "TARGET:review.md",
|
|
216
|
+
message: "Agent Review Submission was generated by a stale scanner version.",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (budget !== "simple" && reviewSubmission?.submitted && reviewQueueState === "needs-material" && !queueReasons.some((reason) => reason.queue === "missing-materials")) {
|
|
220
|
+
pushReason({
|
|
221
|
+
code: "review-closeout-materials-incomplete",
|
|
222
|
+
queue: "missing-materials",
|
|
223
|
+
sourcePath: closeoutMaterialSourcePath(target, taskDir),
|
|
224
|
+
message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const hasLessonWork = lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || lessonCandidates?.openCount > 0;
|
|
228
|
+
const taskQueues = [];
|
|
229
|
+
if (tombstone.deletionState !== "active") {
|
|
230
|
+
taskQueues.push("soft-deleted-superseded");
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
if ((materialIssues || []).length > 0 || queueReasons.some((reason) => reason.queue === "missing-materials"))
|
|
234
|
+
taskQueues.push("missing-materials");
|
|
235
|
+
if (queueReasons.some((reason) => reason.queue === "blocked"))
|
|
236
|
+
taskQueues.push("blocked");
|
|
237
|
+
if (reviewSubmission?.submitted && reviewQueueState === "ready-to-confirm" && !reviewConfirmation?.confirmed && !hasLessonWork && !taskQueues.includes("blocked") && !taskQueues.includes("missing-materials")) {
|
|
238
|
+
taskQueues.push("review");
|
|
239
|
+
}
|
|
240
|
+
if (hasLessonWork)
|
|
241
|
+
taskQueues.push("lessons");
|
|
242
|
+
if (budget === "simple" && state === "done" && closeoutStatus === "closed")
|
|
243
|
+
taskQueues.push("finalized");
|
|
244
|
+
if (reviewStatus === "confirmed")
|
|
245
|
+
taskQueues.push(closeoutStatus === "closed" ? "finalized" : "confirmed");
|
|
246
|
+
}
|
|
247
|
+
if (taskQueues.length === 0)
|
|
248
|
+
taskQueues.push("active");
|
|
249
|
+
return {
|
|
250
|
+
taskQueues,
|
|
251
|
+
queueReasons,
|
|
252
|
+
repairPrompt: renderRepairPrompt({ id, title, taskDir, target, reasons: queueReasons }),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function closeoutMaterialSourcePath(target, taskDir) {
|
|
256
|
+
const localWalkthrough = path.join(taskDir, "walkthrough.md");
|
|
257
|
+
if (fs.existsSync(localWalkthrough))
|
|
258
|
+
return "TARGET:walkthrough.md";
|
|
259
|
+
const closeoutIndexPath = target.harness?.closeoutIndexPath;
|
|
260
|
+
if (closeoutIndexPath)
|
|
261
|
+
return `TARGET:${toPosix(path.relative(target.projectRoot, closeoutIndexPath))}`;
|
|
262
|
+
return "TARGET:closeout-materials";
|
|
263
|
+
}
|
|
264
|
+
export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit = null, projectRoot = "", taskDir = "", indexPath = "", reviewPath = "", progressPath = "" } = {}) {
|
|
265
|
+
if (taskAudit) {
|
|
266
|
+
const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
|
|
267
|
+
if (confirmation?.confirmed &&
|
|
268
|
+
confirmation.auditSource !== "migrated-legacy-review" &&
|
|
269
|
+
projectRoot &&
|
|
270
|
+
(indexPath || taskDir) &&
|
|
271
|
+
confirmation.commitSha) {
|
|
272
|
+
const gitAudit = validateReviewConfirmationGitAudit({
|
|
273
|
+
projectRoot,
|
|
274
|
+
taskId: taskKey,
|
|
275
|
+
reviewPath: indexPath || path.join(taskDir, "INDEX.md"),
|
|
276
|
+
progressPath: "",
|
|
277
|
+
commitSha: confirmation.commitSha,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
...confirmation,
|
|
281
|
+
confirmed: confirmation.confirmed && gitAudit.valid,
|
|
282
|
+
missingFields: gitAudit.valid ? confirmation.missingFields : [...confirmation.missingFields, "Review Commit SHA git audit"],
|
|
283
|
+
gitAudit,
|
|
284
|
+
gitAuditInvalid: !gitAudit.valid,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return confirmation;
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
export function taskReviewStatus({ reviewContent = "", risks = [], confirmation = null, submission = null } = {}) {
|
|
292
|
+
if (risks.some(isBlockingReviewRisk))
|
|
293
|
+
return "blocked-open-findings";
|
|
294
|
+
if (confirmation?.confirmed)
|
|
295
|
+
return "confirmed";
|
|
296
|
+
if (!String(reviewContent || "").trim())
|
|
297
|
+
return "missing";
|
|
298
|
+
if (submission?.submitted)
|
|
299
|
+
return "agent-reviewed";
|
|
300
|
+
if (hasAgentReviewSignal(reviewContent))
|
|
301
|
+
return "agent-reviewed";
|
|
302
|
+
return "required";
|
|
303
|
+
}
|
|
304
|
+
function hasAgentReviewSignal(reviewContent) {
|
|
305
|
+
const content = String(reviewContent || "");
|
|
306
|
+
const verdict = content.match(/^\s*[-*]?\s*Verdict\s*[::]\s*([^\n]+)/im);
|
|
307
|
+
if (verdict) {
|
|
308
|
+
const value = verdict[1].trim().toLowerCase();
|
|
309
|
+
if (/^yes(?:$|[-_\s])/i.test(value) && !/^yes\s*\/\s*no\b/i.test(value))
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return /本轮已检查|未发现阻塞目标的重要发现/.test(content);
|
|
313
|
+
}
|
|
314
|
+
export function isBlockingReviewRisk(risk) {
|
|
315
|
+
return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
|
|
316
|
+
}
|
|
317
|
+
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard" } = {}) {
|
|
318
|
+
if (reviewStatus === "blocked-open-findings")
|
|
319
|
+
return "review-blocked";
|
|
320
|
+
if (budget === "simple" && closeoutStatus === "closed")
|
|
321
|
+
return "closed";
|
|
322
|
+
if (closeoutStatus === "closed" && reviewStatus !== "confirmed")
|
|
323
|
+
return "closed-review-pending";
|
|
324
|
+
if (closeoutStatus === "closed")
|
|
325
|
+
return "closed";
|
|
326
|
+
if (state === "blocked")
|
|
327
|
+
return "blocked";
|
|
328
|
+
if (state === "done")
|
|
329
|
+
return "closing";
|
|
330
|
+
if (state === "review")
|
|
331
|
+
return "in_review";
|
|
332
|
+
if (state === "in_progress")
|
|
333
|
+
return "active";
|
|
334
|
+
if (["planned", "not_started"].includes(state))
|
|
335
|
+
return "ready";
|
|
336
|
+
return "unknown";
|
|
337
|
+
}
|
|
338
|
+
export function deriveReviewQueueState({ state = "unknown", lifecycleState = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard", walkthroughPath = "", lessonCandidateDecisionComplete = false, materialsReady = true, deletionState = "active" } = {}) {
|
|
339
|
+
if (deletionState !== "active")
|
|
340
|
+
return "not-in-queue";
|
|
341
|
+
if (reviewStatus === "blocked-open-findings")
|
|
342
|
+
return "blocked";
|
|
343
|
+
if (["not_started", "planned", "in_progress"].includes(state))
|
|
344
|
+
return "not-in-queue";
|
|
345
|
+
const reviewSurface = requiresReviewMaterials({ state, lifecycleState, closeoutStatus });
|
|
346
|
+
if (!reviewSurface)
|
|
347
|
+
return "not-in-queue";
|
|
348
|
+
if (reviewStatus === "confirmed")
|
|
349
|
+
return closeoutStatus === "closed" ? "not-in-queue" : "confirmed";
|
|
350
|
+
if (budget === "simple" && reviewStatus === "missing")
|
|
351
|
+
return "not-in-queue";
|
|
352
|
+
const missingWalkthrough = budget !== "simple" && !walkthroughPath;
|
|
353
|
+
const missingCandidateDecision = budget !== "simple" && !lessonCandidateDecisionComplete;
|
|
354
|
+
if (!materialsReady || missingWalkthrough || missingCandidateDecision || ["missing", "required"].includes(reviewStatus))
|
|
355
|
+
return "needs-material";
|
|
356
|
+
if (closeoutStatus === "closed")
|
|
357
|
+
return "closed-debt";
|
|
358
|
+
return "ready-to-confirm";
|
|
359
|
+
}
|
|
360
|
+
export function collectStateConflicts({ state, reviewStatus, closeoutStatus, lifecycleState, budget = "standard" }) {
|
|
361
|
+
const conflicts = [];
|
|
362
|
+
if (state === "done" && closeoutStatus !== "closed") {
|
|
363
|
+
conflicts.push({ code: "done-without-closeout", severity: "warn", message: "Task state is done, but closeout is still missing or pending." });
|
|
364
|
+
}
|
|
365
|
+
if (closeoutStatus === "closed" && reviewStatus !== "confirmed" && budget !== "simple") {
|
|
366
|
+
conflicts.push({ code: "closed-without-human-review", severity: "warn", message: "Task is closed, but human review confirmation is still missing." });
|
|
367
|
+
}
|
|
368
|
+
if (reviewStatus === "blocked-open-findings") {
|
|
369
|
+
conflicts.push({ code: "review-blocked-open-findings", severity: "block", message: "Open P0-P2 review findings block human review confirmation." });
|
|
370
|
+
}
|
|
371
|
+
if (lifecycleState === "closed" && reviewStatus === "blocked-open-findings") {
|
|
372
|
+
conflicts.push({ code: "closed-with-blocking-review", severity: "block", message: "Closeout is closed while review findings still block release." });
|
|
373
|
+
}
|
|
374
|
+
return conflicts;
|
|
375
|
+
}
|
|
376
|
+
export function collectReviewRisks(reviewContent) {
|
|
377
|
+
const { header, rows } = tableAfterHeading(reviewContent, /^ID$/i);
|
|
378
|
+
const severityIndex = firstColumn(header, reviewFindingColumns.severity);
|
|
379
|
+
const findingIndex = firstColumn(header, reviewFindingColumns.finding);
|
|
380
|
+
const openIndex = firstColumn(header, reviewFindingColumns.open);
|
|
381
|
+
const blocksIndex = firstColumn(header, reviewFindingColumns.blocksRelease);
|
|
382
|
+
const dispositionIndex = firstColumn(header, reviewFindingColumns.disposition);
|
|
383
|
+
const waiverByIndex = firstColumn(header, reviewFindingColumns.waiverBy);
|
|
384
|
+
if (severityIndex < 0 || findingIndex < 0)
|
|
385
|
+
return [];
|
|
386
|
+
return rows
|
|
387
|
+
.filter((row) => /^P[0-3]$/i.test(row[severityIndex] || ""))
|
|
388
|
+
.map((row) => {
|
|
389
|
+
const disposition = normalizeMetadataValue(row[dispositionIndex] || "", "");
|
|
390
|
+
const waived = ["waived", "accepted-risk"].includes(disposition) && String(row[waiverByIndex] || "").trim();
|
|
391
|
+
return {
|
|
392
|
+
id: row[0],
|
|
393
|
+
severity: row[severityIndex],
|
|
394
|
+
open: !waived && normalizeReviewBoolean(row[openIndex] || "no") === "yes",
|
|
395
|
+
blocksRelease: !waived && normalizeReviewBoolean(row[blocksIndex] || "no") === "yes",
|
|
396
|
+
disposition,
|
|
397
|
+
waiverBy: row[waiverByIndex] || "",
|
|
398
|
+
summary: row[findingIndex],
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function renderRepairPrompt({ id, title, taskDir, target, reasons }) {
|
|
403
|
+
const repairable = (reasons || []).filter((reason) => reason.repairable !== false);
|
|
404
|
+
if (repairable.length === 0)
|
|
405
|
+
return "";
|
|
406
|
+
const relativeTaskDir = toPosix(path.relative(target.projectRoot, taskDir));
|
|
407
|
+
return [
|
|
408
|
+
`Please repair task ${id}: ${title || id}.`,
|
|
409
|
+
"",
|
|
410
|
+
`Task path: ${relativeTaskDir}`,
|
|
411
|
+
"",
|
|
412
|
+
"Detected issues:",
|
|
413
|
+
...repairable.map((reason) => `- [${reason.queue}/${reason.code}] ${reason.message}`),
|
|
414
|
+
"",
|
|
415
|
+
"Allowed writes:",
|
|
416
|
+
...[...new Set(repairable.flatMap((reason) => reason.allowedWritePaths || []))].map((item) => `- ${item}`),
|
|
417
|
+
"",
|
|
418
|
+
"Forbidden actions:",
|
|
419
|
+
"- Do not write Human Review Confirmation; only a human can confirm.",
|
|
420
|
+
"- Do not edit unrelated tasks.",
|
|
421
|
+
"- Do not fabricate evidence or mark work complete without running checks.",
|
|
422
|
+
"",
|
|
423
|
+
"Expected output:",
|
|
424
|
+
"- Fix the listed task-local materials or blockers.",
|
|
425
|
+
"- Update progress.md with evidence.",
|
|
426
|
+
"- Re-run the validation commands below.",
|
|
427
|
+
"",
|
|
428
|
+
"Validation commands:",
|
|
429
|
+
...[...new Set(repairable.flatMap((reason) => reason.validationCommands || []))].map((item) => `- ${item}`),
|
|
430
|
+
].join("\n");
|
|
431
|
+
}
|
|
432
|
+
function fieldsFromMarkdownBlock(block) {
|
|
433
|
+
const fields = new Map();
|
|
434
|
+
const tableLines = String(block || "").split(/\r?\n/).filter((line) => line.trim().startsWith("|"));
|
|
435
|
+
for (let index = 0; index < tableLines.length - 1; index += 1) {
|
|
436
|
+
const header = splitMarkdownRow(tableLines[index]);
|
|
437
|
+
const separator = splitMarkdownRow(tableLines[index + 1]);
|
|
438
|
+
if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell)))
|
|
439
|
+
continue;
|
|
440
|
+
const fieldIndex = firstColumn(header, ["Field", "字段"]);
|
|
441
|
+
const valueIndex = firstColumn(header, ["Value", "值"]);
|
|
442
|
+
if (fieldIndex < 0 || valueIndex < 0)
|
|
443
|
+
continue;
|
|
444
|
+
for (const line of tableLines.slice(index + 2)) {
|
|
445
|
+
const row = splitMarkdownRow(line);
|
|
446
|
+
if (row.length !== header.length)
|
|
447
|
+
break;
|
|
448
|
+
const field = String(row[fieldIndex] || "").trim();
|
|
449
|
+
if (field)
|
|
450
|
+
fields.set(field.toLowerCase(), String(row[valueIndex] || "").trim());
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
for (const line of String(block || "").split(/\r?\n/)) {
|
|
455
|
+
const match = line.match(/^\s*(?:[-*]\s*)?([^::|]+?)\s*[::]\s*(.+?)\s*$/);
|
|
456
|
+
if (match)
|
|
457
|
+
fields.set(match[1].trim().toLowerCase(), match[2].trim());
|
|
458
|
+
}
|
|
459
|
+
return fields;
|
|
460
|
+
}
|
|
461
|
+
function isConcreteField(value) {
|
|
462
|
+
const raw = String(value || "").trim();
|
|
463
|
+
if (!raw)
|
|
464
|
+
return false;
|
|
465
|
+
return !/^\[.*\]$/.test(raw) && !/\{\{[^}]+\}\}/.test(raw);
|
|
466
|
+
}
|
|
467
|
+
function taskKeysMatch(candidate, expected) {
|
|
468
|
+
const left = String(candidate || "").replace(/`/g, "").trim();
|
|
469
|
+
const right = String(expected || "").replace(/`/g, "").trim();
|
|
470
|
+
return left === right || right.endsWith(`/${left}`);
|
|
471
|
+
}
|
|
472
|
+
function parseTombstoneBooleanLike(value) {
|
|
473
|
+
return /^(yes|true|open|blocked|是|开放|阻塞|阻塞确认|阻塞发布)$/i.test(String(value || "").trim());
|
|
474
|
+
}
|