coding-agent-harness 1.0.2 → 1.0.5

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 (219) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +244 -87
  6. package/README.zh-CN.md +77 -35
  7. package/SKILL.md +32 -24
  8. package/docs-release/README.md +9 -5
  9. package/docs-release/architecture/overview.md +17 -5
  10. package/docs-release/architecture/overview.zh-CN.md +9 -5
  11. package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
  12. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  13. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  14. package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
  15. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  16. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
  17. package/docs-release/architecture/system-explainer/README.md +67 -0
  18. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
  19. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  20. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  21. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
  22. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  23. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
  24. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  25. package/docs-release/assets/dashboard-overview.png +0 -0
  26. package/docs-release/guides/agent-installation.en-US.md +39 -15
  27. package/docs-release/guides/agent-installation.md +43 -16
  28. package/docs-release/guides/contributing.md +100 -0
  29. package/docs-release/guides/contributing.zh-CN.md +99 -0
  30. package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
  31. package/docs-release/guides/document-audience-and-surfaces.md +3 -2
  32. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
  33. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
  34. package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
  35. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
  36. package/docs-release/guides/migration-playbook.en-US.md +14 -15
  37. package/docs-release/guides/migration-playbook.md +14 -15
  38. package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
  39. package/docs-release/guides/parent-control-repository-pattern.md +7 -5
  40. package/docs-release/guides/preset-development.md +238 -0
  41. package/docs-release/guides/repository-operating-models.en-US.md +5 -4
  42. package/docs-release/guides/repository-operating-models.md +5 -4
  43. package/docs-release/guides/task-state-machine.en-US.md +224 -0
  44. package/docs-release/guides/task-state-machine.md +231 -0
  45. package/docs-release/intl/en-US.md +1 -1
  46. package/docs-release/intl/zh-CN.md +1 -1
  47. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
  48. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  49. package/package.json +10 -4
  50. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  51. package/presets/legacy-migration/preset.yaml +134 -0
  52. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  53. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  54. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  55. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  56. package/presets/legacy-migration/templates/review.seed.md +12 -0
  57. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  58. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  59. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  60. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  61. package/presets/lesson-sedimentation/preset.yaml +23 -0
  62. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  63. package/presets/module/preset.yaml +25 -0
  64. package/presets/module/templates/execution_strategy.append.md +8 -0
  65. package/presets/module/templates/task_plan.append.md +17 -0
  66. package/presets/standard-task/preset.yaml +31 -0
  67. package/presets/standard-task/templates/task_plan.append.md +7 -0
  68. package/references/adversarial-review-standard.md +2 -2
  69. package/references/agents-md-pattern.md +2 -2
  70. package/references/delivery-operating-model-standard.md +3 -3
  71. package/references/docs-directory-standard.md +6 -7
  72. package/references/harness-ledger.md +53 -96
  73. package/references/lessons-governance.md +88 -93
  74. package/references/module-parallel-standard.md +14 -14
  75. package/references/planning-loop.md +12 -6
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +11 -2
  78. package/references/review-routing-standard.md +7 -1
  79. package/references/ssot-governance.md +67 -59
  80. package/references/taskr-gap-analysis.md +600 -0
  81. package/references/walkthrough-closeout.md +7 -7
  82. package/scripts/check-harness.mjs +40 -301
  83. package/scripts/commands/dashboard-command.mjs +67 -0
  84. package/scripts/commands/migration-command.mjs +126 -0
  85. package/scripts/commands/preset-command.mjs +73 -0
  86. package/scripts/commands/task-command.mjs +328 -0
  87. package/scripts/harness.mjs +59 -260
  88. package/scripts/lib/capability-registry.mjs +82 -28
  89. package/scripts/lib/check-module-parallel.mjs +230 -0
  90. package/scripts/lib/check-profiles.mjs +90 -228
  91. package/scripts/lib/check-task-contracts.mjs +55 -0
  92. package/scripts/lib/core-shared.mjs +65 -2
  93. package/scripts/lib/dashboard-data.mjs +155 -24
  94. package/scripts/lib/dashboard-workbench.mjs +131 -12
  95. package/scripts/lib/dashboard-writer.mjs +20 -4
  96. package/scripts/lib/git-status-summary.mjs +46 -0
  97. package/scripts/lib/governance-index-generator.mjs +174 -0
  98. package/scripts/lib/governance-sync.mjs +611 -0
  99. package/scripts/lib/governance-table-boundary.mjs +175 -0
  100. package/scripts/lib/harness-core.mjs +6 -0
  101. package/scripts/lib/lesson-maintenance.mjs +36 -29
  102. package/scripts/lib/markdown-utils.mjs +33 -0
  103. package/scripts/lib/migration-planner.mjs +4 -6
  104. package/scripts/lib/migration-support.mjs +1 -1
  105. package/scripts/lib/phase-kind.mjs +50 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +494 -0
  108. package/scripts/lib/preset-registry.mjs +776 -0
  109. package/scripts/lib/preset-resource-contracts.mjs +83 -0
  110. package/scripts/lib/review-confirm-git-gate.mjs +248 -0
  111. package/scripts/lib/status-builder.mjs +88 -0
  112. package/scripts/lib/status-dashboard-renderer.mjs +105 -0
  113. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  114. package/scripts/lib/task-audit-metadata.mjs +385 -0
  115. package/scripts/lib/task-audit-migration.mjs +350 -0
  116. package/scripts/lib/task-completion-consistency.mjs +26 -0
  117. package/scripts/lib/task-index.mjs +93 -0
  118. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  119. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  120. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
  121. package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
  122. package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
  123. package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
  124. package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
  125. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
  126. package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
  127. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  128. package/scripts/lib/task-lifecycle.mjs +338 -477
  129. package/scripts/lib/task-metadata.mjs +118 -0
  130. package/scripts/lib/task-review-model.mjs +455 -0
  131. package/scripts/lib/task-scanner.mjs +193 -372
  132. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  133. package/scripts/postinstall.mjs +14 -0
  134. package/skills/preset-creator/SKILL.md +179 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  139. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  140. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  141. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  142. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  143. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  144. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  145. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  146. package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
  147. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  148. package/templates/AGENTS.md.template +24 -18
  149. package/templates/dashboard/assets/app-src/00-state.js +13 -0
  150. package/templates/dashboard/assets/app-src/10-router.js +5 -1
  151. package/templates/dashboard/assets/app-src/20-overview.js +18 -8
  152. package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
  153. package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
  154. package/templates/dashboard/assets/app-src/45-review.js +241 -22
  155. package/templates/dashboard/assets/app-src/50-migration.js +24 -10
  156. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  157. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  158. package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
  159. package/templates/dashboard/assets/app.css +1501 -376
  160. package/templates/dashboard/assets/app.css.manifest.json +10 -0
  161. package/templates/dashboard/assets/app.js +1240 -101
  162. package/templates/dashboard/assets/app.manifest.json +2 -0
  163. package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
  164. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  165. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  166. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  167. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  168. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
  169. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  170. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  171. package/templates/dashboard/assets/i18n.js +263 -23
  172. package/templates/ledger/Harness-Ledger.md +13 -25
  173. package/templates/lessons/lesson-arch-process-change.md +1 -1
  174. package/templates/lessons/lesson-new-doc.md +1 -1
  175. package/templates/lessons/lesson-ref-change.md +1 -1
  176. package/templates/planning/INDEX.md +87 -0
  177. package/templates/planning/brief.md +1 -1
  178. package/templates/planning/execution_strategy.md +31 -0
  179. package/templates/planning/lesson_candidates.md +18 -6
  180. package/templates/planning/module_session_prompt.md +1 -0
  181. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  182. package/templates/planning/optional/references/INDEX.md +3 -3
  183. package/templates/planning/review.md +41 -0
  184. package/templates/planning/task_plan.md +5 -21
  185. package/templates/planning/visual_map.md +13 -9
  186. package/templates/planning/visual_map.simple.md +52 -0
  187. package/templates/reference/execution-workflow-standard.md +31 -3
  188. package/templates/reference/pull-request-standard.md +80 -0
  189. package/templates/reference/repo-governance-standard.md +7 -6
  190. package/templates/reference/review-routing-standard.md +6 -0
  191. package/templates/reference/walkthrough-standard.md +2 -1
  192. package/templates/verifier/verifier-output.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +25 -19
  194. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  195. package/templates-zh-CN/planning/INDEX.md +87 -0
  196. package/templates-zh-CN/planning/brief.md +1 -1
  197. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  198. package/templates-zh-CN/planning/lesson_candidates.md +18 -6
  199. package/templates-zh-CN/planning/module_session_prompt.md +1 -0
  200. package/templates-zh-CN/planning/review.md +41 -1
  201. package/templates-zh-CN/planning/task_plan.md +4 -44
  202. package/templates-zh-CN/planning/visual_map.md +14 -7
  203. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  204. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  205. package/templates-zh-CN/reference/docs-library-standard.md +1 -1
  206. package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
  207. package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
  208. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  209. package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
  210. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  211. package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
  212. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
  213. package/docs-release/assets/dashboard-overview-en.png +0 -0
  214. package/scripts/smoke-dashboard.mjs +0 -92
  215. package/scripts/test-harness.mjs +0 -1395
  216. package/templates/ssot/Feature-SSoT.md +0 -43
  217. package/templates/ssot/Lessons-SSoT.md +0 -44
  218. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  219. package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
