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.
Files changed (279) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +96 -4
  6. package/README.zh-CN.md +75 -4
  7. package/SKILL.md +52 -51
  8. package/dist/build-dist.mjs +189 -0
  9. package/dist/check-dist-observation.mjs +428 -0
  10. package/dist/check-harness.mjs +489 -0
  11. package/dist/check-import-graph.mjs +511 -0
  12. package/dist/check-runtime-emit.mjs +304 -0
  13. package/dist/check-type-boundaries.mjs +139 -0
  14. package/dist/commands/dashboard-command.mjs +80 -0
  15. package/dist/commands/migration-command.mjs +152 -0
  16. package/dist/commands/preset-command.mjs +91 -0
  17. package/dist/commands/task-command.mjs +324 -0
  18. package/dist/harness.mjs +304 -0
  19. package/dist/lib/capability-registry.mjs +643 -0
  20. package/dist/lib/check-module-parallel.mjs +227 -0
  21. package/dist/lib/check-profiles.mjs +414 -0
  22. package/dist/lib/check-task-contracts.mjs +54 -0
  23. package/dist/lib/core-shared.mjs +254 -0
  24. package/dist/lib/dashboard-data.mjs +608 -0
  25. package/dist/lib/dashboard-workbench.mjs +334 -0
  26. package/dist/lib/dashboard-writer.mjs +200 -0
  27. package/dist/lib/git-status-summary.mjs +45 -0
  28. package/dist/lib/governance-index-generator.mjs +236 -0
  29. package/dist/lib/governance-sync.mjs +617 -0
  30. package/dist/lib/governance-table-boundary.mjs +161 -0
  31. package/{scripts → dist}/lib/harness-core.mjs +3 -0
  32. package/dist/lib/harness-paths.mjs +338 -0
  33. package/dist/lib/lesson-maintenance.mjs +139 -0
  34. package/dist/lib/markdown-utils.mjs +193 -0
  35. package/dist/lib/migration-planner.mjs +439 -0
  36. package/dist/lib/migration-support.mjs +317 -0
  37. package/dist/lib/phase-kind.mjs +46 -0
  38. package/dist/lib/preset-audit-contracts.mjs +40 -0
  39. package/dist/lib/preset-engine.mjs +516 -0
  40. package/dist/lib/preset-registry.mjs +831 -0
  41. package/dist/lib/preset-resource-contracts.mjs +83 -0
  42. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  43. package/dist/lib/status-builder.mjs +87 -0
  44. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
  45. package/dist/lib/structure-migration.mjs +404 -0
  46. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  47. package/dist/lib/task-audit-metadata.mjs +376 -0
  48. package/dist/lib/task-audit-migration.mjs +355 -0
  49. package/dist/lib/task-completion-consistency.mjs +29 -0
  50. package/dist/lib/task-index.mjs +133 -0
  51. package/dist/lib/task-lesson-candidates.mjs +239 -0
  52. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  53. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  54. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  55. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  56. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  57. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  58. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  59. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  60. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  61. package/dist/lib/task-lifecycle.mjs +611 -0
  62. package/dist/lib/task-metadata.mjs +116 -0
  63. package/dist/lib/task-review-model.mjs +474 -0
  64. package/dist/lib/task-scanner.mjs +439 -0
  65. package/dist/lib/task-tombstone-commands.mjs +125 -0
  66. package/dist/postinstall.mjs +14 -0
  67. package/dist/run-built-tests.mjs +84 -0
  68. package/docs-release/README.md +1 -0
  69. package/docs-release/architecture/overview.md +13 -13
  70. package/docs-release/architecture/overview.zh-CN.md +13 -13
  71. package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
  72. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  73. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  74. package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
  75. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  76. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
  77. package/docs-release/architecture/system-explainer/README.md +67 -0
  78. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
  79. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  80. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  81. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
  82. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  83. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
  84. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  85. package/docs-release/guides/agent-installation.en-US.md +22 -15
  86. package/docs-release/guides/agent-installation.md +23 -15
  87. package/docs-release/guides/contributing.md +3 -3
  88. package/docs-release/guides/contributing.zh-CN.md +3 -3
  89. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  90. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  91. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  92. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  93. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  94. package/docs-release/guides/migration-playbook.md +59 -1
  95. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  96. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  97. package/docs-release/guides/preset-development.md +28 -4
  98. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  99. package/docs-release/guides/repository-operating-models.md +21 -21
  100. package/docs-release/guides/task-state-machine.en-US.md +35 -18
  101. package/docs-release/guides/task-state-machine.md +35 -18
  102. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  103. package/examples/minimal-project/AGENTS.md +2 -2
  104. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  105. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
  106. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  107. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  108. package/package.json +22 -13
  109. package/presets/legacy-migration/preset.yaml +5 -5
  110. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  111. package/presets/lesson-sedimentation/preset.yaml +3 -3
  112. package/presets/module/preset.yaml +2 -2
  113. package/presets/module/templates/execution_strategy.append.md +1 -1
  114. package/presets/module/templates/task_plan.append.md +3 -3
  115. package/presets/standard-task/preset.yaml +2 -2
  116. package/references/adversarial-review-standard.md +2 -2
  117. package/references/agents-md-pattern.md +14 -14
  118. package/references/cadence-ledger.md +1 -1
  119. package/references/ci-cd-standard.md +1 -1
  120. package/references/delivery-operating-model-standard.md +4 -4
  121. package/references/docs-directory-standard.md +65 -159
  122. package/references/external-source-intake-standard.md +10 -10
  123. package/references/harness-ledger.md +6 -6
  124. package/references/legacy-12-phase-bootstrap.md +2 -2
  125. package/references/lessons-governance.md +15 -15
  126. package/references/long-running-task-standard.md +6 -6
  127. package/references/module-parallel-standard.md +34 -34
  128. package/references/planning-loop.md +6 -6
  129. package/references/project-onboarding-audit.md +4 -4
  130. package/references/regression-system.md +2 -2
  131. package/references/repo-governance-standard.md +4 -4
  132. package/references/review-routing-standard.md +1 -1
  133. package/references/ssot-governance.md +19 -19
  134. package/references/taskr-gap-analysis.md +5 -5
  135. package/references/walkthrough-closeout.md +14 -14
  136. package/references/worktree-parallel.md +3 -3
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  139. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  140. package/templates/AGENTS.md.template +31 -29
  141. package/templates/architecture/README.md +4 -4
  142. package/templates/architecture/service-catalog.md +2 -2
  143. package/templates/architecture/services/service-template.md +1 -1
  144. package/templates/dashboard/assets/app-src/00-state.js +12 -0
  145. package/templates/dashboard/assets/app-src/10-router.js +3 -0
  146. package/templates/dashboard/assets/app-src/20-overview.js +13 -3
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
  148. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  149. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  150. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  151. package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
  152. package/templates/dashboard/assets/app.css +583 -0
  153. package/templates/dashboard/assets/app.css.manifest.json +1 -0
  154. package/templates/dashboard/assets/app.js +585 -11
  155. package/templates/dashboard/assets/app.manifest.json +1 -0
  156. package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
  158. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  159. package/templates/dashboard/assets/i18n.js +144 -4
  160. package/templates/development/README.md +10 -10
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +2 -2
  163. package/templates/development/external-source-packs/README.md +4 -4
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +2 -2
  166. package/templates/integrations/event-contract.md +2 -2
  167. package/templates/integrations/third-party/vendor-template.md +2 -2
  168. package/templates/integrations/webhook-contract.md +2 -2
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/planning/INDEX.md +88 -0
  171. package/templates/planning/brief.md +1 -1
  172. package/templates/planning/module_session_prompt.md +2 -1
  173. package/templates/planning/review.md +0 -18
  174. package/templates/planning/task_plan.md +5 -44
  175. package/templates/planning/visual_map.md +13 -9
  176. package/templates/planning/visual_map.simple.md +52 -0
  177. package/templates/planning/walkthrough.md +47 -0
  178. package/templates/reference/docs-library-standard.md +8 -8
  179. package/templates/reference/execution-workflow-standard.md +29 -2
  180. package/templates/reference/external-source-intake-standard.md +15 -15
  181. package/templates/reference/repo-governance-standard.md +1 -1
  182. package/templates/ssot/Module-Registry.md +1 -1
  183. package/templates/walkthrough/walkthrough-template.md +2 -2
  184. package/templates-zh-CN/AGENTS.md.template +31 -29
  185. package/templates-zh-CN/CLAUDE.md.template +1 -1
  186. package/templates-zh-CN/architecture/README.md +4 -4
  187. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  188. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  189. package/templates-zh-CN/development/README.md +10 -10
  190. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  191. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  192. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  193. package/templates-zh-CN/integrations/README.md +4 -4
  194. package/templates-zh-CN/integrations/api-contract.md +2 -2
  195. package/templates-zh-CN/integrations/event-contract.md +2 -2
  196. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  197. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  198. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  199. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  200. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  201. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  202. package/templates-zh-CN/planning/INDEX.md +87 -0
  203. package/templates-zh-CN/planning/brief.md +1 -1
  204. package/templates-zh-CN/planning/module_session_prompt.md +12 -11
  205. package/templates-zh-CN/planning/review.md +0 -18
  206. package/templates-zh-CN/planning/task_plan.md +3 -63
  207. package/templates-zh-CN/planning/visual_map.md +14 -7
  208. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  209. package/templates-zh-CN/planning/walkthrough.md +47 -0
  210. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  211. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  212. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  213. package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
  214. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  215. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  216. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  217. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  218. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  219. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  220. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  221. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  222. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  223. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  224. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  225. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  226. package/tsconfig.dist.json +16 -0
  227. package/tsconfig.json +25 -0
  228. package/tsconfig.runtime.json +24 -0
  229. package/examples/minimal-project/.harness-capabilities.json +0 -8
  230. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  231. package/scripts/check-harness.mjs +0 -508
  232. package/scripts/commands/dashboard-command.mjs +0 -67
  233. package/scripts/commands/migration-command.mjs +0 -96
  234. package/scripts/commands/preset-command.mjs +0 -73
  235. package/scripts/commands/task-command.mjs +0 -327
  236. package/scripts/harness.mjs +0 -287
  237. package/scripts/lib/capability-registry.mjs +0 -591
  238. package/scripts/lib/check-module-parallel.mjs +0 -237
  239. package/scripts/lib/check-profiles.mjs +0 -418
  240. package/scripts/lib/check-task-contracts.mjs +0 -47
  241. package/scripts/lib/core-shared.mjs +0 -196
  242. package/scripts/lib/dashboard-data.mjs +0 -412
  243. package/scripts/lib/dashboard-workbench.mjs +0 -257
  244. package/scripts/lib/dashboard-writer.mjs +0 -198
  245. package/scripts/lib/git-status-summary.mjs +0 -46
  246. package/scripts/lib/governance-index-generator.mjs +0 -174
  247. package/scripts/lib/governance-sync.mjs +0 -514
  248. package/scripts/lib/governance-table-boundary.mjs +0 -175
  249. package/scripts/lib/lesson-maintenance.mjs +0 -152
  250. package/scripts/lib/markdown-utils.mjs +0 -158
  251. package/scripts/lib/migration-planner.mjs +0 -478
  252. package/scripts/lib/migration-support.mjs +0 -312
  253. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  254. package/scripts/lib/preset-engine.mjs +0 -497
  255. package/scripts/lib/preset-registry.mjs +0 -627
  256. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  257. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  258. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  259. package/scripts/lib/task-completion-consistency.mjs +0 -16
  260. package/scripts/lib/task-index.mjs +0 -93
  261. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  262. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  263. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
  264. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
  265. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  266. package/scripts/lib/task-lifecycle.mjs +0 -649
  267. package/scripts/lib/task-review-model.mjs +0 -469
  268. package/scripts/lib/task-scanner.mjs +0 -576
  269. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  270. package/scripts/postinstall.mjs +0 -14
  271. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  272. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  273. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  274. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  275. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  276. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  277. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  278. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  279. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -1,514 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- import { readBundledTemplate, readFileSafe, repoRoot, todayDate, toPosix, visualMapFile } from "./core-shared.mjs";
