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,161 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Governance table parsing remains behavior-first until target/table domain types are modeled.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { readFileSafe, toPosix } from "./core-shared.mjs";
|
|
6
|
+
import { getCell, parseAllMarkdownTables } from "./markdown-utils.mjs";
|
|
7
|
+
const newRuleCutoff = "2026-05-24";
|
|
8
|
+
const globalTableSpecs = [
|
|
9
|
+
{ key: "feature-ssot", pathFor: (target) => path.join(target.harness.planningRoot, "Feature-SSoT.md"), allowed: "index-state-route-summary", evaluate: evaluateFeatureRow },
|
|
10
|
+
{ key: "harness-ledger", pathFor: (target) => target.harness.ledgerPath, allowed: "task-audit-summary-route", evaluate: evaluateLedgerRow },
|
|
11
|
+
{ key: "closeout-ssot", pathFor: (target) => target.harness.closeoutIndexPath, allowed: "closeout-index-review-route-summary", evaluate: evaluateCloseoutRow },
|
|
12
|
+
{ key: "regression-ssot", pathFor: (target) => path.join(target.harness.regressionRoot, "Regression-SSoT.md"), allowed: "gate-index-current-state", evaluate: evaluateRegressionRow },
|
|
13
|
+
{ key: "cadence-ledger", pathFor: (target) => path.join(target.harness.regressionRoot, "Cadence-Ledger.md"), allowed: "trigger-rule-and-batch-summary", evaluate: evaluateCadenceRow },
|
|
14
|
+
];
|
|
15
|
+
export function validateGovernanceTableBoundaries(target) {
|
|
16
|
+
const failures = [];
|
|
17
|
+
const warnings = [];
|
|
18
|
+
for (const spec of globalTableSpecs) {
|
|
19
|
+
const file = spec.pathFor(target);
|
|
20
|
+
if (!fs.existsSync(file))
|
|
21
|
+
continue;
|
|
22
|
+
const relative = toPosix(path.relative(target.projectRoot, file));
|
|
23
|
+
for (const table of parseAllMarkdownTables(readFileSafe(file), relative, spec.key)) {
|
|
24
|
+
for (const row of table.rows) {
|
|
25
|
+
if (isPlaceholderRow(row))
|
|
26
|
+
continue;
|
|
27
|
+
for (const finding of spec.evaluate(row)) {
|
|
28
|
+
const rowKey = governanceRowKey(row);
|
|
29
|
+
const message = [
|
|
30
|
+
"governance-table-entropy",
|
|
31
|
+
`${relative}:${table.line}`,
|
|
32
|
+
`${spec.key} row ${rowKey}`,
|
|
33
|
+
finding.reason,
|
|
34
|
+
`allowed=${spec.allowed}`,
|
|
35
|
+
`route=${finding.route}`,
|
|
36
|
+
].join(": ");
|
|
37
|
+
if (isLegacyRow(rowUpdatedDate(row)))
|
|
38
|
+
warnings.push(`${message}: legacy-report-only`);
|
|
39
|
+
else
|
|
40
|
+
failures.push(message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { failures, warnings };
|
|
46
|
+
}
|
|
47
|
+
function evaluateFeatureRow(row) {
|
|
48
|
+
const cells = row.cells || {};
|
|
49
|
+
const text = rowText(row);
|
|
50
|
+
const taskPlan = getCell(cells, ["Task Plan", "Task", "任务计划", "路径"], "");
|
|
51
|
+
const evidence = getCell(cells, ["Acceptance Evidence", "Evidence", "验收证据"], "");
|
|
52
|
+
const findings = [];
|
|
53
|
+
if (/(?:09-PLANNING\/MODULES|planning\/modules)\//i.test(taskPlan) && !isModuleAggregateRow(row) && localDetailPattern().test(text)) {
|
|
54
|
+
findings.push({
|
|
55
|
+
reason: "module-local detail belongs in module_plan.md or task files, not Feature SSoT",
|
|
56
|
+
route: "module-plan-or-task-detail",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (longEvidencePattern().test(evidence) || temporaryPromptPattern().test(text)) {
|
|
60
|
+
findings.push({
|
|
61
|
+
reason: "long evidence or temporary repair prompt belongs in task evidence, not Feature SSoT",
|
|
62
|
+
route: "task-artifacts-or-progress",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return findings;
|
|
66
|
+
}
|
|
67
|
+
function isModuleAggregateRow(row) {
|
|
68
|
+
const cells = row.cells || {};
|
|
69
|
+
const id = getCell(cells, ["ID"], "");
|
|
70
|
+
const taskPlan = getCell(cells, ["Task Plan", "Task", "任务计划", "路径"], "");
|
|
71
|
+
return /^F-MODULE-/i.test(id) && /(?:09-PLANNING\/MODULES|planning\/modules)\/[^/]+\/module_plan\.md/i.test(taskPlan);
|
|
72
|
+
}
|
|
73
|
+
function evaluateLedgerRow(row) {
|
|
74
|
+
const text = rowText(row);
|
|
75
|
+
const evidence = ledgerEvidenceText(row);
|
|
76
|
+
if (executionLogPattern().test(evidence) || temporaryPromptPattern().test(text) || rawTranscriptPattern().test(evidence)) {
|
|
77
|
+
return [{
|
|
78
|
+
reason: "execution logs, long evidence, and temporary repair prompts belong in task progress/review/artifacts",
|
|
79
|
+
route: "task-progress-review-artifacts",
|
|
80
|
+
}];
|
|
81
|
+
}
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
function ledgerEvidenceText(row) {
|
|
85
|
+
const cells = row.cells || {};
|
|
86
|
+
return [
|
|
87
|
+
"Evidence Summary",
|
|
88
|
+
"Evidence",
|
|
89
|
+
"Regression Evidence",
|
|
90
|
+
"Review Evidence",
|
|
91
|
+
"Regression",
|
|
92
|
+
"Review",
|
|
93
|
+
"证据摘要",
|
|
94
|
+
"证据",
|
|
95
|
+
"回归",
|
|
96
|
+
"审查",
|
|
97
|
+
].map((column) => getCell(cells, [column], "")).filter(Boolean).join(" ");
|
|
98
|
+
}
|
|
99
|
+
function evaluateCloseoutRow(row) {
|
|
100
|
+
const text = rowText(row);
|
|
101
|
+
if (executionLogPattern().test(text) || rawTranscriptPattern().test(text)) {
|
|
102
|
+
return [{
|
|
103
|
+
reason: "closeout rows should route to walkthrough/evidence instead of carrying execution detail",
|
|
104
|
+
route: "walkthrough-or-task-evidence",
|
|
105
|
+
}];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
function evaluateRegressionRow(row) {
|
|
110
|
+
const text = rowText(row);
|
|
111
|
+
if (executionLogPattern().test(text) || temporaryPromptPattern().test(text)) {
|
|
112
|
+
return [{
|
|
113
|
+
reason: "regression global tables should keep gate state and route detailed failure analysis elsewhere",
|
|
114
|
+
route: "regression-detail-or-task-review",
|
|
115
|
+
}];
|
|
116
|
+
}
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
function evaluateCadenceRow(row) {
|
|
120
|
+
const text = rowText(row);
|
|
121
|
+
if (rawTranscriptPattern().test(text) || temporaryPromptPattern().test(text)) {
|
|
122
|
+
return [{
|
|
123
|
+
reason: "cadence rows should summarize batch outcomes and route raw run detail elsewhere",
|
|
124
|
+
route: "regression-batch-artifacts",
|
|
125
|
+
}];
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
function governanceRowKey(row) {
|
|
130
|
+
return getCell(row.cells || {}, ["ID", "Lesson", "Lesson ID", "Feature", "Work Item", "Gate ID", "Batch ID"], "") || row.id || "unknown-row";
|
|
131
|
+
}
|
|
132
|
+
function rowText(row) {
|
|
133
|
+
return Object.values(row.cells || {}).join(" ");
|
|
134
|
+
}
|
|
135
|
+
function rowUpdatedDate(row) {
|
|
136
|
+
const value = getCell(row.cells || {}, ["Updated", "Date", "日期", "Last Verified", "最近验证"], "");
|
|
137
|
+
const match = String(value).match(/\d{4}-\d{2}-\d{2}/);
|
|
138
|
+
return match ? match[0] : "";
|
|
139
|
+
}
|
|
140
|
+
function isLegacyRow(updated) {
|
|
141
|
+
return updated && updated < newRuleCutoff;
|
|
142
|
+
}
|
|
143
|
+
function isPlaceholderRow(row) {
|
|
144
|
+
const text = rowText(row);
|
|
145
|
+
return /\b(?:YYYY|MM|DD|NNN)\b|L-YYYY|HL-YYYY|\[[^\]]+\]|\.\.\.md|\bowner\b|\bShort lesson title\b/i.test(text);
|
|
146
|
+
}
|
|
147
|
+
function localDetailPattern() {
|
|
148
|
+
return /\b(module|local|implementation detail|parser branch|button label|copy every|工作项|局部|实现细节)\b/i;
|
|
149
|
+
}
|
|
150
|
+
function longEvidencePattern() {
|
|
151
|
+
return /\b(long evidence|full local evidence|raw evidence|stack trace|reviewer transcript|copied raw)\b/i;
|
|
152
|
+
}
|
|
153
|
+
function executionLogPattern() {
|
|
154
|
+
return /\b(execution log|command failed|stack trace|raw output|step one|step two|执行流水|命令输出)\b/i;
|
|
155
|
+
}
|
|
156
|
+
function temporaryPromptPattern() {
|
|
157
|
+
return /\b(temporary repair prompt|repair prompt|copyable prompt|paste back|临时修复提示)\b/i;
|
|
158
|
+
}
|
|
159
|
+
function rawTranscriptPattern() {
|
|
160
|
+
return /\b(raw transcript|reviewer transcript|full transcript|完整记录|原始记录)\b/i;
|
|
161
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
export * from "./core-shared.mjs";
|
|
2
3
|
export * from "./markdown-utils.mjs";
|
|
3
4
|
export * from "./capability-registry.mjs";
|
|
4
5
|
export * from "./task-scanner.mjs";
|
|
6
|
+
export * from "./status-builder.mjs";
|
|
5
7
|
export * from "./check-profiles.mjs";
|
|
6
8
|
export * from "./dashboard-data.mjs";
|
|
7
9
|
export * from "./dashboard-workbench.mjs";
|
|
8
10
|
export * from "./migration-planner.mjs";
|
|
11
|
+
export * from "./structure-migration.mjs";
|
|
9
12
|
export * from "./preset-registry.mjs";
|
|
10
13
|
export * from "./governance-index-generator.mjs";
|
|
11
14
|
export * from "./task-lifecycle.mjs";
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const v2HarnessRoot = "coding-agent-harness";
|
|
4
|
+
export const legacyPlanningRoot = ["docs", "09-PLANNING"];
|
|
5
|
+
export const legacyTaskRoot = [...legacyPlanningRoot, "TASKS"];
|
|
6
|
+
export const legacyModuleRoot = [...legacyPlanningRoot, "MODULES"];
|
|
7
|
+
export const legacyWalkthroughRoot = ["docs", "10-WALKTHROUGH"];
|
|
8
|
+
export const legacyLedgerFile = ["docs", "Harness-Ledger.md"];
|
|
9
|
+
export const legacyCloseoutFile = [...legacyWalkthroughRoot, "Closeout-SSoT.md"];
|
|
10
|
+
export const legacyCompatMode = "legacy-compat";
|
|
11
|
+
export const safeAdoptionCapability = "safe-adoption";
|
|
12
|
+
export function legacyPath(...segments) {
|
|
13
|
+
return segments.flat().join("/");
|
|
14
|
+
}
|
|
15
|
+
export function resolveHarnessPaths(targetInput = ".") {
|
|
16
|
+
const target = normalizeTargetShape(targetInput);
|
|
17
|
+
const manifestPath = path.join(target.harnessRootCandidate, "harness.yaml");
|
|
18
|
+
const manifest = readHarnessManifest(manifestPath);
|
|
19
|
+
if (manifest) {
|
|
20
|
+
const structure = manifest.structure || {};
|
|
21
|
+
const harnessRoot = structure.harnessRoot || v2HarnessRoot;
|
|
22
|
+
const planningRoot = structure.planningRoot || `${harnessRoot}/planning`;
|
|
23
|
+
const tasksRoot = structure.tasksRoot || `${planningRoot}/tasks`;
|
|
24
|
+
const modulesRoot = structure.modulesRoot || `${planningRoot}/modules`;
|
|
25
|
+
const externalRoot = structure.externalRoot || `${planningRoot}/external`;
|
|
26
|
+
const governanceRoot = structure.governanceRoot || `${harnessRoot}/governance`;
|
|
27
|
+
const generatedRoot = structure.generatedRoot || `${governanceRoot}/generated`;
|
|
28
|
+
const regressionRoot = structure.regressionRoot || `${governanceRoot}/regression`;
|
|
29
|
+
const resolved = Object.fromEntries(Object.entries({
|
|
30
|
+
harnessRoot,
|
|
31
|
+
planningRoot,
|
|
32
|
+
tasksRoot,
|
|
33
|
+
modulesRoot,
|
|
34
|
+
externalRoot,
|
|
35
|
+
governanceRoot,
|
|
36
|
+
generatedRoot,
|
|
37
|
+
regressionRoot,
|
|
38
|
+
}).map(([key, value]) => [key, resolveManifestStructurePath(target.projectRoot, key, value)]));
|
|
39
|
+
return {
|
|
40
|
+
version: 2,
|
|
41
|
+
manifest,
|
|
42
|
+
manifestPath,
|
|
43
|
+
input: target.input,
|
|
44
|
+
projectRoot: target.projectRoot,
|
|
45
|
+
docsRoot: target.docsRoot,
|
|
46
|
+
docsOnly: target.docsOnly,
|
|
47
|
+
harnessRoot: resolved.harnessRoot,
|
|
48
|
+
planningRoot: resolved.planningRoot,
|
|
49
|
+
tasksRoot: resolved.tasksRoot,
|
|
50
|
+
modulesRoot: resolved.modulesRoot,
|
|
51
|
+
taskRoots: [resolved.tasksRoot, resolved.modulesRoot],
|
|
52
|
+
externalRoot: resolved.externalRoot,
|
|
53
|
+
governanceRoot: resolved.governanceRoot,
|
|
54
|
+
generatedRoot: resolved.generatedRoot,
|
|
55
|
+
regressionRoot: resolved.regressionRoot,
|
|
56
|
+
ledgerPath: path.join(resolved.generatedRoot, "Harness-Ledger.md"),
|
|
57
|
+
closeoutIndexPath: path.join(resolved.generatedRoot, "Closeout-Index.md"),
|
|
58
|
+
legacy: legacyPaths(target.projectRoot),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const legacy = legacyPaths(target.projectRoot);
|
|
62
|
+
return {
|
|
63
|
+
version: 1,
|
|
64
|
+
manifest: null,
|
|
65
|
+
manifestPath,
|
|
66
|
+
input: target.input,
|
|
67
|
+
projectRoot: target.projectRoot,
|
|
68
|
+
docsRoot: target.docsRoot,
|
|
69
|
+
docsOnly: target.docsOnly,
|
|
70
|
+
harnessRoot: target.docsRoot,
|
|
71
|
+
planningRoot: legacy.planningRoot,
|
|
72
|
+
tasksRoot: legacy.tasksRoot,
|
|
73
|
+
modulesRoot: legacy.modulesRoot,
|
|
74
|
+
taskRoots: [legacy.tasksRoot, legacy.modulesRoot],
|
|
75
|
+
externalRoot: "",
|
|
76
|
+
governanceRoot: target.docsRoot,
|
|
77
|
+
generatedRoot: path.join(legacy.planningRoot, "generated"),
|
|
78
|
+
regressionRoot: path.join(target.docsRoot, "05-TEST-QA"),
|
|
79
|
+
ledgerPath: legacy.ledgerPath,
|
|
80
|
+
closeoutIndexPath: legacy.closeoutPath,
|
|
81
|
+
legacy,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function taskIdFromDirectory(paths, taskDir) {
|
|
85
|
+
const normalized = path.resolve(taskDir);
|
|
86
|
+
const tasksRoot = path.resolve(paths.tasksRoot);
|
|
87
|
+
const modulesRoot = path.resolve(paths.modulesRoot);
|
|
88
|
+
const externalRoot = paths.externalRoot ? path.resolve(paths.externalRoot) : "";
|
|
89
|
+
if (isPathInside(normalized, tasksRoot))
|
|
90
|
+
return `TASKS/${toPosix(path.relative(tasksRoot, normalized))}`;
|
|
91
|
+
if (isPathInside(normalized, modulesRoot)) {
|
|
92
|
+
const relative = toPosix(path.relative(modulesRoot, normalized));
|
|
93
|
+
const match = relative.match(/^([^/]+)\/tasks\/(.+)$/);
|
|
94
|
+
return match ? `MODULES/${match[1]}/${match[2]}` : `MODULES/${relative}`;
|
|
95
|
+
}
|
|
96
|
+
if (externalRoot && isPathInside(normalized, externalRoot))
|
|
97
|
+
return `EXTERNAL/${toPosix(path.relative(externalRoot, normalized))}`;
|
|
98
|
+
if (paths.version === 1)
|
|
99
|
+
return toPosix(path.relative(paths.planningRoot, normalized));
|
|
100
|
+
return toPosix(path.relative(paths.projectRoot, normalized));
|
|
101
|
+
}
|
|
102
|
+
export function taskRefPath(paths, raw) {
|
|
103
|
+
if (/^TASKS\//.test(raw))
|
|
104
|
+
return path.join(paths.tasksRoot, raw.replace(/^TASKS\//, ""));
|
|
105
|
+
if (/^MODULES\//.test(raw))
|
|
106
|
+
return moduleRefPath(paths, raw.replace(/^MODULES\//, ""));
|
|
107
|
+
if (/^EXTERNAL\//.test(raw) && paths.externalRoot)
|
|
108
|
+
return path.join(paths.externalRoot, raw.replace(/^EXTERNAL\//, ""));
|
|
109
|
+
if (/^(tasks|modules|external)\//.test(raw))
|
|
110
|
+
return path.join(paths.planningRoot, raw);
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
export function taskLocalWalkthrough(paths, taskDir) {
|
|
114
|
+
if (paths.version !== 2)
|
|
115
|
+
return "";
|
|
116
|
+
const walkthrough = path.join(taskDir, "walkthrough.md");
|
|
117
|
+
if (!fs.existsSync(walkthrough))
|
|
118
|
+
return "";
|
|
119
|
+
let stat;
|
|
120
|
+
try {
|
|
121
|
+
stat = fs.lstatSync(walkthrough);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
127
|
+
return "";
|
|
128
|
+
return toPosix(path.relative(paths.projectRoot, walkthrough));
|
|
129
|
+
}
|
|
130
|
+
export function dashboardWatchRoots(paths) {
|
|
131
|
+
const roots = paths.version === 2
|
|
132
|
+
? [
|
|
133
|
+
paths.harnessRoot,
|
|
134
|
+
paths.planningRoot,
|
|
135
|
+
paths.tasksRoot,
|
|
136
|
+
paths.modulesRoot,
|
|
137
|
+
paths.externalRoot,
|
|
138
|
+
paths.governanceRoot,
|
|
139
|
+
paths.generatedRoot,
|
|
140
|
+
paths.regressionRoot,
|
|
141
|
+
]
|
|
142
|
+
: [paths.docsRoot];
|
|
143
|
+
return dedupeAncestorRoots(roots.filter(Boolean).map((root) => path.resolve(root)).filter((root) => fs.existsSync(root)));
|
|
144
|
+
}
|
|
145
|
+
export function discoverImplicitHarnessTarget(input = ".") {
|
|
146
|
+
const root = path.resolve(input || ".");
|
|
147
|
+
const nearest = findNearestHarnessRoot(root);
|
|
148
|
+
if (nearest)
|
|
149
|
+
return projectRootForHarnessRoot(nearest);
|
|
150
|
+
const discovered = findProjectHarnessRoot(root, { requireDeclaredProjectRoot: false });
|
|
151
|
+
return discovered ? projectRootForHarnessRoot(discovered) : "";
|
|
152
|
+
}
|
|
153
|
+
function moduleRefPath(paths, relative) {
|
|
154
|
+
if (paths.version !== 2)
|
|
155
|
+
return path.join(paths.modulesRoot, relative);
|
|
156
|
+
const [moduleKey = "", ...taskSegments] = relative.split("/");
|
|
157
|
+
return taskSegments.length ? path.join(paths.modulesRoot, moduleKey, "tasks", ...taskSegments) : path.join(paths.modulesRoot, moduleKey);
|
|
158
|
+
}
|
|
159
|
+
export function toPosix(value) {
|
|
160
|
+
return String(value).split(path.sep).join("/");
|
|
161
|
+
}
|
|
162
|
+
function normalizeTargetShape(input = ".") {
|
|
163
|
+
if (input && typeof input === "object" && input.projectRoot) {
|
|
164
|
+
const requestedProjectRoot = path.resolve(input.projectRoot);
|
|
165
|
+
const inputPath = path.resolve(input.input || requestedProjectRoot);
|
|
166
|
+
const directHarnessRoot = findNearestHarnessRoot(inputPath);
|
|
167
|
+
const discoveredHarnessRoot = directHarnessRoot || findProjectHarnessRoot(requestedProjectRoot);
|
|
168
|
+
const projectRoot = directHarnessRoot
|
|
169
|
+
? projectRootForHarnessRoot(directHarnessRoot)
|
|
170
|
+
: requestedProjectRoot;
|
|
171
|
+
return {
|
|
172
|
+
...input,
|
|
173
|
+
projectRoot,
|
|
174
|
+
docsRoot: input.docsRoot || path.join(projectRoot, "docs"),
|
|
175
|
+
harnessRootCandidate: input.harnessRootCandidate || discoveredHarnessRoot || path.join(projectRoot, v2HarnessRoot),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const target = path.resolve(typeof input === "string" ? input || "." : ".");
|
|
179
|
+
const docsProjectRoot = path.dirname(target);
|
|
180
|
+
const siblingHarnessRoot = findProjectHarnessRoot(docsProjectRoot);
|
|
181
|
+
const siblingV2Manifest = siblingHarnessRoot
|
|
182
|
+
? path.join(siblingHarnessRoot, "harness.yaml")
|
|
183
|
+
: path.join(docsProjectRoot, v2HarnessRoot, "harness.yaml");
|
|
184
|
+
const isDocsRoot = path.basename(target) === "docs" &&
|
|
185
|
+
(fs.existsSync(path.join(target, "09-PLANNING")) || fs.existsSync(path.join(target, "11-REFERENCE")) || fs.existsSync(siblingV2Manifest));
|
|
186
|
+
const directHarnessRoot = !isDocsRoot ? findNearestHarnessRoot(target) : "";
|
|
187
|
+
const discoveredHarnessRoot = directHarnessRoot || (!isDocsRoot ? findProjectHarnessRoot(target) : siblingHarnessRoot);
|
|
188
|
+
const projectRoot = isDocsRoot ? docsProjectRoot : directHarnessRoot ? projectRootForHarnessRoot(directHarnessRoot) : target;
|
|
189
|
+
return {
|
|
190
|
+
input: target,
|
|
191
|
+
projectRoot,
|
|
192
|
+
docsRoot: isDocsRoot ? target : path.join(target, "docs"),
|
|
193
|
+
docsOnly: isDocsRoot,
|
|
194
|
+
harnessRootCandidate: discoveredHarnessRoot || path.join(projectRoot, v2HarnessRoot),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function findNearestHarnessRoot(target) {
|
|
198
|
+
let current = target;
|
|
199
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
200
|
+
if (fs.existsSync(path.join(current, "harness.yaml")))
|
|
201
|
+
return current;
|
|
202
|
+
const parent = path.dirname(current);
|
|
203
|
+
if (parent === current)
|
|
204
|
+
break;
|
|
205
|
+
current = parent;
|
|
206
|
+
}
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
function findProjectHarnessRoot(projectRoot, { requireDeclaredProjectRoot = true } = {}) {
|
|
210
|
+
const defaultRoot = path.join(projectRoot, v2HarnessRoot);
|
|
211
|
+
if (fs.existsSync(path.join(defaultRoot, "harness.yaml")))
|
|
212
|
+
return defaultRoot;
|
|
213
|
+
const candidates = [];
|
|
214
|
+
const ignored = new Set([".git", "node_modules", "tmp", "dist", "build", ".next", "coverage"]);
|
|
215
|
+
function visit(dir, depth) {
|
|
216
|
+
if (depth > 5 || !fs.existsSync(dir))
|
|
217
|
+
return;
|
|
218
|
+
if (fs.existsSync(path.join(dir, "harness.yaml")) && (!requireDeclaredProjectRoot || projectRootForHarnessRoot(dir) === projectRoot)) {
|
|
219
|
+
candidates.push(dir);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
223
|
+
if (ignored.has(entry))
|
|
224
|
+
continue;
|
|
225
|
+
const full = path.join(dir, entry);
|
|
226
|
+
let stat;
|
|
227
|
+
try {
|
|
228
|
+
stat = fs.lstatSync(full);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
234
|
+
visit(full, depth + 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
visit(projectRoot, 0);
|
|
238
|
+
const unique = [...new Set(candidates)].sort((left, right) => {
|
|
239
|
+
const leftDepth = path.relative(projectRoot, left).split(path.sep).filter(Boolean).length;
|
|
240
|
+
const rightDepth = path.relative(projectRoot, right).split(path.sep).filter(Boolean).length;
|
|
241
|
+
return leftDepth - rightDepth || left.localeCompare(right);
|
|
242
|
+
});
|
|
243
|
+
if (unique.length > 1) {
|
|
244
|
+
const shallowestDepth = path.relative(projectRoot, unique[0]).split(path.sep).filter(Boolean).length;
|
|
245
|
+
const shallowest = unique.filter((item) => path.relative(projectRoot, item).split(path.sep).filter(Boolean).length === shallowestDepth);
|
|
246
|
+
if (shallowest.length === 1)
|
|
247
|
+
return shallowest[0];
|
|
248
|
+
throw new Error(`Multiple v2 harness manifests found at the same nearest depth; pass the intended harness root explicitly: ${shallowest.map((item) => toPosix(path.relative(projectRoot, item))).join(", ")}`);
|
|
249
|
+
}
|
|
250
|
+
return unique[0] || "";
|
|
251
|
+
}
|
|
252
|
+
function projectRootForHarnessRoot(harnessRoot) {
|
|
253
|
+
const manifest = readHarnessManifest(path.join(harnessRoot, "harness.yaml"));
|
|
254
|
+
const declaredHarnessRoot = manifest?.structure?.harnessRoot || v2HarnessRoot;
|
|
255
|
+
let current = harnessRoot;
|
|
256
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
257
|
+
if (path.resolve(current, declaredHarnessRoot) === path.resolve(harnessRoot))
|
|
258
|
+
return current;
|
|
259
|
+
const parent = path.dirname(current);
|
|
260
|
+
if (parent === current)
|
|
261
|
+
break;
|
|
262
|
+
current = parent;
|
|
263
|
+
}
|
|
264
|
+
return path.dirname(harnessRoot);
|
|
265
|
+
}
|
|
266
|
+
function legacyPaths(projectRoot) {
|
|
267
|
+
const docsRoot = path.join(projectRoot, "docs");
|
|
268
|
+
const planningRoot = path.join(docsRoot, ...legacyPlanningRoot.slice(1));
|
|
269
|
+
return {
|
|
270
|
+
docsRoot,
|
|
271
|
+
planningRoot,
|
|
272
|
+
tasksRoot: path.join(docsRoot, ...legacyTaskRoot.slice(1)),
|
|
273
|
+
modulesRoot: path.join(docsRoot, ...legacyModuleRoot.slice(1)),
|
|
274
|
+
walkthroughRoot: path.join(docsRoot, ...legacyWalkthroughRoot.slice(1)),
|
|
275
|
+
ledgerPath: path.join(projectRoot, ...legacyLedgerFile),
|
|
276
|
+
closeoutPath: path.join(projectRoot, ...legacyCloseoutFile),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function readHarnessManifest(manifestPath) {
|
|
280
|
+
if (!fs.existsSync(manifestPath))
|
|
281
|
+
return null;
|
|
282
|
+
const manifest = { version: 2, locale: "en-US", capabilities: [], structure: {} };
|
|
283
|
+
let section = "";
|
|
284
|
+
for (const rawLine of fs.readFileSync(manifestPath, "utf8").split(/\r?\n/)) {
|
|
285
|
+
const line = rawLine.replace(/\s+#.*$/, "");
|
|
286
|
+
if (!line.trim())
|
|
287
|
+
continue;
|
|
288
|
+
const top = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
289
|
+
if (top) {
|
|
290
|
+
section = top[1];
|
|
291
|
+
if (section === "version")
|
|
292
|
+
manifest.version = Number(top[2]) || 2;
|
|
293
|
+
else if (section === "locale")
|
|
294
|
+
manifest.locale = top[2] || "en-US";
|
|
295
|
+
else if (section !== "structure" && section !== "capabilities")
|
|
296
|
+
manifest[section] = top[2];
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const listItem = line.match(/^\s*-\s*(.+)$/);
|
|
300
|
+
if (section === "capabilities" && listItem) {
|
|
301
|
+
manifest.capabilities.push(listItem[1].trim());
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const nested = line.match(/^\s+([A-Za-z][A-Za-z0-9_-]*):\s*(.+)$/);
|
|
305
|
+
if (section === "structure" && nested)
|
|
306
|
+
manifest.structure[nested[1]] = nested[2].trim();
|
|
307
|
+
}
|
|
308
|
+
if (!manifest.structure.harnessRoot && manifest.harnessRoot)
|
|
309
|
+
manifest.structure.harnessRoot = manifest.harnessRoot;
|
|
310
|
+
if (!manifest.structure.planningRoot && manifest.harnessRoot)
|
|
311
|
+
manifest.structure.planningRoot = `${manifest.harnessRoot}/planning`;
|
|
312
|
+
return manifest;
|
|
313
|
+
}
|
|
314
|
+
function resolveManifestStructurePath(projectRoot, fieldName, relativePath) {
|
|
315
|
+
const raw = String(relativePath || "").trim();
|
|
316
|
+
if (!raw)
|
|
317
|
+
throw new Error(`Invalid v2 harness manifest: structure.${fieldName} is empty`);
|
|
318
|
+
if (path.isAbsolute(raw))
|
|
319
|
+
throw new Error(`Invalid v2 harness manifest: structure.${fieldName} escapes project root: ${raw}`);
|
|
320
|
+
const resolved = path.resolve(projectRoot, raw);
|
|
321
|
+
if (!isPathInside(resolved, projectRoot)) {
|
|
322
|
+
throw new Error(`Invalid v2 harness manifest: structure.${fieldName} escapes project root: ${raw}`);
|
|
323
|
+
}
|
|
324
|
+
return resolved;
|
|
325
|
+
}
|
|
326
|
+
function isPathInside(child, parent) {
|
|
327
|
+
const relative = path.relative(parent, child);
|
|
328
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
329
|
+
}
|
|
330
|
+
function dedupeAncestorRoots(roots) {
|
|
331
|
+
const result = [];
|
|
332
|
+
for (const root of [...new Set(roots)].sort((a, b) => a.length - b.length)) {
|
|
333
|
+
if (result.some((parent) => isPathInside(root, parent)))
|
|
334
|
+
continue;
|
|
335
|
+
result.push(root);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { datePrefix, lessonCandidatesFile, normalizeTarget, readFileSafe, slug, toPosix, } from "./core-shared.mjs";
|
|
5
|
+
import { collectTasks, parseLessonCandidateStatus, } from "./task-scanner.mjs";
|
|
6
|
+
import { beginGovernanceSync, commitGovernanceSync, releaseGovernanceSync, } from "./governance-sync.mjs";
|
|
7
|
+
export function promoteLessonCandidate(targetInput, taskId, candidateId, { dryRun = false, apply = false } = {}) {
|
|
8
|
+
const target = normalizeTarget(targetInput);
|
|
9
|
+
const normalizedRef = slug(taskId);
|
|
10
|
+
const matchesBareSlug = (item) => {
|
|
11
|
+
if (!datePrefix.test(normalizedRef)) {
|
|
12
|
+
const shortBase = datePrefix.test(item.shortId) ? item.shortId.replace(datePrefix, "") : item.shortId;
|
|
13
|
+
if (shortBase === normalizedRef)
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
const candidates = collectTasks(target).filter((item) => item.id === taskId || item.shortId === taskId || item.id.endsWith(`/${taskId}`) || matchesBareSlug(item));
|
|
19
|
+
if (candidates.length > 1) {
|
|
20
|
+
const options = candidates.map((item) => `- ${item.id}`).join("\n");
|
|
21
|
+
throw new Error(`Ambiguous task reference: ${taskId}\n${options}`);
|
|
22
|
+
}
|
|
23
|
+
const task = candidates[0];
|
|
24
|
+
if (!task)
|
|
25
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
26
|
+
if (!candidateId)
|
|
27
|
+
throw new Error("Missing lesson candidate id");
|
|
28
|
+
const taskDir = path.join(target.projectRoot, task.path.replace(/^TARGET:/, ""));
|
|
29
|
+
const candidatePath = path.join(taskDir, lessonCandidatesFile);
|
|
30
|
+
const candidateContent = readFileSafe(candidatePath);
|
|
31
|
+
const parsed = parseLessonCandidateStatus(candidateContent);
|
|
32
|
+
const row = parsed.rows.find((item) => item.id.toLowerCase() === candidateId.toLowerCase());
|
|
33
|
+
if (!row)
|
|
34
|
+
throw new Error(`Lesson candidate not found: ${candidateId}`);
|
|
35
|
+
if (!["needs-promotion", "promoted"].includes(row.status)) {
|
|
36
|
+
throw new Error(`Lesson candidate must be needs-promotion before promotion; current status is ${row.status}`);
|
|
37
|
+
}
|
|
38
|
+
const lessonId = lessonIdFromCandidate(row.id);
|
|
39
|
+
const title = row.title || lessonId;
|
|
40
|
+
const detailRoot = target.harness.version === 2
|
|
41
|
+
? toPosix(path.relative(target.projectRoot, path.join(target.harness.governanceRoot, "lessons")))
|
|
42
|
+
: "docs/01-GOVERNANCE/lessons";
|
|
43
|
+
const detailRelative = `${detailRoot}/${lessonId}-${slug(title)}.md`;
|
|
44
|
+
const detailPath = path.join(target.projectRoot, detailRelative);
|
|
45
|
+
const changes = [];
|
|
46
|
+
if (!fs.existsSync(detailPath))
|
|
47
|
+
changes.push({ action: dryRun ? "would-create" : "create", path: `TARGET:${detailRelative}` });
|
|
48
|
+
if (row.status !== "promoted" || parsed.status !== "promoted")
|
|
49
|
+
changes.push({ action: dryRun ? "would-update" : "update", path: task.lessonCandidatePath || `TARGET:${toPosix(path.relative(target.projectRoot, candidatePath))}` });
|
|
50
|
+
const effectiveDryRun = dryRun || !apply;
|
|
51
|
+
if (effectiveDryRun) {
|
|
52
|
+
return {
|
|
53
|
+
dryRun: true,
|
|
54
|
+
applyRequired: true,
|
|
55
|
+
taskId: task.id,
|
|
56
|
+
candidateId: row.id,
|
|
57
|
+
lessonId,
|
|
58
|
+
detailDoc: `TARGET:${detailRelative}`,
|
|
59
|
+
changes: changes.map((change) => ({ ...change, action: change.action.replace(/^(create|append|update)$/, "would-$1") })),
|
|
60
|
+
nextCommand: `harness lesson-promote ${task.shortId} ${row.id} --apply`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const governanceContext = beginGovernanceSync(target, { operation: `lesson-promote ${task.id} ${row.id}` });
|
|
64
|
+
try {
|
|
65
|
+
fs.mkdirSync(path.dirname(detailPath), { recursive: true });
|
|
66
|
+
if (!fs.existsSync(detailPath))
|
|
67
|
+
fs.writeFileSync(detailPath, renderLessonDetail({ lessonId, candidate: row, task, detailRelative }));
|
|
68
|
+
fs.writeFileSync(candidatePath, markCandidatePromoted(candidateContent, row.id, lessonId));
|
|
69
|
+
const commit = commitGovernanceSync(governanceContext, [
|
|
70
|
+
detailRelative,
|
|
71
|
+
toPosix(path.relative(target.projectRoot, candidatePath)),
|
|
72
|
+
], { message: `chore(harness): promote lesson ${row.id}` });
|
|
73
|
+
return { dryRun: false, taskId: task.id, candidateId: row.id, lessonId, detailDoc: `TARGET:${detailRelative}`, changes, governance: { commit } };
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
releaseGovernanceSync(governanceContext);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function lessonIdFromCandidate(candidateId) {
|
|
80
|
+
const match = String(candidateId || "").match(/^LC-(\d{4})(\d{2})(\d{2})-(\d+)$/i);
|
|
81
|
+
if (!match)
|
|
82
|
+
return `L-${slug(candidateId)}`;
|
|
83
|
+
return `L-${match[1]}-${match[2]}-${match[3]}-${match[4].padStart(3, "0")}`;
|
|
84
|
+
}
|
|
85
|
+
function renderLessonDetail({ lessonId, candidate, task }) {
|
|
86
|
+
return [
|
|
87
|
+
`# ${lessonId} - ${candidate.title || "Lesson Candidate"}`,
|
|
88
|
+
"",
|
|
89
|
+
"## Source",
|
|
90
|
+
"",
|
|
91
|
+
`- Task: ${task.id}`,
|
|
92
|
+
`- Candidate: ${candidate.id}`,
|
|
93
|
+
`- Promotion target: ${candidate.promotionTarget || "not specified"}`,
|
|
94
|
+
"",
|
|
95
|
+
"## Summary",
|
|
96
|
+
"",
|
|
97
|
+
candidate.title || "Promoted lesson candidate.",
|
|
98
|
+
"",
|
|
99
|
+
"## Why It Matters",
|
|
100
|
+
"",
|
|
101
|
+
candidate.reviewDecision || "Human review marked this candidate for governance promotion.",
|
|
102
|
+
"",
|
|
103
|
+
"## Status",
|
|
104
|
+
"",
|
|
105
|
+
"- State: pending governance integration",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n");
|
|
108
|
+
}
|
|
109
|
+
function markCandidatePromoted(content, candidateId, lessonId) {
|
|
110
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
111
|
+
const headerIndex = lines.findIndex((line) => /^\|\s*ID\s*\|/.test(line));
|
|
112
|
+
if (headerIndex >= 0) {
|
|
113
|
+
const header = splitSimpleRow(lines[headerIndex]);
|
|
114
|
+
const statusIndex = header.findIndex((cell) => /^(Row Status|行状态|Status|状态)$/i.test(cell));
|
|
115
|
+
const decisionIndex = header.findIndex((cell) => /^(Review Decision|审查决定)$/i.test(cell));
|
|
116
|
+
for (let index = headerIndex + 2; index < lines.length && lines[index].trim().startsWith("|"); index += 1) {
|
|
117
|
+
const cells = splitSimpleRow(lines[index]);
|
|
118
|
+
if ((cells[0] || "").toLowerCase() !== candidateId.toLowerCase())
|
|
119
|
+
continue;
|
|
120
|
+
if (statusIndex >= 0)
|
|
121
|
+
cells[statusIndex] = "promoted";
|
|
122
|
+
if (decisionIndex >= 0 && !cells[decisionIndex].includes(lessonId))
|
|
123
|
+
cells[decisionIndex] = `${cells[decisionIndex]} promoted:${lessonId}`.trim();
|
|
124
|
+
lines[index] = `| ${cells.map(escapeCell).join(" | ")} |`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return `${lines.join("\n")
|
|
128
|
+
.replace("| Task-level status | needs-promotion |", "| Task-level status | promoted |")
|
|
129
|
+
.replace("| Promotion state | not-promoted |", "| Promotion state | promoted |")
|
|
130
|
+
.replace("| Closeout token | pending |", `| Closeout token | checked-created:${lessonId} |`)
|
|
131
|
+
.replace(/\| Closeout token \| queued-promotion:[^|]+ \|/, `| Closeout token | checked-created:${lessonId} |`)
|
|
132
|
+
.trimEnd()}\n`;
|
|
133
|
+
}
|
|
134
|
+
function splitSimpleRow(line) {
|
|
135
|
+
return String(line || "").replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((cell) => cell.trim());
|
|
136
|
+
}
|
|
137
|
+
function escapeCell(value) {
|
|
138
|
+
return String(value || "").replace(/\r?\n/g, " ").replaceAll("|", "\\|").trim();
|
|
139
|
+
}
|