@@ -16,10 +16,11 @@ Keep it as a charter and routing index. Detailed rules belong in
16
16
 
17
17
  1. Preserve architecture boundaries documented in `docs/11-REFERENCE/engineering-standard.md`.
18
18
  2. Do not commit secrets, credentials, private endpoints, or user data.
19
- 3. Start non-trivial work with a task directory under `docs/09-PLANNING/TASKS/`.
19
+ 3. Start non-trivial work with a Harness CLI-created task directory under `docs/09-PLANNING/TASKS/`.
20
20
  4. Record evidence before claiming completion.
21
21
  5. Protect unrelated working-tree changes; never revert files outside the assigned scope.
22
- 6. Commit verified, meaningful work slices unless the user explicitly says not to commit.
22
+ 6. Commit verified, meaningful work slices proactively. Do not leave completed work only as dirty files unless the user explicitly pauses commits, checks fail, dirty ownership is unclear, or a security boundary prevents a clean commit; in those cases record the no-commit reason, owner, and next step in progress, handoff, or closeout notes. Never mix unrelated dirty changes into a task commit.
23
+ 7. Before the final response, inspect the current task `visual_map.md` gate phase. If the current `Exit Command` has `Actor: agent`, run it or record the blocker. Never run a `human` gate such as `review-confirm` unless the user has explicitly performed or delegated that human confirmation.
23
24
 
24
25
  ## Reading Matrix
25
26
 
@@ -32,8 +33,8 @@ Read the smallest context that can answer the task.
32
33
  | Local development, mocks, stubs, or cross-repo debugging | `docs/04-DEVELOPMENT/README.md`, `docs/04-DEVELOPMENT/codebase-map.md` |
33
34
  | API, event, webhook, SDK, or third-party contract | `docs/06-INTEGRATIONS/README.md` and the related contract file |
34
35
  | Tests, smoke, or regression | `docs/11-REFERENCE/testing-standard.md`, `docs/05-TEST-QA/Regression-SSoT.md` |
