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,82 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { lessonCandidatesFile, readFileSafe, todayDate, toPosix, visualMapFile, } from "../core-shared.mjs";
|
|
5
|
+
import { firstColumn, updateMarkdownTableRow, } from "../markdown-utils.mjs";
|
|
6
|
+
import { normalizePhaseActor, normalizePhaseKind, } from "../phase-kind.mjs";
|
|
7
|
+
import { parseLessonCandidateStatus } from "../task-lesson-candidates.mjs";
|
|
8
|
+
export function advanceLifecyclePhase(target, taskDir, event) {
|
|
9
|
+
const visualMapPath = path.join(taskDir, visualMapFile);
|
|
10
|
+
if (!fs.existsSync(visualMapPath))
|
|
11
|
+
return "";
|
|
12
|
+
const content = readFileSafe(visualMapPath);
|
|
13
|
+
let updated = false;
|
|
14
|
+
const phaseUpdate = updateMarkdownTableRow(content, /^Phase ID$/i, (header, row) => {
|
|
15
|
+
if (updated || !phaseMatchesLifecycleEvent(header, row, event))
|
|
16
|
+
return null;
|
|
17
|
+
updated = true;
|
|
18
|
+
const next = [...row];
|
|
19
|
+
const stateIndex = firstColumn(header, ["State", "状态"]);
|
|
20
|
+
const completionIndex = firstColumn(header, ["Completion", "完成度"]);
|
|
21
|
+
const evidenceIndex = firstColumn(header, ["Evidence Status", "证据状态"]);
|
|
22
|
+
if (stateIndex >= 0)
|
|
23
|
+
next[stateIndex] = "done";
|
|
24
|
+
if (completionIndex >= 0)
|
|
25
|
+
next[completionIndex] = "100";
|
|
26
|
+
if (evidenceIndex >= 0)
|
|
27
|
+
next[evidenceIndex] = "present";
|
|
28
|
+
return next;
|
|
29
|
+
});
|
|
30
|
+
if (!updated || phaseUpdate.content === content)
|
|
31
|
+
return "";
|
|
32
|
+
fs.writeFileSync(visualMapPath, phaseUpdate.content);
|
|
33
|
+
return toPosix(path.relative(target.projectRoot, visualMapPath));
|
|
34
|
+
}
|
|
35
|
+
export function autoRecordNoLessonCandidateDecision(target, taskDir) {
|
|
36
|
+
const candidatePath = path.join(taskDir, lessonCandidatesFile);
|
|
37
|
+
if (!fs.existsSync(candidatePath))
|
|
38
|
+
return "";
|
|
39
|
+
const content = readFileSafe(candidatePath);
|
|
40
|
+
const status = parseLessonCandidateStatus(content);
|
|
41
|
+
if (status.rows.length > 0 || status.declaredStatus !== "pending-review")
|
|
42
|
+
return "";
|
|
43
|
+
const next = replaceNoLessonCandidateFields(content);
|
|
44
|
+
if (next === content)
|
|
45
|
+
return "";
|
|
46
|
+
fs.writeFileSync(candidatePath, next);
|
|
47
|
+
return toPosix(path.relative(target.projectRoot, candidatePath));
|
|
48
|
+
}
|
|
49
|
+
function phaseMatchesLifecycleEvent(header, row, event) {
|
|
50
|
+
const kindIndex = firstColumn(header, ["Kind", "阶段类型", "类型"]);
|
|
51
|
+
if (kindIndex < 0)
|
|
52
|
+
return false;
|
|
53
|
+
const actorIndex = firstColumn(header, ["Actor", "执行者", "角色"]);
|
|
54
|
+
const exitCommandIndex = firstColumn(header, ["Exit Command", "出口命令", "退出命令"]);
|
|
55
|
+
const outputIndex = firstColumn(header, ["Output", "产出"]);
|
|
56
|
+
const kind = normalizePhaseKind(row[kindIndex]);
|
|
57
|
+
const actor = actorIndex >= 0 ? normalizePhaseActor(row[actorIndex]) : "agent";
|
|
58
|
+
const exitCommand = String(row[exitCommandIndex] || "");
|
|
59
|
+
const output = String(row[outputIndex] || "");
|
|
60
|
+
if (event === "task-start")
|
|
61
|
+
return kind === "init" && actor === "agent";
|
|
62
|
+
if (event === "task-review") {
|
|
63
|
+
return kind === "gate" && actor === "agent" && (/\btask-review\b/.test(exitCommand) || /Agent Review Submission/i.test(output));
|
|
64
|
+
}
|
|
65
|
+
if (event === "task-complete") {
|
|
66
|
+
return kind === "gate" && actor === "agent" && /\btask-complete\b/.test(exitCommand);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function replaceNoLessonCandidateFields(content) {
|
|
71
|
+
let next = String(content || "");
|
|
72
|
+
const replacements = [
|
|
73
|
+
[/(\|\s*Task-level status\s*\|\s*)[^|]+(\|)/i, "$1no-candidate-accepted $2"],
|
|
74
|
+
[/(\|\s*Review decision\s*\|\s*)[^|]+(\|)/i, "$1accepted-no-candidate $2"],
|
|
75
|
+
[/(\|\s*Closeout token\s*\|\s*)[^|]+(\|)/i, "$1checked-none:auto-no-candidate $2"],
|
|
76
|
+
[/(\|\s*Last updated\s*\|\s*)[^|]+(\|)/i, `$1${todayDate()} $2`],
|
|
77
|
+
];
|
|
78
|
+
for (const [pattern, replacement] of replacements)
|
|
79
|
+
next = next.replace(pattern, replacement);
|
|
80
|
+
const reason = "Agent review found no reusable lesson candidates in this task; the empty candidate table is recorded as checked for closeout.";
|
|
81
|
+
return next.replace(/(## No-Candidate Reason\s*\n)([\s\S]*?)(?=\n## |\s*$)/, `$1\n${reason}\n`);
|
|
82
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Dynamic review confirmation flow stays behavior-first until the metadata domain model PR.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { lessonCandidatesFile, nowTimestamp, readFileSafe, toPosix, } from "../core-shared.mjs";
|
|
6
|
+
import { parseTaskAuditMetadata, readGitIdentity, replaceTaskAuditMetadata, taskAuditFieldOrder, } from "../task-audit-metadata.mjs";
|
|
7
|
+
import { collectReviewRisks, isBlockingReviewRisk, isLessonCandidateDecisionComplete, parseLessonCandidateStatus, parseTaskBudget, taskIdForDirectory, } from "../task-scanner.mjs";
|
|
8
|
+
import { commitReviewConfirmationGate, prepareReviewConfirmGitGate } from "../review-confirm-git-gate.mjs";
|
|
9
|
+
import { validateHumanReviewConfirmation } from "./review-gates.mjs";
|
|
10
|
+
import { markdownCell } from "./text-utils.mjs";
|
|
11
|
+
export function confirmTaskReview({ target, taskDir, findTaskByDirectory }, { reviewer = "Human Reviewer", message = "", confirmText = "", evidence = "" } = {}) {
|
|
12
|
+
assertTaskDirectoryInsidePlanning(target, taskDir);
|
|
13
|
+
const canonicalTaskId = taskIdForDirectory(target, taskDir);
|
|
14
|
+
const shortId = path.basename(taskDir);
|
|
15
|
+
if (confirmText && ![shortId, canonicalTaskId].includes(confirmText)) {
|
|
16
|
+
throw new Error(`Review confirmation text must match task id: ${shortId}`);
|
|
17
|
+
}
|
|
18
|
+
if (!confirmText)
|
|
19
|
+
throw new Error(`Missing review confirmation text: ${shortId}`);
|
|
20
|
+
const reviewPath = path.join(taskDir, "review.md");
|
|
21
|
+
const indexPath = path.join(taskDir, "INDEX.md");
|
|
22
|
+
const reviewContent = readFileSafe(reviewPath);
|
|
23
|
+
const indexContent = readFileSafe(indexPath);
|
|
24
|
+
const budget = parseTaskBudget(readFileSafe(path.join(taskDir, "task_plan.md")));
|
|
25
|
+
const candidateStatus = parseLessonCandidateStatus(readFileSafe(path.join(taskDir, lessonCandidatesFile)));
|
|
26
|
+
const blockingRisks = collectReviewRisks(reviewContent).filter(isBlockingReviewRisk);
|
|
27
|
+
if (blockingRisks.length > 0) {
|
|
28
|
+
const ids = blockingRisks.map((risk) => risk.id || risk.severity).join(", ");
|
|
29
|
+
throw new Error(`Open blocking review findings must be closed before confirmation: ${ids}`);
|
|
30
|
+
}
|
|
31
|
+
validateHumanReviewConfirmation({
|
|
32
|
+
task: findTaskByDirectory(target, taskDir),
|
|
33
|
+
budget,
|
|
34
|
+
});
|
|
35
|
+
if (budget !== "simple" && !isLessonCandidateDecisionComplete(candidateStatus)) {
|
|
36
|
+
throw new Error(`Human review confirmation requires lesson candidate decision complete; current status is ${candidateStatus.status}.`);
|
|
37
|
+
}
|
|
38
|
+
const gitGate = prepareReviewConfirmGitGate(target.projectRoot, [indexPath]);
|
|
39
|
+
const timestamp = nowTimestamp();
|
|
40
|
+
const confirmationId = `HRC-${timestamp.replace(/[^0-9]/g, "").slice(0, 14)}`;
|
|
41
|
+
const identity = readGitIdentity(target.projectRoot);
|
|
42
|
+
const baseAuditFields = taskAuditFieldsFromIndex(indexContent);
|
|
43
|
+
const buildAuditFields = ({ commitSha = "pending", auditStatus = "commit-pending" } = {}) => ({
|
|
44
|
+
...baseAuditFields,
|
|
45
|
+
"Human Review Status": "confirmed",
|
|
46
|
+
"Confirmation ID": confirmationId,
|
|
47
|
+
"Confirmed At": timestamp,
|
|
48
|
+
"Reviewer": reviewer || identity.name || "Human Reviewer",
|
|
49
|
+
"Reviewer Email": identity.email || "n/a",
|
|
50
|
+
"Confirm Text": markdownCell(confirmText),
|
|
51
|
+
"Evidence Checked": evidence || `TARGET:${toPosix(path.relative(target.projectRoot, reviewPath))}`,
|
|
52
|
+
"Review Commit SHA": commitSha,
|
|
53
|
+
"Audit Source": "native-index",
|
|
54
|
+
"Audit Status": auditStatus,
|
|
55
|
+
"Message": message || "Human review confirmed",
|
|
56
|
+
"Migration Status": baseAuditFields["Migration Status"] || "native",
|
|
57
|
+
});
|
|
58
|
+
fs.writeFileSync(indexPath, ensureTrailingNewline(replaceTaskAuditMetadata(indexContent, buildAuditFields(), { locale: target.locale })));
|
|
59
|
+
const audit = commitReviewConfirmationGate(gitGate, {
|
|
60
|
+
taskId: canonicalTaskId,
|
|
61
|
+
reviewPath: indexPath,
|
|
62
|
+
message: message || `Human review confirmed by ${reviewer}`,
|
|
63
|
+
writeFinalAudit(commitSha) {
|
|
64
|
+
const currentIndex = readFileSafe(indexPath);
|
|
65
|
+
const finalIndex = replaceTaskAuditMetadata(currentIndex, buildAuditFields({ commitSha, auditStatus: "committed" }), { locale: target.locale });
|
|
66
|
+
fs.writeFileSync(indexPath, ensureTrailingNewline(finalIndex));
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
event: "review-confirm",
|
|
71
|
+
task: findTaskByDirectory(target, taskDir) || { id: canonicalTaskId, reviewStatus: "confirmed" },
|
|
72
|
+
audit,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function assertTaskDirectoryInsidePlanning(target, taskDir) {
|
|
76
|
+
const realTaskDir = fs.realpathSync(taskDir);
|
|
77
|
+
const allowedRoots = (target.harness?.taskRoots || [])
|
|
78
|
+
.filter(fs.existsSync)
|
|
79
|
+
.map((root) => fs.realpathSync(root));
|
|
80
|
+
if (!allowedRoots.some((root) => realTaskDir === root || realTaskDir.startsWith(`${root}${path.sep}`))) {
|
|
81
|
+
throw new Error(`Task directory outside planning root: ${taskIdForDirectory(target, taskDir)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function taskAuditFieldsFromIndex(content) {
|
|
85
|
+
const audit = parseTaskAuditMetadata(content, { required: true });
|
|
86
|
+
const fields = {};
|
|
87
|
+
for (const field of taskAuditFieldOrder)
|
|
88
|
+
fields[field] = audit.fields.get(field.toLowerCase()) || "n/a";
|
|
89
|
+
return fields;
|
|
90
|
+
}
|
|
91
|
+
function ensureTrailingNewline(content) {
|
|
92
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
93
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Dynamic review gate 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, } from "../core-shared.mjs";
|
|
6
|
+
import { collectReviewRisks, isBlockingReviewRisk, parseTaskAuditMetadata, parsePhases, parseReviewConfirmation, readVisualMapContractFile, } from "../task-scanner.mjs";
|
|
7
|
+
import { implementationPhases, phaseHasRecordedProgress, } from "../phase-kind.mjs";
|
|
8
|
+
export function validateLifecycleTransition({ event, currentState, budget, reviewContent = "", indexContent = "", reviewTaskKey = "", projectRoot = "", taskDir = "" }) {
|
|
9
|
+
if (event === "task-review" && currentState !== "in_progress") {
|
|
10
|
+
throw new Error(`task-review requires current state in_progress; current state is ${currentState || "unknown"}`);
|
|
11
|
+
}
|
|
12
|
+
if (event === "task-complete" && budget !== "simple" && currentState !== "review") {
|
|
13
|
+
throw new Error(`task-complete for ${budget} tasks requires current state review. Run task-review first.`);
|
|
14
|
+
}
|
|
15
|
+
if (event === "task-complete" && budget !== "simple") {
|
|
16
|
+
const blockingRisks = collectReviewRisks(reviewContent).filter(isBlockingReviewRisk);
|
|
17
|
+
if (blockingRisks.length > 0) {
|
|
18
|
+
const ids = blockingRisks.map((risk) => risk.id || risk.severity).join(", ");
|
|
19
|
+
throw new Error(`Open blocking review findings must be closed before task-complete: ${ids}`);
|
|
20
|
+
}
|
|
21
|
+
if (!parseReviewConfirmation(reviewContent, { taskKey: reviewTaskKey, taskAudit: parseTaskAuditMetadata(indexContent), projectRoot, taskDir, indexPath: path.join(taskDir, "INDEX.md") })?.confirmed) {
|
|
22
|
+
throw new Error("Human review must be confirmed before task-complete. Run review-confirm first.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function validateReviewEntryGate(taskDir, budget) {
|
|
27
|
+
if (budget === "simple")
|
|
28
|
+
return;
|
|
29
|
+
const candidatePath = path.join(taskDir, lessonCandidatesFile);
|
|
30
|
+
if (!fs.existsSync(candidatePath)) {
|
|
31
|
+
throw new Error(`task-review requires ${lessonCandidatesFile} before entering human review.`);
|
|
32
|
+
}
|
|
33
|
+
const phases = parsePhases(readVisualMapContractFile(taskDir).content);
|
|
34
|
+
const actionablePhases = implementationPhases(phases);
|
|
35
|
+
if (phases.length > 0 && actionablePhases.length === 0) {
|
|
36
|
+
throw new Error("task-review requires at least one non-skipped Visual Map execution phase.");
|
|
37
|
+
}
|
|
38
|
+
const hasRecordedPhaseProgress = actionablePhases.some(phaseHasRecordedProgress);
|
|
39
|
+
if (actionablePhases.length > 0 && !hasRecordedPhaseProgress) {
|
|
40
|
+
throw new Error("task-review requires at least one Visual Map execution phase progress update. Run task-phase before entering human review.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function validateHumanReviewConfirmation({ task, budget }) {
|
|
44
|
+
if (budget === "simple")
|
|
45
|
+
return;
|
|
46
|
+
if (!task?.walkthroughPath) {
|
|
47
|
+
throw new Error("Human review confirmation requires task-local walkthrough.md before review-confirm.");
|
|
48
|
+
}
|
|
49
|
+
const queueState = task?.reviewQueueState || "not-in-queue";
|
|
50
|
+
if (queueState !== "ready-to-confirm") {
|
|
51
|
+
const state = task?.state || "unknown";
|
|
52
|
+
throw new Error(`Human review confirmation requires canonical ready-to-confirm review queue; current state is ${state}, review queue is ${queueState}.`);
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(task?.taskQueues) || !task.taskQueues.includes("review")) {
|
|
55
|
+
const queues = Array.isArray(task?.taskQueues) ? task.taskQueues.join(", ") : "none";
|
|
56
|
+
throw new Error(`Human review confirmation requires the task to be in the Review queue; current queues: ${queues || "none"}.`);
|
|
57
|
+
}
|
|
58
|
+
if (!task?.lessonCandidateDecisionComplete) {
|
|
59
|
+
const status = task?.lessonCandidateStatus || "missing";
|
|
60
|
+
throw new Error(`Human review confirmation requires lesson candidate decision complete; current status is ${status}.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Dynamic review submission rendering stays behavior-first until the metadata domain model PR.
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { lessonCandidatesFile, longRunningTaskContractFile, nowTimestamp, readFileSafe, toPosix, visualMapFile, } from "../core-shared.mjs";
|
|
7
|
+
import { collectReviewRisks, isBlockingReviewRisk, taskScannerVersion, } from "../task-review-model.mjs";
|
|
8
|
+
import { markdownCell } from "./text-utils.mjs";
|
|
9
|
+
export function renderAgentReviewSubmission({ target, taskDir, canonicalTaskId, message, evidence }) {
|
|
10
|
+
const timestamp = nowTimestamp();
|
|
11
|
+
const submissionId = `ARS-${timestamp.replace(/[^0-9]/g, "").slice(0, 14)}`;
|
|
12
|
+
const materialsHash = hashTaskMaterials(taskDir);
|
|
13
|
+
const reviewContent = readFileSafe(path.join(taskDir, "review.md"));
|
|
14
|
+
const openFindings = collectReviewRisks(reviewContent).filter(isBlockingReviewRisk).length;
|
|
15
|
+
const evidenceSummary = evidence || message || "Agent submitted task for human review.";
|
|
16
|
+
return [
|
|
17
|
+
"## Agent Review Submission",
|
|
18
|
+
"",
|
|
19
|
+
"| Field | Value |",
|
|
20
|
+
"| --- | --- |",
|
|
21
|
+
`| Submission ID | ${submissionId} |`,
|
|
22
|
+
`| Submitted At | ${timestamp} |`,
|
|
23
|
+
"| Submitted By | agent |",
|
|
24
|
+
`| Task Key | ${canonicalTaskId} |`,
|
|
25
|
+
`| Materials Checklist Hash | ${materialsHash} |`,
|
|
26
|
+
`| Evidence Summary | ${markdownCell(evidenceSummary)} |`,
|
|
27
|
+
`| Open Findings Count | ${openFindings} |`,
|
|
28
|
+
`| Scanner Version | ${taskScannerVersion} |`,
|
|
29
|
+
`| Target | TARGET:${toPosix(path.relative(target.projectRoot, taskDir))} |`,
|
|
30
|
+
"",
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
export function replaceAgentReviewSubmission(content, block) {
|
|
34
|
+
const trimmed = String(content || "").trimEnd();
|
|
35
|
+
if (/^##\s*(?:Agent Review Submission|Agent 审查提交|Agent 提交审查)\s*$/im.test(trimmed)) {
|
|
36
|
+
return `${trimmed.replace(/^##\s*(?:Agent Review Submission|Agent 审查提交|Agent 提交审查)\s*$[\s\S]*?(?=^##\s+|(?![\s\S]))/im, `${block.trimEnd()}\n\n`)}\n`;
|
|
37
|
+
}
|
|
38
|
+
return `${trimmed}\n\n${block.trimEnd()}\n`;
|
|
39
|
+
}
|
|
40
|
+
function hashTaskMaterials(taskDir) {
|
|
41
|
+
const hash = crypto.createHash("sha256");
|
|
42
|
+
for (const fileName of ["brief.md", "task_plan.md", visualMapFile, lessonCandidatesFile, "progress.md", "review.md", "findings.md", longRunningTaskContractFile]) {
|
|
43
|
+
const filePath = path.join(taskDir, fileName);
|
|
44
|
+
if (!fs.existsSync(filePath))
|
|
45
|
+
continue;
|
|
46
|
+
hash.update(fileName);
|
|
47
|
+
hash.update("\0");
|
|
48
|
+
hash.update(readFileSafe(filePath));
|
|
49
|
+
hash.update("\0");
|
|
50
|
+
}
|
|
51
|
+
return hash.digest("hex").slice(0, 16);
|
|
52
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { localizedTemplateSource, todayDate } from "../core-shared.mjs";
|
|
4
|
+
import { markdownCell } from "./text-utils.mjs";
|
|
5
|
+
function shellArg(value) {
|
|
6
|
+
const text = String(value || "");
|
|
7
|
+
if (/^[A-Za-z0-9._/:=@+-]+$/.test(text))
|
|
8
|
+
return text;
|
|
9
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
10
|
+
}
|
|
11
|
+
function commandPathArg(value, fallback) {
|
|
12
|
+
const text = String(value || fallback || ".").trim() || ".";
|
|
13
|
+
if (text === ".")
|
|
14
|
+
return ".";
|
|
15
|
+
return path.isAbsolute(text) ? fallback : text;
|
|
16
|
+
}
|
|
17
|
+
function renderNewTaskCommand({ taskId, title, locale, budget, longRunning, moduleKey, preset, fromSession, targetInput }) {
|
|
18
|
+
const parts = ["harness", "new-task"];
|
|
19
|
+
if (taskId)
|
|
20
|
+
parts.push(taskId);
|
|
21
|
+
parts.push("--budget", budget, "--locale", locale);
|
|
22
|
+
if (title)
|
|
23
|
+
parts.push("--title", title);
|
|
24
|
+
if (moduleKey)
|
|
25
|
+
parts.push("--module", moduleKey);
|
|
26
|
+
if (preset && preset !== "none")
|
|
27
|
+
parts.push("--preset", preset);
|
|
28
|
+
if (fromSession)
|
|
29
|
+
parts.push("--from-session", commandPathArg(fromSession, "<session.json>"));
|
|
30
|
+
if (longRunning)
|
|
31
|
+
parts.push("--long-running");
|
|
32
|
+
parts.push(commandPathArg(targetInput, "<target>"));
|
|
33
|
+
return parts.map(shellArg).join(" ");
|
|
34
|
+
}
|
|
35
|
+
export function buildScaffoldProvenance({ taskId, normalizedTaskId, title, locale, budget, longRunning, moduleKey, preset, fromSession, targetInput, automaticTaskId = false }) {
|
|
36
|
+
return {
|
|
37
|
+
createdBy: "harness new-task",
|
|
38
|
+
command: markdownCell(renderNewTaskCommand({
|
|
39
|
+
taskId: automaticTaskId ? "" : taskId || normalizedTaskId,
|
|
40
|
+
title,
|
|
41
|
+
locale,
|
|
42
|
+
budget,
|
|
43
|
+
longRunning,
|
|
44
|
+
moduleKey,
|
|
45
|
+
preset,
|
|
46
|
+
fromSession,
|
|
47
|
+
targetInput,
|
|
48
|
+
})),
|
|
49
|
+
createdAt: todayDate(),
|
|
50
|
+
budget,
|
|
51
|
+
templateSource: localizedTemplateSource("templates/planning/brief.md", locale),
|
|
52
|
+
exceptionReason: "n/a",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
|
|
2
|
+
import { localizedTemplateSource, longRunningTaskContractFile, visualMapFile, lessonCandidatesFile } from "../core-shared.mjs";
|
|
3
|
+
export function taskTemplateFiles({ locale = "en-US" } = {}) {
|
|
4
|
+
return [
|
|
5
|
+
["INDEX.md", "templates/planning/INDEX.md"],
|
|
6
|
+
["brief.md", "templates/planning/brief.md"],
|
|
7
|
+
["task_plan.md", "templates/planning/task_plan.md"],
|
|
8
|
+
["execution_strategy.md", "templates/planning/execution_strategy.md"],
|
|
9
|
+
[visualMapFile, "templates/planning/visual_map.md"],
|
|
10
|
+
["findings.md", "templates/planning/findings.md"],
|
|
11
|
+
[lessonCandidatesFile, "templates/planning/lesson_candidates.md"],
|
|
12
|
+
["progress.md", "templates/planning/progress.md"],
|
|
13
|
+
["review.md", "templates/planning/review.md"],
|
|
14
|
+
].map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
|
|
15
|
+
}
|
|
16
|
+
export function simpleTaskTemplateFiles({ locale = "en-US" } = {}) {
|
|
17
|
+
return [
|
|
18
|
+
["INDEX.md", "templates/planning/INDEX.md"],
|
|
19
|
+
["brief.md", "templates/planning/brief.md"],
|
|
20
|
+
["task_plan.md", "templates/planning/task_plan.md"],
|
|
21
|
+
[visualMapFile, "templates/planning/visual_map.simple.md"],
|
|
22
|
+
["progress.md", "templates/planning/progress.md"],
|
|
23
|
+
].map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
|
|
24
|
+
}
|
|
25
|
+
export function optionalTaskTemplateFiles({ locale = "en-US" } = {}) {
|
|
26
|
+
return [
|
|
27
|
+
["references/INDEX.md", "templates/planning/optional/references/INDEX.md"],
|
|
28
|
+
["artifacts/INDEX.md", "templates/planning/optional/artifacts/INDEX.md"],
|
|
29
|
+
].map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
|
|
30
|
+
}
|
|
31
|
+
export function moduleTemplateFiles({ locale = "en-US" } = {}) {
|
|
32
|
+
return [
|
|
33
|
+
["brief.md", "templates/planning/module_brief.md"],
|
|
34
|
+
["module_plan.md", "templates/planning/module_plan.md"],
|
|
35
|
+
["execution_strategy.md", "templates/planning/execution_strategy.md"],
|
|
36
|
+
[visualMapFile, "templates/planning/visual_map.md"],
|
|
37
|
+
["session_prompt.md", "templates/planning/module_session_prompt.md"],
|
|
38
|
+
].map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
|
|
39
|
+
}
|
|
40
|
+
export function taskFilesForBudget({ budget, locale }) {
|
|
41
|
+
const files = budget === "simple"
|
|
42
|
+
? simpleTaskTemplateFiles({ locale })
|
|
43
|
+
: budget === "complex"
|
|
44
|
+
? [...taskTemplateFiles({ locale }), ...optionalTaskTemplateFiles({ locale })]
|
|
45
|
+
: taskTemplateFiles({ locale });
|
|
46
|
+
return [...files, ["walkthrough.md", localizedTemplateSource("templates/planning/walkthrough.md", locale)]];
|
|
47
|
+
}
|
|
48
|
+
export function appendLongRunningContractFile(files, { locale, longRunning }) {
|
|
49
|
+
if (!longRunning)
|
|
50
|
+
return files;
|
|
51
|
+
return [...files, [longRunningTaskContractFile, localizedTemplateSource("templates/planning/long-running-task-contract.md", locale)]];
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
|
|
2
|
+
import { nowTimestamp } from "../core-shared.mjs";
|
|
3
|
+
export function appendProgressLog(content, { event, message, evidence, actor = "coordinator" }) {
|
|
4
|
+
const timestamp = nowTimestamp();
|
|
5
|
+
const safeMessage = String(message || event).replace(/\r?\n/g, " ").trim();
|
|
6
|
+
const safeEvidence = String(evidence || "n/a").replace(/\r?\n/g, " ").trim();
|
|
7
|
+
if (/^##\s*Log\s*$/im.test(content)) {
|
|
8
|
+
return content.replace(/(^##\s*Log\s*$[\s\S]*?\| --- \| --- \| --- \| --- \| --- \|\n)/im, `$1| ${timestamp} | ${actor} | ${event}: ${safeMessage} | ${safeEvidence} | ${event === "task-complete" ? "done" : "continue"} |\n`);
|
|
9
|
+
}
|
|
10
|
+
if (/^##\s*进度记录\s*$/im.test(content)) {
|
|
11
|
+
return `${content.trimEnd()}\n\n### [${timestamp}] - ${event}\n\n- 做了什么:${safeMessage}\n- 验证结果:已记录\n- 下一步:${event === "task-complete" ? "完成" : "继续执行"}\n- 证据:${safeEvidence}\n`;
|
|
12
|
+
}
|
|
13
|
+
return `${content.trimEnd()}\n\n## Log\n\n| Time | Actor | Action | Evidence | Next |\n| --- | --- | --- | --- | --- |\n| ${timestamp} | ${actor} | ${event}: ${safeMessage} | ${safeEvidence} | ${event === "task-complete" ? "done" : "continue"} |\n`;
|
|
14
|
+
}
|
|
15
|
+
export function markdownCell(value) {
|
|
16
|
+
return String(value || "")
|
|
17
|
+
.replace(/\r?\n/g, " ")
|
|
18
|
+
.replaceAll("|", "\\|")
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
export function markWalkthroughClosed(content) {
|
|
22
|
+
if (/^Closeout Status\s*:/im.test(content)) {
|
|
23
|
+
return content.replace(/^Closeout Status\s*:[^\n]*/im, "Closeout Status: closed").trimEnd() + "\n";
|
|
24
|
+
}
|
|
25
|
+
return `${String(content || "").trimEnd()}\n\nCloseout Status: closed\n`;
|
|
26
|
+
}
|