5
- import { collectTasks } from "./task-scanner.mjs";
6
- import { firstColumn, splitMarkdownRow, updateMarkdownTableRow } from "./markdown-utils.mjs";
7
- import { markdownCell } from "./task-lifecycle/text-utils.mjs";
8
-
9
- export class GovernanceSyncError extends Error {
10
- constructor(message, { code = "governance-sync-failed", details = {}, recovery = [] } = {}) {
11
- super(message);
12
- this.name = "GovernanceSyncError";
13
- this.code = code;
14
- this.details = details;
15
- this.recovery = recovery;
16
- }
17
- }
18
-
19
- export function beginGovernanceSync(target, { operation = "governance-sync", dryRun = false } = {}) {
20
- if (dryRun) return { target, dryRun, operation, git: inspectGit(target.projectRoot), lockPath: "", active: false };
21
- const lockPath = path.join(target.projectRoot, ".harness/locks/governance-sync.lock");
22
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
23
- let fd = null;
24
- try {
25
- fd = fs.openSync(lockPath, "wx");
26
- fs.writeFileSync(
27
- fd,
28
- `${JSON.stringify({
29
- operation,
30
- pid: process.pid,
31
- host: process.env.HOSTNAME || "",
32
- branch: currentBranch(target.projectRoot),
33
- targetRoot: target.projectRoot,
34
- startedAt: new Date().toISOString(),
35
- }, null, 2)}\n`,
36
- );
37
- } catch (error) {
38
- if (fd !== null) fs.closeSync(fd);
39
- throw new GovernanceSyncError("Governance sync lock already exists; refusing concurrent registry writes.", {
40
- code: "governance-lock-exists",
41
- details: { lockPath, error: error.message },
42
- recovery: [
43
- `Inspect ${lockPath}.`,
44
- "If no process owns the lock, remove it manually and retry.",
45
- ],
46
- });
47
- }
48
- if (fd !== null) fs.closeSync(fd);
49
-
50
- const gitState = inspectGit(target.projectRoot);
51
- if (gitState.inGit) {
52
- if (real(gitState.gitRoot) !== real(target.projectRoot)) {
53
- releaseGovernanceSync({ lockPath, active: true });
54
- throw new GovernanceSyncError("Governance sync requires the target argument to be the Git repository root.", {
55
- code: "governance-git-root-mismatch",
56
- details: { targetRoot: target.projectRoot, gitRoot: gitState.gitRoot },
57
- recovery: ["Run the harness command against the target repository root."],
58
- });
59
- }
60
- if (gitState.entries.length > 0) {
61
- releaseGovernanceSync({ lockPath, active: true });
62
- throw new GovernanceSyncError("Governance sync requires a clean Git working tree before CLI-owned writes.", {
63
- code: "governance-git-dirty",
64
- details: { entries: gitState.entries },
65
- recovery: ["Commit or otherwise resolve unrelated changes before running this lifecycle command."],
66
- });
67
- }
68
- assertCommitIdentity(target.projectRoot);
69
- }
70
- return { target, dryRun, operation, git: gitState, lockPath, active: true };
71
- }
72
-
73
- export function releaseGovernanceSync(context) {
74
- if (!context?.active || !context.lockPath) return;
75
- try {
76
- fs.unlinkSync(context.lockPath);
77
- } catch {
78
- // Best-effort cleanup; command errors report the original failure.
79
- }
80
- }
81
-
82
- export function commitGovernanceSync(context, allowedRelativePaths, { message = "chore(harness): sync governance state" } = {}) {
83
- const allowed = [...new Set((allowedRelativePaths || []).filter(Boolean).map(toPosix))].sort();
84
- if (context?.dryRun || !context?.git?.inGit) return { committed: false, reason: context?.git?.inGit ? "dry-run" : "not-git", allowedPaths: allowed };
85
- assertOnlyAllowedChanged(context.target.projectRoot, allowed);
86
- if (allowed.length === 0) return { committed: false, reason: "no-allowed-paths", allowedPaths: allowed };
87
- git(context.target.projectRoot, ["add", "--", ...allowed]);
88
- assertOnlyAllowedStaged(context.target.projectRoot, allowed);
89
- const staged = git(context.target.projectRoot, ["diff", "--cached", "--name-only", "-z"]).stdout.split("\0").filter(Boolean);
90
- if (staged.length === 0) return { committed: false, reason: "no-changes", allowedPaths: allowed };
91
- const commitResult = git(context.target.projectRoot, ["commit", "-m", message], { allowFailure: true });
92
- if (commitResult.status !== 0) {
93
- throw new GovernanceSyncError("Governance sync wrote files but Git commit failed.", {
94
- code: "governance-git-commit-failed",
95
- details: { stdout: commitResult.stdout.trim(), stderr: commitResult.stderr.trim(), allowedPaths: allowed },
96
- recovery: [
97
- `Inspect files: ${allowed.join(", ")}`,
98
- `Then run: git add -- ${allowed.join(" ")} && git commit -m ${JSON.stringify(message)}`,
99
- ],
100
- });
101
- }
102
- assertClean(context.target.projectRoot);
103
- return { committed: true, commitSha: git(context.target.projectRoot, ["rev-parse", "HEAD"]).stdout.trim(), allowedPaths: allowed };
104
- }
105
-
106
- export function syncTaskGovernance(target, task, { event = "new-task", state = "planned", message = "", dryRun = false } = {}) {
107
- const changes = [];
108
- const planPath = stripTargetPrefix(task.path) + "/task_plan.md";
109
- const reviewPath = stripTargetPrefix(task.path) + "/review.md";
110
- const ledger = syncLedgerRow(target, task, { event, state, message, planPath, reviewPath, dryRun });
111
- if (ledger) changes.push(ledger);
112
- if (task.module) {
113
- const moduleRegistry = syncModuleRegistryRow(target, task, { state, planPath, dryRun });
114
- if (moduleRegistry) changes.push(moduleRegistry);
115
- changes.push(...syncModuleGeneratedIndexes(target, task.module, { task, dryRun }).changes);
116
- }
117
- return { changes };
118
- }
119
-
120
- export function syncModuleStepGovernance(target, { moduleKey, stepId, state, dryRun = false } = {}) {
121
- const changes = [];
122
- const ledgerPath = path.join(target.docsRoot, "Harness-Ledger.md");
123
- const ledgerRelative = toPosix(path.relative(target.projectRoot, ledgerPath));
124
- ensureFileFromTemplate(ledgerPath, "templates/ledger/Harness-Ledger.md", { dryRun });
125
- if (!dryRun) {
126
- const content = readFileSafe(ledgerPath);
127
- const row = [
128
- `HL-${todayDate().replaceAll("-", "")}-${Date.now().toString().slice(-6)}`,
129
- "module",
130
- moduleKey,
131
- `Module ${moduleKey} step ${stepId}`,
132
- state === "done" ? "review" : state === "in-progress" ? "active" : state,
133
- "none",
134
- `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`,
135
- "n/a",
136
- "checked-none:module-step",
137
- "pending",
138
- "module-step",
139
- todayDate(),
140
- ];
141
- fs.writeFileSync(ledgerPath, appendRow(content, /^ID$/i, row));
142
- }
143
- changes.push({ destination: ledgerRelative, action: dryRun ? "would-sync-governance" : "sync-governance", surface: "harness-ledger" });
144
- return { changes };
145
- }
146
-
147
- export function governanceRelativePaths(changes) {
148
- return [...new Set((changes || []).map((change) => change.destination).filter(Boolean).map(toPosix))];
149
- }
150
-
151
- function syncLedgerRow(target, task, { event, state, message, planPath, reviewPath, dryRun }) {
152
- const ledgerPath = path.join(target.docsRoot, "Harness-Ledger.md");
153
- ensureFileFromTemplate(ledgerPath, "templates/ledger/Harness-Ledger.md", { dryRun });
154
- const relative = toPosix(path.relative(target.projectRoot, ledgerPath));
155
- if (!dryRun) {
156
- const content = readFileSafe(ledgerPath);
157
- const row = [
158
- ledgerId(task),
159
- task.module ? "module" : "task",
160
- task.module || "none",
161
- task.title || task.shortId || task.id,
162
- mapLedgerState(state),
163
- "none",
164
- planPath,
165
- event === "task-review" || state === "review" ? reviewPath : "pending",
166
- "pending",
167
- "pending",
168
- message || "none",
169
- todayDate(),
170
- ];
171
- fs.writeFileSync(ledgerPath, upsertRow(content, /^ID$/i, (header, existing) => rowMatchesPlan(header, existing, planPath), row));
172
- }
173
- return { destination: relative, action: dryRun ? "would-sync-governance" : "sync-governance", surface: "harness-ledger" };
174
- }
175
-
176
- function syncModuleRegistryRow(target, task, { state, planPath, dryRun }) {
177
- const registryPath = path.join(target.docsRoot, "09-PLANNING/Module-Registry.md");
178
- ensureFileFromTemplate(registryPath, "templates/ssot/Module-Registry.md", { dryRun });
179
- const relative = toPosix(path.relative(target.projectRoot, registryPath));
180
- if (!dryRun) {
181
- const content = readFileSafe(registryPath);
182
- const moduleKey = task.module;
183
- const modulePlan = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
184
- const row = [
185
- `M-${moduleKey.toUpperCase().replace(/[^A-Z0-9]+/g, "-")}`,
186
- moduleKey,
187
- `docs/09-PLANNING/MODULES/${moduleKey}/**`,
188
- "coordinator",
189
- state === "planned" ? "reserved" : mapModuleState(state),
190
- `codex/${moduleKey}`,
191
- modulePlan,
192
- "none",
193
- "none",
194
- planPath,
195
- "none",
196
- todayDate(),
197
- ];
198
- fs.writeFileSync(registryPath, upsertRow(content, /^ID$/i, (header, existing) => rowMatchesModule(header, existing, moduleKey, modulePlan), row));
199
- }
200
- return { destination: relative, action: dryRun ? "would-sync-governance" : "sync-governance", surface: "module-registry" };
201
- }
202
-
203
- function syncModuleGeneratedIndexes(target, moduleKey, { task = null, dryRun = false } = {}) {
204
- const moduleTasks = collectModuleTasks(target, moduleKey, task);
205
- const surfaces = moduleGeneratedIndexSurfaces(target, moduleTasks);
206
- if (!dryRun) {
207
- for (const surface of surfaces) {
208
- fs.mkdirSync(path.dirname(surface.absolute), { recursive: true });
209
- fs.writeFileSync(surface.absolute, surface.content);
210
- }
211
- }
212
- return {
213
- changes: surfaces.map((surface) => ({
214
- destination: surface.relative,
215
- action: dryRun ? "would-sync-governance" : "sync-governance",
216
- surface: surface.surface,
217
- })),
218
- };
219
- }
220
-
221
- export function moduleGeneratedIndexSurfaces(target, tasks = collectTasks(target)) {
222
- const modules = [...new Set((tasks || []).map((task) => task.module).filter(Boolean))].sort();
223
- const surfaces = [];
224
- for (const moduleKey of modules) {
225
- const moduleTasks = (tasks || [])
226
- .filter((task) => task.module === moduleKey)
227
- .sort((a, b) => String(stripDatePrefix(a.shortId || a.id)).localeCompare(String(stripDatePrefix(b.shortId || b.id))));
228
- const moduleDir = path.join(target.docsRoot, "09-PLANNING/MODULES", moduleKey);
229
- const modulePlanPath = path.join(moduleDir, "module_plan.md");
230
- const moduleVisualPath = path.join(moduleDir, visualMapFile);
231
- const stepRows = moduleTasks.map((task, index) => {
232
- const stepId = moduleStepId(task);
233
- const previous = index === 0 ? "none" : moduleStepId(moduleTasks[index - 1]);
234
- return [stepId, task.title || task.shortId || task.id, mapModuleState(task.state), stripTargetPrefix(task.taskPlanPath || `${stripTargetPrefix(task.path)}/task_plan.md`), previous];
235
- });
236
- surfaces.push({
237
- surface: "module-plan-index",
238
- absolute: modulePlanPath,
239
- relative: toPosix(path.relative(target.projectRoot, modulePlanPath)),
240
- rows: stepRows,
241
- content: replaceTableRows(existingOrTemplate(modulePlanPath, "templates/planning/module_plan.md"), /^Step ID$/i, stepRows),
242
- });
243
- surfaces.push({
244
- surface: "module-visual-index",
245
- absolute: moduleVisualPath,
246
- relative: toPosix(path.relative(target.projectRoot, moduleVisualPath)),
247
- rows: stepRows,
248
- content: renderModuleVisualMap(moduleKey, moduleTasks),
249
- });
250
- }
251
- return surfaces;
252
- }
253
-
254
- function collectModuleTasks(target, moduleKey, task) {
255
- const tasks = collectTasks(target).filter((candidate) => candidate.module === moduleKey);
256
- if (task && !tasks.some((candidate) => stripTargetPrefix(candidate.taskPlanPath) === `${stripTargetPrefix(task.path)}/task_plan.md`)) {
257
- tasks.push({
258
- ...task,
259
- module: moduleKey,
260
- state: task.state || "planned",
261
- taskPlanPath: `${stripTargetPrefix(task.path)}/task_plan.md`,
262
- completion: 0,
263
- });
264
- }
265
- return tasks;
266
- }
267
-
268
- function renderModuleVisualMap(moduleKey, tasks) {
269
- const rows = tasks.map((task, index) => {
270
- const stepId = moduleStepId(task);
271
- const previous = index === 0 ? "none" : moduleStepId(tasks[index - 1]);
272
- const state = mapPhaseState(task.state);
273
- const completion = Number.isInteger(task.completion) ? task.completion : state === "done" ? 100 : 0;
274
- return [
275
- stepId,
276
- previous,
277
- state,
278
- completion,
279
- task.title || task.shortId || task.id,
280
- stripTargetPrefix(task.taskPlanPath || `${stripTargetPrefix(task.path)}/task_plan.md`),
281
- task.materialsReady ? "present" : "missing",
282
- previous === "none" ? "none" : `depends on ${previous}`,
283
- "coordinator",
284
- ];
285
- });
286
- const graphLines = tasks.map((task, index) => {
287
- const stepId = moduleStepId(task);
288
- const label = markdownCell(task.title || task.shortId || task.id).replace(/"/g, "'");
289
- if (index === 0) return ` ${stepId}["${label}"]`;
290
- const previous = moduleStepId(tasks[index - 1]);
291
- return ` ${previous} --> ${stepId}["${label}"]`;
292
- });
293
- return `# ${moduleKey} - Visual Map
294
-
295
- Visual Map Contract: v1.0
296
-
297
- Generated by \`harness new-task --module\` and \`harness governance rebuild\`.
298
-
299
- ## Map Index
300
-
301
- | ID | Type | Purpose | Required For Understanding | Source Evidence | Promotion Candidate |
302
- | --- | --- | --- | --- | --- | --- |
303
- | MAP-01 | topology | Show module task sequence generated from task files | yes | task scan | no |
304
-
305
- ## Phase Graph
306
-
307
- \`\`\`mermaid
308
- flowchart LR
309
- ${graphLines.length ? graphLines.join("\n") : " EMPTY[\"No module tasks\"]"}
310
- \`\`\`
311
-
312
- ## Phase Table
313
-
314
- | Phase ID | Depends On | State | Completion | Output | Required Evidence | Evidence Status | Blocking Risk | Owner / Handoff |
315
- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |
316
- ${rows.map((row) => `| ${fitRow(row, 9).join(" | ")} |`).join("\n")}
317
-
318
- Allowed Evidence Status: missing, partial, present, waived.
319
- `;
320
- }
321
-
322
- function replaceTableRows(content, headerPattern, rows) {
323
- const lines = String(content || "").split(/\r?\n/);
324
- for (let index = 0; index < lines.length - 1; index += 1) {
325
- if (!lines[index].trim().startsWith("|")) continue;
326
- const header = splitMarkdownRow(lines[index]);
327
- if (!header.some((cell) => headerPattern.test(cell))) continue;
328
- const separator = splitMarkdownRow(lines[index + 1]);
329
- if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
330
- let end = index + 2;
331
- while (end < lines.length && lines[end].trim().startsWith("|")) end += 1;
332
- lines.splice(index + 2, end - index - 2, ...rows.map((row) => `| ${fitRow(row, header.length).join(" | ")} |`));
333
- return `${lines.join("\n").trimEnd()}\n`;
334
- }
335
- return `${String(content || "").trimEnd()}\n\n${rows.map((row) => `| ${fitRow(row, row.length).join(" | ")} |`).join("\n")}\n`;
336
- }
337
-
338
- function existingOrTemplate(filePath, templateSource) {
339
- return fs.existsSync(filePath) ? readFileSafe(filePath) : readBundledTemplate(templateSource);
340
- }
341
-
342
- function moduleStepId(task) {
343
- return `T-${stripDatePrefix(task.shortId || task.id || "task").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "").toUpperCase().slice(0, 48)}`;
344
- }
345
-
346
- function stripDatePrefix(value) {
347
- return String(value || "").replace(/^(?:TASKS\/|MODULES\/[^/]+\/)?\d{4}-\d{2}-\d{2}-/, "");
348
- }
349
-
350
- function mapPhaseState(state) {
351
- if (state === "in_progress") return "in_progress";
352
- if (state === "review") return "review";
353
- if (state === "done") return "done";
354
- if (state === "blocked") return "blocked";
355
- return "planned";
356
- }
357
-
358
- function ensureFileFromTemplate(destinationPath, templateSource, { dryRun = false } = {}) {
359
- if (fs.existsSync(destinationPath) || dryRun) return;
360
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
361
- fs.writeFileSync(destinationPath, readBundledTemplate(templateSource));
362
- }
363
-
364
- function upsertRow(content, headerPattern, matcher, row) {
365
- const updated = updateMarkdownTableRow(content, headerPattern, (header, existing) => (matcher(header, existing) ? fitRow(row, header.length) : null));
366
- if (updated.matched) return updated.content;
367
- return appendRow(content, headerPattern, row);
368
- }
369
-
370
- function appendRow(content, headerPattern, row) {
371
- const lines = String(content || "").split(/\r?\n/);
372
- for (let index = 0; index < lines.length; index += 1) {
373
- if (!lines[index].trim().startsWith("|")) continue;
374
- const header = splitMarkdownRow(lines[index]);
375
- if (!header.some((cell) => headerPattern.test(cell))) continue;
376
- let insertAt = index + 2;
377
- while (insertAt < lines.length && lines[insertAt].trim().startsWith("|")) insertAt += 1;
378
- lines.splice(insertAt, 0, `| ${fitRow(row, header.length).join(" | ")} |`);
379
- return lines.join("\n");
380
- }
381
- return `${String(content || "").trimEnd()}\n\n| ${row.join(" | ")} |\n`;
382
- }
383
-
384
- function fitRow(row, length) {
385
- const next = row.map((cell) => markdownCell(cell));
386
- while (next.length < length) next.push("");
387
- return next.slice(0, length);
388
- }
389
-
390
- function rowMatchesPlan(header, row, planPath) {
391
- const planIndex = firstColumn(header, ["Task Plan", "Plan", "当前产物"]);
392
- return planIndex >= 0 && String(row[planIndex] || "").includes(planPath);
393
- }
394
-
395
- function rowMatchesModule(header, row, moduleKey, modulePlan) {
396
- const moduleIndex = firstColumn(header, ["Module", "模块", "模块 Key"]);
397
- const taskPlanIndex = firstColumn(header, ["Task Plan", "当前产物"]);
398
- return String(row[moduleIndex] || "").toLowerCase() === String(moduleKey).toLowerCase() || String(row[taskPlanIndex] || "").includes(modulePlan);
399
- }
400
-
401
- function ledgerId(task) {
402
- return `HL-${String(task.shortId || task.id || "task").replace(/^TASKS\//, "").replace(/^MODULES\//, "").replace(/[^A-Za-z0-9-]+/g, "-").slice(0, 72)}`;
403
- }
404
-
405
- function stripTargetPrefix(value) {
406
- return String(value || "").replace(/^TARGET:/, "").replace(/\/$/, "");
407
- }
408
-
409
- function mapLedgerState(state) {
410
- if (state === "in_progress") return "active";
411
- if (state === "review") return "review";
412
- if (state === "done") return "closed";
413
- if (state === "blocked") return "blocked";
414
- return "planned";
415
- }
416
-
417
- function mapModuleState(state) {
418
- if (state === "in_progress") return "active";
419
- if (state === "review") return "handoff";
420
- if (state === "done") return "merged";
421
- if (state === "blocked") return "blocked";
422
- return "reserved";
423
- }
424
-
425
- export function inspectGit(root) {
426
- const gitRootResult = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
427
- if (gitRootResult.status !== 0) return { inGit: false, gitRoot: "", entries: [] };
428
- const gitRoot = path.resolve(gitRootResult.stdout.trim());
429
- return { inGit: true, gitRoot, entries: statusEntries(root) };
430
- }
431
-
432
- function currentBranch(root) {
433
- const result = git(root, ["branch", "--show-current"], { allowFailure: true });
434
- return result.status === 0 ? result.stdout.trim() : "";
435
- }
436
-
437
- function assertCommitIdentity(root) {
438
- const name = git(root, ["config", "--get", "user.name"], { allowFailure: true }).stdout.trim();
439
- const email = git(root, ["config", "--get", "user.email"], { allowFailure: true }).stdout.trim();
440
- if (!name || !email) {
441
- throw new GovernanceSyncError("Governance sync auto-commit requires Git user.name and user.email.", {
442
- code: "governance-git-identity-missing",
443
- details: { hasName: Boolean(name), hasEmail: Boolean(email) },
444
- recovery: ["Configure a local Git identity for the target repository."],
445
- });
446
- }
447
- }
448
-
449
- function assertOnlyAllowedChanged(root, allowedPaths) {
450
- const outside = statusEntries(root).filter((entry) => !allowedPaths.includes(entry.path));
451
- if (outside.length > 0) {
452
- throw new GovernanceSyncError("Governance sync produced changes outside the allowlist.", {
453
- code: "governance-allowlist-violation",
454
- details: { disallowed: outside, allowedPaths },
455
- recovery: ["Inspect the extra paths; the CLI will not stage or commit unrelated files."],
456
- });
457
- }
458
- }
459
-
460
- function assertOnlyAllowedStaged(root, allowedPaths) {
461
- const outside = statusEntries(root).filter((entry) => entry.index !== " " && !allowedPaths.includes(entry.path));
462
- if (outside.length > 0) {
463
- throw new GovernanceSyncError("Git index contains staged files outside the governance sync allowlist.", {
464
- code: "governance-index-allowlist-violation",
465
- details: { disallowed: outside, allowedPaths },
466
- recovery: ["Unstage unrelated files before retrying the lifecycle command."],
467
- });
468
- }
469
- }
470
-
471
- function assertClean(root) {
472
- const entries = statusEntries(root);
473
- if (entries.length > 0) {
474
- throw new GovernanceSyncError("Governance sync commit completed but working tree is not clean.", {
475
- code: "governance-post-commit-dirty",
476
- details: { entries },
477
- recovery: ["Inspect remaining files before continuing."],
478
- });
479
- }
480
- }
481
-
482
- function statusEntries(root) {
483
- return git(root, ["status", "--porcelain=v1", "--untracked-files=all"]).stdout
484
- .split(/\r?\n/)
485
- .filter(Boolean)
486
- .map((line) => ({
487
- index: line.slice(0, 1),
488
- worktree: line.slice(1, 2),
489
- path: toPosix(parseStatusPath(line.slice(3))),
490
- raw: line,
491
- }))
492
- .filter((entry) => entry.path !== ".harness/locks/governance-sync.lock");
493
- }
494
-
495
- function parseStatusPath(value) {
496
- const unquoted = value.replace(/^"|"$/g, "");
497
- return unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() : unquoted;
498
- }
499
-
500
- function git(cwd, args, { allowFailure = false } = {}) {
501
- const result = spawnSync("git", args, { cwd, encoding: "utf8" });
502
- if (!allowFailure && result.status !== 0) {
503
- throw new GovernanceSyncError(`git ${args.join(" ")} failed`, {
504
- code: "governance-git-command-failed",
505
- details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
506
- recovery: ["Inspect the Git error and retry after resolving it."],
507
- });
508
- }
509
- return result;
510
- }
511
-
512
- function real(filePath) {
513
- return fs.realpathSync(filePath);
514
- }
@@ -1,175 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { readFileSafe, toPosix } from "./core-shared.mjs";
4
- import { getCell, parseAllMarkdownTables } from "./markdown-utils.mjs";
5
-
6
- const newRuleCutoff = "2026-05-24";
7
-
8
- const globalTableSpecs = [
9
- { key: "feature-ssot", relativePath: "09-PLANNING/Feature-SSoT.md", allowed: "index-state-route-summary", evaluate: evaluateFeatureRow },
10
- { key: "harness-ledger", relativePath: "Harness-Ledger.md", allowed: "task-audit-summary-route", evaluate: evaluateLedgerRow },
11
- { key: "closeout-ssot", relativePath: "10-WALKTHROUGH/Closeout-SSoT.md", allowed: "closeout-index-review-route-summary", evaluate: evaluateCloseoutRow },
12
- { key: "regression-ssot", relativePath: "05-TEST-QA/Regression-SSoT.md", allowed: "gate-index-current-state", evaluate: evaluateRegressionRow },
13
- { key: "cadence-ledger", relativePath: "05-TEST-QA/Cadence-Ledger.md", allowed: "trigger-rule-and-batch-summary", evaluate: evaluateCadenceRow },
14
- ];
15
-
16
- export function validateGovernanceTableBoundaries(target) {
17
- const failures = [];
18
- const warnings = [];
19
- for (const spec of globalTableSpecs) {
20
- const file = path.join(target.docsRoot, spec.relativePath);
21
- if (!fs.existsSync(file)) 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)) continue;
26
- for (const finding of spec.evaluate(row)) {
27
- const rowKey = governanceRowKey(row);
28
- const message = [
29
- "governance-table-entropy",
30
- `${relative}:${table.line}`,
31
- `${spec.key} row ${rowKey}`,
32
- finding.reason,
33
- `allowed=${spec.allowed}`,
34
- `route=${finding.route}`,
35
- ].join(": ");
36
- if (isLegacyRow(rowUpdatedDate(row))) warnings.push(`${message}: legacy-report-only`);
37
- else failures.push(message);
38
- }
39
- }
40
- }
41
- }
42
- return { failures, warnings };
43
- }
44
-
45
- function evaluateFeatureRow(row) {
46
- const cells = row.cells || {};
47
- const text = rowText(row);
48
- const taskPlan = getCell(cells, ["Task Plan", "Task", "任务计划", "路径"], "");
49
- const evidence = getCell(cells, ["Acceptance Evidence", "Evidence", "验收证据"], "");
50
- const findings = [];
51
- if (/09-PLANNING\/MODULES\//i.test(taskPlan) && !isModuleAggregateRow(row) && localDetailPattern().test(text)) {
52
- findings.push({
53
- reason: "module-local detail belongs in module_plan.md or task files, not Feature SSoT",
54
- route: "module-plan-or-task-detail",
55
- });
56
- }
57
- if (longEvidencePattern().test(evidence) || temporaryPromptPattern().test(text)) {
58
- findings.push({
59
- reason: "long evidence or temporary repair prompt belongs in task evidence, not Feature SSoT",
60
- route: "task-artifacts-or-progress",
61
- });
62
- }
63
- return findings;
64
- }
65
-
66
- function isModuleAggregateRow(row) {
67
- const cells = row.cells || {};
68
- const id = getCell(cells, ["ID"], "");
69
- const taskPlan = getCell(cells, ["Task Plan", "Task", "任务计划", "路径"], "");
70
- return /^F-MODULE-/i.test(id) && /09-PLANNING\/MODULES\/[^/]+\/module_plan\.md/i.test(taskPlan);
71
- }
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
-
85
- function ledgerEvidenceText(row) {
86
- const cells = row.cells || {};
87
- return [
88
- "Evidence Summary",
89
- "Evidence",
90
- "Regression Evidence",
91
- "Review Evidence",
92
- "Regression",
93
- "Review",
94
- "证据摘要",
95
- "证据",
96
- "回归",
97
- "审查",
98
- ].map((column) => getCell(cells, [column], "")).filter(Boolean).join(" ");
99
- }
100
-
101
- function evaluateCloseoutRow(row) {
102
- const text = rowText(row);
103
- if (executionLogPattern().test(text) || rawTranscriptPattern().test(text)) {
104
- return [{
105
- reason: "closeout rows should route to walkthrough/evidence instead of carrying execution detail",
106
- route: "walkthrough-or-task-evidence",
107
- }];
108
- }
109
- return [];
110
- }
111
-
112
- function evaluateRegressionRow(row) {
113
- const text = rowText(row);
114
- if (executionLogPattern().test(text) || temporaryPromptPattern().test(text)) {
115
- return [{
116
- reason: "regression global tables should keep gate state and route detailed failure analysis elsewhere",
117
- route: "regression-detail-or-task-review",
118
- }];
119
- }
120
- return [];
121
- }
122
-
123
- function evaluateCadenceRow(row) {
124
- const text = rowText(row);
125
- if (rawTranscriptPattern().test(text) || temporaryPromptPattern().test(text)) {
126
- return [{
127
- reason: "cadence rows should summarize batch outcomes and route raw run detail elsewhere",
128
- route: "regression-batch-artifacts",
129
- }];
130
- }
131
- return [];
132
- }
133
-
134
- function governanceRowKey(row) {
135
- return getCell(row.cells || {}, ["ID", "Lesson", "Lesson ID", "Feature", "Work Item", "Gate ID", "Batch ID"], "") || row.id || "unknown-row";
136
- }
137
-
138
- function rowText(row) {
139
- return Object.values(row.cells || {}).join(" ");
140
- }
141
-
142
- function rowUpdatedDate(row) {
143
- const value = getCell(row.cells || {}, ["Updated", "Date", "日期", "Last Verified", "最近验证"], "");
144
- const match = String(value).match(/\d{4}-\d{2}-\d{2}/);
145
- return match ? match[0] : "";
146
- }
147
-
148
- function isLegacyRow(updated) {
149
- return updated && updated < newRuleCutoff;
150
- }
151
-
152
- function isPlaceholderRow(row) {
153
- const text = rowText(row);
154
- return /\b(?:YYYY|MM|DD|NNN)\b|L-YYYY|HL-YYYY|\[[^\]]+\]|\.\.\.md|\bowner\b|\bShort lesson title\b/i.test(text);
155
- }
156
-
157
- function localDetailPattern() {
158
- return /\b(module|local|implementation detail|parser branch|button label|copy every|工作项|局部|实现细节)\b/i;
159
- }
160
-
161
- function longEvidencePattern() {
162
- return /\b(long evidence|full local evidence|raw evidence|stack trace|reviewer transcript|copied raw)\b/i;
163
- }
164
-
165
- function executionLogPattern() {
166
- return /\b(execution log|command failed|stack trace|raw output|step one|step two|执行流水|命令输出)\b/i;
167
- }
168
-
169
- function temporaryPromptPattern() {
170
- return /\b(temporary repair prompt|repair prompt|copyable prompt|paste back|临时修复提示)\b/i;
171
- }
172
-
173
- function rawTranscriptPattern() {
174
- return /\b(raw transcript|reviewer transcript|full transcript|完整记录|原始记录)\b/i;
175
- }