35
- | Execution, commit, PR, or release work | `docs/11-REFERENCE/execution-workflow-standard.md`, `docs/11-REFERENCE/repo-governance-standard.md`, `docs/11-REFERENCE/ci-cd-standard.md` |
36
- | Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use the Harness CLI if this project has it configured |
36
+ | Execution, commit, PR, or release work | `docs/11-REFERENCE/execution-workflow-standard.md`, `docs/11-REFERENCE/repo-governance-standard.md`, `docs/11-REFERENCE/pull-request-standard.md`, `docs/11-REFERENCE/ci-cd-standard.md` |
37
+ | Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use `harness new-task` / lifecycle commands when this project has the Harness CLI configured |
37
38
  | Brief, Execution Strategy, or Visual Map | Current task `brief.md`, `execution_strategy.md`, and `visual_map.md` |
38
39
  | Long-running task | `docs/11-REFERENCE/long-running-task-standard.md` |
39
40
  | Reviewer, subagent, or adversarial review | `docs/11-REFERENCE/review-routing-standard.md`, `docs/11-REFERENCE/adversarial-review-standard.md` |
@@ -42,37 +43,42 @@ Read the smallest context that can answer the task.
42
43
  | Documentation governance or Harness update | `docs/11-REFERENCE/docs-library-standard.md`, `.harness-capabilities.json` |
43
44
  | External source material intake | `docs/11-REFERENCE/external-source-intake-standard.md` |
44
45
  | Regression SSoT maintenance | `docs/11-REFERENCE/regression-ssot-governance.md` |
45
- | Walkthrough, closeout, or lessons | `docs/11-REFERENCE/walkthrough-standard.md`, `docs/10-WALKTHROUGH/Closeout-SSoT.md`, `docs/01-GOVERNANCE/Lessons-SSoT.md` |
46
+ | Walkthrough, closeout, or lessons | `docs/11-REFERENCE/walkthrough-standard.md`, `docs/10-WALKTHROUGH/Closeout-SSoT.md`, current task `lesson_candidates.md`, `docs/01-GOVERNANCE/lessons/` |
46
47
  | Worktree operations | `docs/11-REFERENCE/worktree-standard.md` |
47
48
 
48
49
  ## Standard Execution Flow
49
50
 
50
51
  1. Confirm the request, scope, and affected files.
51
- 2. For non-trivial work, create or update the task plan before editing code.
52
- 3. Load only the reference documents required by the Reading Matrix.
53
- 4. Preserve existing project facts; merge new context instead of overwriting history.
54
- 5. Use a worktree or branch when the task requires isolation or parallel work.
55
- 6. Implement only within the approved scope.
56
- 7. Run the relevant checks and capture evidence in the task records.
57
- 8. Route required review and close blocking findings.
58
- 9. Write or update the walkthrough and closeout records.
59
- 10. Update SSoT / ledger files that the task actually touched.
52
+ 2. For non-trivial work, create or update the task plan before editing code. Use `harness new-task`; if the CLI is unavailable, keep Task Audit Metadata in the task `INDEX.md` and set `Created By` to `manual-exception` with a concrete reason.
53
+ 3. Assess delegation from the user's goal; users do not need to know or ask for subagents.
54
+ 4. Load only the reference documents required by the Reading Matrix.
55
+ 5. Preserve existing project facts; merge new context instead of overwriting history.
56
+ 6. Use a worktree or branch when the task requires isolation or parallel work.
57
+ 7. Implement only within the approved scope.
58
+ 8. Run the relevant checks and capture evidence in the task records.
59
+ 9. Route required review and close blocking findings.
60
+ 10. Follow the current `visual_map.md` lifecycle gate and its `Exit Command` / `Actor`.
61
+ 11. Write or update the walkthrough and closeout records.
62
+ 12. Update SSoT / ledger files that the task actually touched.
60
63
 
61
64
  ## Coordination Rules
62
65
 
63
- - Reviewers are read-only unless explicitly assigned a write scope.
64
- - Workers may edit only their assigned branch, worktree, and file scope.
66
+ - At task start, read the current task `execution_strategy.md` Subagent Authorization and Subagent Delegation Decision sections, then state whether reviewer or worker subagents should be used; do this even when the user only states the desired outcome.
67
+ - Reviewer subagents are allowed by default for read-only review within the current task.
68
+ - If a worker subagent would materially help and is not authorized, proactively ask the user once in plain language for task/scope/worktree authorization; it is fine to say "worker subagent", but do not wait for the user to know or suggest subagents.
69
+ - If independent slices are obvious but exact file paths are not, identify the file paths first and then immediately ask for independent-execution-helper authorization before implementation.
70
+ - Worker subagents require one user authorization recorded in `execution_strategy.md`; after that, reuse is allowed only within the same task, scope, and worktree/branch.
65
71
  - Shared ledgers, registries, and SSoT files are coordinator-owned unless a lock is explicitly assigned.
66
72
  - Worker handoff must include branch or worktree, changed files, checks, evidence, and residual risks.
67
73
 
68
74
  ## Single Sources Of Truth
69
75
 
70
- - Feature SSoT: `docs/09-PLANNING/Feature-SSoT.md`
71
76
  - Delivery SSoT: `docs/09-PLANNING/Delivery-SSoT.md`
72
77
  - Module Registry: `docs/09-PLANNING/Module-Registry.md`
73
78
  - Regression SSoT: `docs/05-TEST-QA/Regression-SSoT.md`
74
79
  - Cadence Ledger: `docs/05-TEST-QA/Cadence-Ledger.md`
75
- - Lessons SSoT: `docs/01-GOVERNANCE/Lessons-SSoT.md`
80
+ - Lesson Candidates: current task `lesson_candidates.md`
81
+ - Lesson Detail Docs: `docs/01-GOVERNANCE/lessons/`
76
82
  - Closeout SSoT: `docs/10-WALKTHROUGH/Closeout-SSoT.md`
77
83
  - Harness Ledger: `docs/Harness-Ledger.md`
78
84
 
@@ -12,9 +12,22 @@ const state = {
12
12
  taskGroupPage: 1,
13
13
  warningFilter: "all",
14
14
  warningPage: 1,
15
+ presetQuery: "",
16
+ presetSourceFilter: "all",
17
+ selectedPresetKey: "",
18
+ selectedPresetId: "",
19
+ presetActionResult: null,
20
+ presetInstallSource: "",
21
+ presetInstallScope: "project",
22
+ presetInstallForce: false,
23
+ presetSeedScope: "project",
24
+ presetSeedForce: false,
25
+ presetUninstallScope: "project",
26
+ presetUninstallConfirm: "",
15
27
  renderMode: "rendered",
16
28
  theme: localStorage.getItem("harness.theme") || "system",
17
29
  taskLayout: localStorage.getItem("harness.taskLayout") || "list",
30
+ taskSortOrder: localStorage.getItem("harness.taskSortOrder") === "asc" ? "asc" : "desc",
18
31
  runtime: { mode: "static", csrfToken: "", writableActions: [] },
19
32
  runtimeLoaded: false,
20
33
  runtimePoller: null,
@@ -29,6 +29,7 @@ function shell() {
29
29
  ${routeLink("#/tasks", t("taskIndex"), "tasks")}
30
30
  ${routeLink("#/review", t("reviewQueue"), "review")}
31
31
  ${routeLink("#/modules", t("moduleView"), "modules")}
32
+ ${routeLink("#/presets", t("presetCatalog"), "presets")}
32
33
  <button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
33
34
  <button data-theme-toggle>${themeLabel()}</button>
34
35
  </div>
@@ -55,6 +56,7 @@ function renderRoute() {
55
56
  if (route.name === "reviewTask") return reviewWorkspace(route);
56
57
  if (route.name === "review") return reviewQueue();
57
58
  if (route.name === "modules") return modulesView(route.id);
59
+ if (route.name === "presets") return presetsView();
58
60
  if (route.name === "tasks") return taskIndex();
59
61
  return overview();
60
62
  }
@@ -66,11 +68,13 @@ function currentRoute() {
66
68
  if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
67
69
  if (parts[0] === "review") return { name: "review" };
68
70
  if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
71
+ if (parts[0] === "presets") return { name: "presets" };
69
72
  if (parts[0] === "tasks") return { name: "tasks" };
70
73
  return { name: "overview" };
71
74
  }
72
75
 
73
76
  function routeLink(hash, text, routeName) {
74
- const active = currentRoute().name === routeName;
77
+ const current = currentRoute().name;
78
+ const active = current === routeName || (routeName === "review" && current === "reviewTask");
75
79
  return `<a class="${active ? "active" : ""}" href="${hash}">${escapeHtml(text)}</a>`;
76
80
  }
@@ -16,6 +16,9 @@ function overview() {
16
16
 
17
17
  function statusStrip() {
18
18
  const status = bundle.status?.checkState?.status || "unknown";
19
+ const validationMode = bundle.status?.checkState?.validationMode || "validated";
20
+ const dataOnly = validationMode === "data-only";
21
+ const displayState = dataOnly ? "snapshot" : status;
19
22
  const failures = bundle.status?.checkState?.failures || 0;
20
23
  const warnings = bundle.status?.checkState?.warnings || 0;
21
24
  const tasks = bundle.status?.tasks || [];
@@ -23,9 +26,9 @@ function statusStrip() {
23
26
  const visual = summary.visualMapCoverage || {};
24
27
  const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
25
28
  return `<section class="status-card-group">
26
- <div class="status-primary ${status}">
27
- <span>${t("readiness")}</span>
28
- <strong>${label(status)}</strong>
29
+ <div class="status-primary ${displayState}">
30
+ <span>${dataOnly ? t("snapshotStatus") : t("readiness")}</span>
31
+ <strong>${dataOnly ? t("snapshot") : label(status)}</strong>
29
32
  <p>${nextActionText()}</p>
30
33
  </div>
31
34
  <div class="metrics-grid">
@@ -46,6 +49,7 @@ function metric(labelText, value) {
46
49
  }
47
50
 
48
51
  function nextActionText() {
52
+ if ((bundle.status?.checkState?.validationMode || "validated") === "data-only") return t("snapshotNotValidated");
49
53
  const failures = bundle.status?.checkState?.failures || 0;
50
54
  if (failures > 0) return t("resolveBlockers");
51
55
  const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
@@ -165,24 +169,29 @@ function graphSummary() {
165
169
  }
166
170
 
167
171
  function activeTaskBriefs() {
168
- const tasks = activeTasks().slice(0, 8);
172
+ const tasks = activeTasks();
169
173
  return `<section class="task-briefs">
170
174
  <div class="section-head">
171
175
  <div>
172
176
  <p class="eyebrow">${t("currentWork")}</p>
173
177
  <h2>${t("activeBriefs")}</h2>
174
178
  </div>
175
- <a href="#/tasks">${t("openTaskIndex")}</a>
179
+ <div class="section-actions">
180
+ <span class="subtle">${t("activeBriefCount").replace("{count}", tasks.length).replace("{order}", taskSortLabel())}</span>
181
+ <a href="#/tasks">${t("openTaskIndex")}</a>
182
+ </div>
183
+ </div>
184
+ <div class="brief-scroll">
185
+ <div class="brief-grid">${tasks.map((task) => taskBriefCard(task, { compact: false })).join("") || emptyState(t("noActiveTasks"))}</div>
176
186
  </div>
177
- <div class="brief-grid">${tasks.map((task) => taskBriefCard(task, { compact: false })).join("") || emptyState(t("noActiveTasks"))}</div>
178
187
  </section>`;
179
188
  }
180
189
 
181
190
  function activeTasks() {
182
191
  const tasks = bundle.status?.tasks || [];
183
192
  const active = tasks.filter((task) => isActiveTaskState(task.state) || ["planned", "not_started"].includes(task.state));
184
- if (active.length > 0) return active;
185
- return tasks.filter((task) => task.briefSource === "standalone").slice(0, 6);
193
+ if (active.length > 0) return sortTasksByTime(active);
194
+ return sortTasksByTime(tasks.filter((task) => task.briefSource === "standalone"));
186
195
  }
187
196
 
188
197
  function isActiveTaskState(state) {
@@ -205,6 +214,7 @@ function taskBriefCard(task, { compact = true } = {}) {
205
214
  <p class="brief-teaser">${escapeHtml(summaryText)}</p>
206
215
  </div>
207
216
  <div class="card-actions">
217
+ ${taskCopyButton(task)}
208
218
  <button class="btn-drawer-trigger" data-open-drawer="${escapeAttr(task.id)}">${t("viewDetails")}</button>
209
219
  </div>
210
220
  </article>`;
@@ -8,6 +8,59 @@ function stateToColorVar(state) {
8
8
  return map[state] || "--muted";
9
9
  }
10
10
 
11
+ function taskSortLabel() {
12
+ return state.taskSortOrder === "asc" ? t("sortOldest") : t("sortNewest");
13
+ }
14
+
15
+ function taskDateKey(task) {
16
+ const source = `${task.shortId || ""} ${task.id || ""}`.trim();
17
+ const match = source.match(/(?:^|[^\d])(\d{4})-(\d{2})(?:-(\d{2}))?/);
18
+ if (!match) return null;
19
+ const year = Number(match[1]);
20
+ const month = Number(match[2]);
21
+ const day = Number(match[3] || "1");
22
+ if (!year || month < 1 || month > 12 || day < 1 || day > 31) return null;
23
+ return Date.UTC(year, month - 1, day);
24
+ }
25
+
26
+ function stableTaskLabel(task) {
27
+ return `${task.shortId || ""} ${task.id || ""} ${task.title || ""}`.trim();
28
+ }
29
+
30
+ function compareTasksByTime(left, right) {
31
+ const leftDate = taskDateKey(left);
32
+ const rightDate = taskDateKey(right);
33
+ if (leftDate !== null && rightDate !== null && leftDate !== rightDate) {
34
+ return state.taskSortOrder === "asc" ? leftDate - rightDate : rightDate - leftDate;
35
+ }
36
+ if (leftDate !== null && rightDate === null) return -1;
37
+ if (leftDate === null && rightDate !== null) return 1;
38
+ return stableTaskLabel(left).localeCompare(stableTaskLabel(right));
39
+ }
40
+
41
+ function sortTasksByTime(tasks) {
42
+ return [...tasks].sort(compareTasksByTime);
43
+ }
44
+
45
+ function taskFolderName(task) {
46
+ const fromPath = String(task?.path || "").split("/").filter(Boolean).pop();
47
+ const fromId = String(task?.id || "").split("/").filter(Boolean).pop();
48
+ return task?.shortId || fromPath || fromId || task?.title || "";
49
+ }
50
+
51
+ function taskCopyButton(task, extraClass = "") {
52
+ const folderName = taskFolderName(task);
53
+ return `<button type="button" class="copy-task-name ${extraClass}" data-copy-task-name="${escapeAttr(folderName)}" data-copy-task-folder="${escapeAttr(folderName)}" aria-label="${escapeAttr(t("copyTaskName"))}" title="${escapeAttr(t("copyTaskName"))}">
54
+ ${t("copyTaskNameShort")}
55
+ </button>`;
56
+ }
57
+
58
+ function taskGroupTimeKey(group) {
59
+ const match = group.match(/^(?:month|legacy):(\d{4})-(\d{2})$/);
60
+ if (!match) return null;
61
+ return Date.UTC(Number(match[1]), Number(match[2]) - 1, 1);
62
+ }
63
+
11
64
  function taskToolbarCard(filteredCount) {
12
65
  return `<section class="sidebar-card">
13
66
  <h3>${t("filterTitle")}</h3>
@@ -39,6 +92,17 @@ function taskToolbarCard(filteredCount) {
39
92
  </button>
40
93
  </div>
41
94
  </div>
95
+ <div class="select-group">
96
+ <label>${t("sortByTime")}</label>
97
+ <div class="layout-toggle-group sort-toggle-group">
98
+ <button class="layout-btn ${state.taskSortOrder === "desc" ? "active" : ""}" data-task-sort-order="desc" aria-label="${t("sortNewest")}">
99
+ ${t("sortNewest")}
100
+ </button>
101
+ <button class="layout-btn ${state.taskSortOrder === "asc" ? "active" : ""}" data-task-sort-order="asc" aria-label="${t("sortOldest")}">
102
+ ${t("sortOldest")}
103
+ </button>
104
+ </div>
105
+ </div>
42
106
  <div class="search-stats">
43
107
  ${t("showing")} <strong>${filteredCount}</strong> / ${(bundle.status?.tasks || []).length} ${t("tasks")}
44
108
  </div>
@@ -142,11 +206,12 @@ function taskRow(task) {
142
206
  const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
143
207
  const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
144
208
 
145
- return `<a class="task-row-card" href="#/tasks/${encodeURIComponent(task.id)}" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateToColorVar(task.state)})">
209
+ return `<article class="task-row-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateToColorVar(task.state)})">
146
210
  <div class="row-accent-bar"></div>
147
211
  <div class="row-main">
148
212
  <strong>${escapeHtml(task.title)}</strong>
149
213
  <span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(taskModuleKey(task))}</span>
214
+ ${taskCopyButton(task, "row-copy")}
150
215
  </div>
151
216
  <div class="row-status">${tag(task.state)}</div>
152
217
  <div class="row-progress">
@@ -165,7 +230,7 @@ function taskRow(task) {
165
230
  ${mapReady ? t("badgeMap") : t("badgeMapMissing")}
166
231
  </span>
167
232
  </div>
168
- </a>`;
233
+ </article>`;
169
234
  }
170
235
 
171
236
  function taskIndex() {
@@ -204,7 +269,18 @@ function orderedTaskGroups(groups) {
204
269
  if (group === "unknown") return 3;
205
270
  return 4;
206
271
  };
207
- return Object.entries(groups).sort(([left], [right]) => rank(left) - rank(right) || left.localeCompare(right));
272
+ return Object.entries(groups).sort(([left], [right]) => {
273
+ const rankDiff = rank(left) - rank(right);
274
+ if (rankDiff !== 0) return rankDiff;
275
+ const leftTime = taskGroupTimeKey(left);
276
+ const rightTime = taskGroupTimeKey(right);
277
+ if (leftTime !== null && rightTime !== null && leftTime !== rightTime) {
278
+ return state.taskSortOrder === "asc" ? leftTime - rightTime : rightTime - leftTime;
279
+ }
280
+ if (leftTime !== null && rightTime === null) return -1;
281
+ if (leftTime === null && rightTime !== null) return 1;
282
+ return left.localeCompare(right);
283
+ });
208
284
  }
209
285
 
210
286
  function taskGroups(tasks) {
@@ -229,11 +305,12 @@ function taskGroups(tasks) {
229
305
  }
230
306
 
231
307
  function taskGroup(group, tasks) {
232
- const pageCount = Math.max(1, Math.ceil(tasks.length / taskPageSize));
308
+ const orderedTasks = sortTasksByTime(tasks);
309
+ const pageCount = Math.max(1, Math.ceil(orderedTasks.length / taskPageSize));
233
310
  const page = Math.min(Math.max(1, Number(state.taskPageByGroup[group]) || 1), pageCount);
234
311
  const start = (page - 1) * taskPageSize;
235
- const visibleTasks = tasks.slice(start, start + taskPageSize);
236
- const avgCompletion = tasks.length ? clampCompletion(tasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / tasks.length) : 0;
312
+ const visibleTasks = orderedTasks.slice(start, start + taskPageSize);
313
+ const avgCompletion = orderedTasks.length ? clampCompletion(orderedTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / orderedTasks.length) : 0;
237
314
 
238
315
  const isGrid = state.taskLayout === "grid";
239
316
  const layoutClass = isGrid ? "task-card-grid" : "task-list";
@@ -250,7 +327,7 @@ function taskGroup(group, tasks) {
250
327
  <div class="section-head">
251
328
  <div>
252
329
  <h2>${taskGroupLabel(group)}</h2>
253
- <p class="subtle">${t("showing")} ${Math.min(start + 1, tasks.length)}-${Math.min(start + visibleTasks.length, tasks.length)} / ${tasks.length}</p>
330
+ <p class="subtle">${t("showing")} ${Math.min(start + 1, orderedTasks.length)}-${Math.min(start + visibleTasks.length, orderedTasks.length)} / ${orderedTasks.length}</p>
254
331
  </div>
255
332
  <div class="group-actions">
256
333
  <div class="group-progress" aria-label="${escapeAttr(t("groupCompletion"))}">
@@ -275,10 +352,13 @@ function taskCard(task) {
275
352
  const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
276
353
  const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
277
354
 
278
- return `<a class="task-card" href="#/tasks/${encodeURIComponent(task.id)}" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateColor})">
355
+ return `<article class="task-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateColor})">
279
356
  <div class="card-header">
280
357
  <span class="card-id">${escapeHtml(task.id)}</span>
281
- ${tag(task.state)}
358
+ <div class="card-header-actions">
359
+ ${taskCopyButton(task, "compact")}
360
+ ${tag(task.state)}
361
+ </div>
282
362
  </div>
283
363
  <h4 class="card-title" title="${escapeAttr(task.title)}">${escapeHtml(task.title)}</h4>
284
364
  <div class="card-meta">
@@ -301,7 +381,7 @@ function taskCard(task) {
301
381
  ${mapReady ? t("badgeMap") : t("badgeMapMissing")}
302
382
  </span>
303
383
  </div>
304
- </a>`;
384
+ </article>`;
305
385
  }
306
386
 
307
387
  function taskGroupLabel(group) {
@@ -316,248 +396,14 @@ function taskGroupLabel(group) {
316
396
 
317
397
  function filteredTasks() {
318
398
  const query = state.query.trim().toLowerCase();
319
- return (bundle.status?.tasks || []).filter((task) => {
399
+ return sortTasksByTime((bundle.status?.tasks || []).filter((task) => {
320
400
  const stateMatch = state.taskState === "all" || task.state === state.taskState;
321
401
  if (!stateMatch) return false;
322
402
  if (!query) return true;
323
403
  return [task.id, task.shortId, task.title, task.module, task.inferredModule, task.classificationSource, task.classificationBucket, task.state].some((value) => String(value || "").toLowerCase().includes(query));
324
- });
404
+ }));
325
405
  }
326
406
 
327
407
  function taskModuleKey(task) {
328
408
  return task.module || task.inferredModule || "legacy-unclassified";
329
409
  }
330
-
331
- function taskDetail(route) {
332
- const taskId = route.id;
333
- const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
334
- if (!task) return `<main>${emptyState(t("taskNotFound"))}</main>`;
335
- return `<main class="task-detail">
336
- <nav class="crumbs"><a href="#/tasks">${t("taskIndex")}</a><span>/</span><span>${escapeHtml(task.id)}</span></nav>
337
- <section class="detail-hero">
338
- <div>
339
- <p class="eyebrow">${t("taskVisibility")}</p>
340
- <h2>${escapeHtml(task.title)}</h2>
341
- <p>${escapeHtml(task.path)}</p>
342
- </div>
343
- <div class="detail-score">${task.completion}%</div>
344
- </section>
345
- ${taskStateSummary(task)}
346
- ${phaseTimeline(task)}
347
- <section class="detail-grid">
348
- <article class="detail-main">
349
- ${taskDocumentLibrary(task, route.doc)}
350
- </article>
351
- <aside class="detail-side">
352
- ${reviewActionPanel(task, { mode: "summary" })}
353
- ${migrationSnapshotPanel(task)}
354
- ${openFindings(task)}
355
- ${evidenceList(task)}
356
- ${documentTabs(task)}
357
- </aside>
358
- </section>
359
- </main>`;
360
- }
361
-
362
- function taskStateSummary(task) {
363
- return `<section class="task-state-summary">
364
- <div>
365
- <span>${t("legacyState")}</span>
366
- ${tag(task.state)}
367
- </div>
368
- <div>
369
- <span>${t("lifecycleState")}</span>
370
- ${tag(task.lifecycleState || "unknown")}
371
- </div>
372
- <div>
373
- <span>${t("reviewStatus")}</span>
374
- ${tag(task.reviewStatus || "missing")}
375
- </div>
376
- <div>
377
- <span>${t("closeoutStatus")}</span>
378
- ${tag(task.closeoutStatus || "missing")}
379
- </div>
380
- </section>`;
381
- }
382
-
383
- function phaseTimeline(task) {
384
- return `<section class="phase-timeline">
385
- <h2>${t("phaseTimeline")}</h2>
386
- ${(task.phases || []).map((phase) => `<div class="phase-step ${phase.state}">
387
- <strong>${escapeHtml(phase.id)}</strong>
388
- <span>${phase.completion}%</span>
389
- <p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
390
- ${progressBar(phase.completion)}
391
- </div>`).join("") || emptyState(t("noPhaseData"))}
392
- </section>`;
393
- }
394
-
395
- function taskDocSection(task, fileName, title, required) {
396
- const doc = taskDocument(task, fileName);
397
- if (!doc && !required) return "";
398
- return `<section class="doc-section">
399
- <div class="section-head"><h2>${escapeHtml(title)}</h2>${doc ? `<button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button>` : ""}</div>
400
- <div class="markdown">${doc ? window.HarnessMarkdown.render(doc.content, state.renderMode) : generatedBrief(task)}</div>
401
- </section>`;
402
- }
403
-
404
- function taskDocumentLibrary(task, selectedTab) {
405
- const docs = orderedTaskDocuments(task);
406
- if (!docs.length) return taskDocSection(task, "brief.md", t("brief"), true);
407
- const selectedKey = docs.some((doc) => doc.key === selectedTab) ? selectedTab : defaultTaskDocumentKey(task, docs);
408
- return `<section class="doc-library">
409
- <div class="section-head">
410
- <div>
411
- <p class="eyebrow">${t("taskDocuments")}</p>
412
- <h2>${escapeHtml(t("sourceDocuments"))}</h2>
413
- </div>
414
- <button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button>
415
- </div>
416
- <div class="doc-accordion-list">
417
- ${docs.map((item) => documentAccordion(item, item.key === selectedKey)).join("")}
418
- </div>
419
- </section>`;
420
- }
421
-
422
- function orderedTaskDocuments(task) {
423
- const docs = taskDocTabs
424
- .map(([key, file]) => {
425
- const doc = taskDocument(task, file);
426
- if (doc) return { key, file, title: t(key), path: doc.path, content: doc.content };
427
- if (key === "brief") return { key, file, title: t(key), path: `${task.path}/brief.md`, content: generatedBrief(task), generated: true };
428
- return null;
429
- })
430
- .filter(Boolean);
431
- const priority = taskDocumentPriority(task);
432
- const rank = new Map(priority.map((key, index) => [key, index]));
433
- return docs.sort((a, b) => (rank.get(a.key) ?? 99) - (rank.get(b.key) ?? 99));
434
- }
435
-
436
- function taskDocumentPriority(task) {
437
- const stateName = task?.state || "";
438
- const lifecycle = task?.lifecycleState || "";
439
- if (stateName === "review" || ["in_review", "review-blocked"].includes(lifecycle)) {
440
- return ["walkthrough", "lessonCandidates", "review", "findings", "visualMap", "progress", "brief", "taskPlan", "strategy", "longRunningContract", "legacyRoadmap", "references", "artifacts"];
441
- }
442
- if (stateName === "in_progress" || lifecycle === "active" || stateName === "blocked") {
443
- return ["progress", "visualMap", "brief", "taskPlan", "strategy", "findings", "review", "walkthrough", "references", "artifacts", "legacyRoadmap"];
444
- }
445
- if (stateName === "done" || ["closing", "closed"].includes(lifecycle)) {
446
- return ["walkthrough", "progress", "review", "findings", "visualMap", "brief", "taskPlan", "strategy", "references", "artifacts", "legacyRoadmap"];
447
- }
448
- return ["brief", "taskPlan", "visualMap", "strategy", "progress", "findings", "review", "walkthrough", "references", "artifacts", "legacyRoadmap"];
449
- }
450
-
451
- function defaultTaskDocumentKey(task, docs) {
452
- const priority = taskDocumentPriority(task);
453
- return priority.find((key) => docs.some((doc) => doc.key === key)) || docs[0]?.key || "brief";
454
- }
455
-
456
- function documentAccordion(item, open) {
457
- return `<details class="doc-accordion" ${open ? "open" : ""}>
458
- <summary>
459
- <span>${escapeHtml(item.title)}</span>
460
- <small>${escapeHtml(item.generated ? t("generatedFallback") : item.path)}</small>
461
- </summary>
462
- <div class="markdown">${window.HarnessMarkdown.render(item.content, state.renderMode)}</div>
463
- </details>`;
464
- }
465
-
466
- function documentTabs(task) {
467
- const docs = orderedTaskDocuments(task);
468
- return `<section class="side-panel">
469
- <h3>${t("sourceDocuments")}</h3>
470
- ${docs.map((doc) => `<a href="#/tasks/${encodeURIComponent(task.id)}/docs/${encodeURIComponent(doc.key)}" title="${escapeAttr(doc.path)}">${escapeHtml(doc.title)}</a>`).join("") || `<p>${t("noDocuments")}</p>`}
471
- </section>`;
472
- }
473
-
474
- function selectedSourceDocument(task, tab) {
475
- if (!tab) return "";
476
- const match = taskDocTabs.find(([key]) => key === tab);
477
- if (!match) return "";
478
- const doc = taskDocument(task, match[1]);
479
- if (!doc) return "";
480
- return `<section class="doc-section selected-source">
481
- <div class="section-head"><h2>${t("selectedSource")} · ${t(match[0])}</h2><button data-render-toggle>${state.renderMode === "rendered" ? t("source") : t("rendered")}</button></div>
482
- <div class="markdown">${window.HarnessMarkdown.render(doc.content, state.renderMode)}</div>
483
- </section>`;
484
- }
485
-
486
- function openFindings(task) {
487
- const risks = task.risks || [];
488
- return `<section class="side-panel">
489
- <h3>${t("openFindings")}</h3>
490
- ${risks.map((risk) => `<div class="finding ${risk.open || risk.blocksRelease ? "open" : ""}"><strong>${escapeHtml(risk.severity)}</strong><span>${escapeHtml(risk.summary)}</span></div>`).join("") || `<p>${t("noOpenFindings")}</p>`}
491
- </section>`;
492
- }
493
-
494
- function migrationSnapshotPanel(task) {
495
- const snapshot = task.migrationSnapshot;
496
- if (!snapshot) return "";
497
- return `<section class="side-panel">
498
- <h3>${t("migrationSnapshot")}</h3>
499
- <p>${escapeHtml(t("taskPreset"))}: ${tag(task.taskPreset || "none")}</p>
500
- <p>${escapeHtml(t("targetLevel"))}: ${tag(snapshot.targetLevel || "unknown")}</p>
501
- <p>${escapeHtml(t("achievedLevel"))}: ${tag(snapshot.achievedLevel || "unknown")}</p>
502
- <p>${escapeHtml(t("strictDeferred"))}: ${tag(snapshot.strictDeferred ? "yes" : "no")}</p>
503
- <p>${escapeHtml(t("evidenceBundle"))}: <code>${escapeHtml(snapshot.evidenceBundle || "missing")}</code></p>
504
- </section>`;
505
- }
506
-
507
- function reviewActionPanel(task, { mode = "summary" } = {}) {
508
- if (!isTaskInReviewStage(task)) return "";
509
- const blocking = task.reviewStatus === "blocked-open-findings" || (task.risks || []).some((risk) => /^P[0-2]$/i.test(risk.severity || "") && (risk.open || risk.blocksRelease));
510
- const confirmed = task.reviewStatus === "confirmed";
511
- const candidateBlocked = task.budget !== "simple" && !task.lessonCandidateDecisionComplete;
512
- const candidateStatus = task.lessonCandidateStatus || "missing";
513
- if (mode !== "workspace") {
514
- return `<section class="side-panel review-actions">
515
- <h3>${t("reviewActions")}</h3>
516
- <p>${escapeHtml(confirmed ? t("reviewAlreadyConfirmed") : t("reviewOpenInWorkspace"))}</p>
517
- <p>${escapeHtml(t("lessonCandidateStatus"))}: ${tag(candidateStatus)}</p>
518
- <a href="#/review/${encodeURIComponent(task.id)}">${t("openReviewWorkspace")}</a>
519
- </section>`;
520
- }
521
- if (!canUseWorkbenchAction("review-complete")) {
522
- return `<section class="side-panel review-actions">
523
- <h3>${t("reviewActions")}</h3>
524
- <p>${escapeHtml(t("staticReadOnlyDetail"))}</p>
525
- </section>`;
526
- }
527
- if (confirmed) {
528
- return `<section class="side-panel review-actions">
529
- <h3>${t("reviewActions")}</h3>
530
- <p>${escapeHtml(t("reviewAlreadyConfirmed"))}</p>
531
- </section>`;
532
- }
533
- const missingWalkthrough = task.budget !== "simple" && !task.walkthroughPath;
534
- const disabled = blocking || missingWalkthrough || candidateBlocked;
535
- const message = missingWalkthrough ? t("reviewWalkthroughRequired") : blocking ? t("reviewBlocked") : candidateBlocked ? t("reviewCandidateDecisionRequired") : t("reviewWorkbenchReady");
536
- return `<section class="side-panel review-actions">
537
- <h3>${t("reviewActions")}</h3>
538
- <p>${escapeHtml(message)}</p>
539
- <p>${escapeHtml(t("lessonCandidateStatus"))}: ${tag(candidateStatus)}</p>
540
- <label class="review-check">
541
- <input type="checkbox" data-review-confirm-check="${escapeAttr(task.id)}" ${disabled ? "disabled" : ""}>
542
- <span>${t("reviewConfirmChecklist")}</span>
543
- </label>
544
- <input data-review-confirm-text="${escapeAttr(task.id)}" value="" placeholder="${escapeAttr(task.shortId || task.id)}" ${disabled ? "disabled" : ""}>
545
- <button data-review-complete="${escapeAttr(task.id)}" ${disabled ? "disabled" : ""}>${t("confirmReviewComplete")}</button>
546
- <div class="review-result" data-review-result="${escapeAttr(task.id)}"></div>
547
- </section>`;
548
- }
549
-
550
- function isTaskInReviewStage(task) {
551
- const state = task?.state || "";
552
- const lifecycle = task?.lifecycleState || "";
553
- if (["not_started", "planned", "in_progress"].includes(state)) return false;
554
- return state === "review" || ["in_review", "review-blocked"].includes(lifecycle);
555
- }
556
-
557
- function evidenceList(task) {
558
- const evidence = task.evidence || [];
559
- return `<section class="side-panel">
560
- <h3>${t("evidence")}</h3>
561
- ${evidence.map((item) => `<p><strong>${escapeHtml(item.type || "evidence")}</strong> ${escapeHtml(item.summary || "")}</p>`).join("") || `<p>${t("noEvidence")}</p>`}
562
- </section>`;
563
- }