coding-agent-harness 1.0.7 → 1.1.0

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 (238) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +9 -5
  3. package/README.md +12 -2
  4. package/README.zh-CN.md +10 -2
  5. package/SKILL.md +14 -3
  6. package/dist/build-dist.mjs +32 -6
  7. package/dist/check-dist-observation.mjs +73 -28
  8. package/dist/check-harness.mjs +0 -1
  9. package/dist/check-import-graph.mjs +44 -27
  10. package/dist/check-lite-forbidden-surfaces.mjs +121 -0
  11. package/dist/check-no-ts-nocheck.mjs +88 -0
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +67 -8
  14. package/dist/commands/dashboard-command.mjs +52 -14
  15. package/dist/commands/migration-command.mjs +18 -8
  16. package/dist/commands/module-command.mjs +142 -0
  17. package/dist/commands/preset-command.mjs +65 -4
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +111 -53
  20. package/dist/harness.mjs +6 -303
  21. package/dist/lib/capability-registry.mjs +229 -53
  22. package/dist/lib/check-module-parallel.mjs +1 -6
  23. package/dist/lib/check-profiles.mjs +39 -46
  24. package/dist/lib/check-task-contracts.mjs +6 -4
  25. package/dist/lib/command-registry.mjs +248 -0
  26. package/dist/lib/core-shared.mjs +78 -3
  27. package/dist/lib/dashboard-data.mjs +203 -22
  28. package/dist/lib/dashboard-workbench.mjs +245 -21
  29. package/dist/lib/dashboard-writer.mjs +4 -1
  30. package/dist/lib/git-status-summary.mjs +0 -1
  31. package/dist/lib/governance-index-generator.mjs +7 -5
  32. package/dist/lib/governance-sync.mjs +46 -121
  33. package/dist/lib/governance-table-boundary.mjs +1 -14
  34. package/dist/lib/harness-core.mjs +5 -1
  35. package/dist/lib/harness-paths.mjs +115 -1
  36. package/dist/lib/impact-classifier.mjs +420 -0
  37. package/dist/lib/lesson-maintenance.mjs +1 -2
  38. package/dist/lib/markdown-utils.mjs +50 -1
  39. package/dist/lib/migration-planner.mjs +31 -16
  40. package/dist/lib/migration-support.mjs +5 -4
  41. package/dist/lib/module-registry.mjs +296 -0
  42. package/dist/lib/preset-audit-contracts.mjs +24 -1
  43. package/dist/lib/preset-engine.mjs +68 -29
  44. package/dist/lib/preset-registry.mjs +374 -72
  45. package/dist/lib/preset-runner.mjs +560 -0
  46. package/dist/lib/review-confirm-git-gate.mjs +73 -19
  47. package/dist/lib/status-builder.mjs +23 -8
  48. package/dist/lib/structure-migration.mjs +6 -4
  49. package/dist/lib/subagent-authorization-audit.mjs +8 -2
  50. package/dist/lib/task-archive-eligibility.mjs +65 -0
  51. package/dist/lib/task-audit-metadata.mjs +25 -11
  52. package/dist/lib/task-audit-migration.mjs +21 -14
  53. package/dist/lib/task-discovery-contract.mjs +32 -0
  54. package/dist/lib/task-index.mjs +4 -2
  55. package/dist/lib/task-lesson-candidates.mjs +1 -2
  56. package/dist/lib/task-lesson-sedimentation.mjs +310 -9
  57. package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
  58. package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
  59. package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
  60. package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
  61. package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
  62. package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
  63. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
  64. package/dist/lib/task-lifecycle/template-files.mjs +2 -5
  65. package/dist/lib/task-lifecycle.mjs +117 -159
  66. package/dist/lib/task-metadata.mjs +10 -5
  67. package/dist/lib/task-preset-contract-drift.mjs +45 -0
  68. package/dist/lib/task-repository.mjs +192 -0
  69. package/dist/lib/task-review-model.mjs +38 -17
  70. package/dist/lib/task-scanner.mjs +75 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +187 -18
  73. package/dist/lib/types/check-profiles.js +1 -0
  74. package/dist/lib/types/impact.js +1 -0
  75. package/dist/lib/types/preset.js +1 -0
  76. package/dist/lib/types/task-lifecycle.js +1 -0
  77. package/dist/lib/types/task-scanner.js +1 -0
  78. package/dist/postinstall.mjs +2 -2
  79. package/dist/run-built-tests.mjs +10 -3
  80. package/docs-release/README.md +2 -1
  81. package/docs-release/architecture/document-contract-kernel/README.md +150 -0
  82. package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
  83. package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
  84. package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
  85. package/docs-release/architecture/overview.md +2 -2
  86. package/docs-release/architecture/overview.zh-CN.md +2 -2
  87. package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
  88. package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
  89. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
  90. package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
  91. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
  92. package/docs-release/architecture/system-explainer/README.md +1 -1
  93. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
  94. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
  95. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
  96. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  97. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
  98. package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
  99. package/docs-release/guides/agent-installation.en-US.md +4 -6
  100. package/docs-release/guides/agent-installation.md +11 -8
  101. package/docs-release/guides/contributing.md +10 -3
  102. package/docs-release/guides/contributing.zh-CN.md +10 -3
  103. package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
  104. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
  105. package/docs-release/guides/migration-playbook.en-US.md +9 -6
  106. package/docs-release/guides/migration-playbook.md +9 -6
  107. package/docs-release/guides/preset-development.md +68 -2
  108. package/docs-release/guides/task-state-machine.en-US.md +8 -8
  109. package/docs-release/guides/task-state-machine.md +7 -7
  110. package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
  111. package/package.json +19 -11
  112. package/postinstall.mjs +37 -0
  113. package/presets/legacy-migration/preset.yaml +5 -5
  114. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  115. package/presets/lesson-sedimentation/preset.yaml +3 -3
  116. package/presets/module/preset.yaml +2 -2
  117. package/presets/module/templates/execution_strategy.append.md +1 -1
  118. package/presets/module/templates/task_plan.append.md +3 -3
  119. package/presets/release-closeout/checks/check-release-package.mjs +29 -0
  120. package/presets/release-closeout/preset.yaml +100 -0
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
  122. package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
  123. package/presets/release-closeout/templates/findings.seed.md +5 -0
  124. package/presets/release-closeout/templates/review.seed.md +3 -0
  125. package/presets/release-closeout/templates/task_plan.append.md +24 -0
  126. package/presets/standard-task/preset.yaml +2 -2
  127. package/references/agents-md-pattern.md +23 -17
  128. package/references/lessons-governance.md +2 -2
  129. package/references/module-parallel-standard.md +3 -6
  130. package/references/pull-request-standard.md +2 -2
  131. package/references/ssot-governance.md +2 -2
  132. package/references/taskr-gap-analysis.md +3 -3
  133. package/run-dist.mjs +34 -0
  134. package/skills/preset-creator/SKILL.md +40 -8
  135. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  136. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  137. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  138. package/templates/AGENTS.md.template +28 -26
  139. package/templates/architecture/README.md +2 -2
  140. package/templates/architecture/service-catalog.md +2 -2
  141. package/templates/architecture/services/service-template.md +1 -1
  142. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  143. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  144. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  145. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  146. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  148. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  149. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  150. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  151. package/templates/dashboard/assets/app.css +928 -53
  152. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  153. package/templates/dashboard/assets/app.js +1071 -98
  154. package/templates/dashboard/assets/app.manifest.json +1 -0
  155. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  156. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  157. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  158. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  159. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  160. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  161. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  162. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  163. package/templates/dashboard/assets/i18n.js +166 -2
  164. package/templates/development/README.md +9 -9
  165. package/templates/development/cross-repo-debugging.md +3 -3
  166. package/templates/development/external-context/service-template.md +1 -1
  167. package/templates/development/external-source-packs/README.md +2 -2
  168. package/templates/integrations/README.md +4 -4
  169. package/templates/integrations/api-contract.md +1 -1
  170. package/templates/integrations/event-contract.md +1 -1
  171. package/templates/integrations/third-party/vendor-template.md +1 -1
  172. package/templates/integrations/webhook-contract.md +1 -1
  173. package/templates/ledger/Harness-Ledger.md +1 -1
  174. package/templates/modules/module_brief.md +50 -0
  175. package/templates/modules/module_plan.md +49 -0
  176. package/templates/modules/registry_view.md +9 -0
  177. package/templates/modules/session_prompt_pack.md +55 -0
  178. package/templates/planning/brief.md +32 -8
  179. package/templates/planning/module_brief.md +28 -3
  180. package/templates/planning/module_plan.md +26 -11
  181. package/templates/planning/module_session_prompt.md +11 -2
  182. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  183. package/templates/planning/review.md +1 -1
  184. package/templates/planning/visual_map.md +1 -1
  185. package/templates/reference/docs-library-standard.md +7 -7
  186. package/templates/reference/execution-workflow-standard.md +13 -0
  187. package/templates/reference/external-source-intake-standard.md +10 -10
  188. package/templates/reference/pull-request-standard.md +2 -2
  189. package/templates/reference/repo-governance-standard.md +1 -1
  190. package/templates/reference/review-routing-standard.md +4 -0
  191. package/templates/ssot/Module-Registry.md +4 -38
  192. package/templates/walkthrough/walkthrough-template.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +27 -25
  194. package/templates-zh-CN/CLAUDE.md.template +1 -1
  195. package/templates-zh-CN/architecture/README.md +2 -2
  196. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  197. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  198. package/templates-zh-CN/development/README.md +9 -9
  199. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  200. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  201. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  202. package/templates-zh-CN/integrations/README.md +4 -4
  203. package/templates-zh-CN/integrations/api-contract.md +1 -1
  204. package/templates-zh-CN/integrations/event-contract.md +1 -1
  205. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  206. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  207. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  208. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  209. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  210. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  211. package/templates-zh-CN/modules/module_brief.md +47 -0
  212. package/templates-zh-CN/modules/module_plan.md +48 -0
  213. package/templates-zh-CN/modules/registry_view.md +9 -0
  214. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  215. package/templates-zh-CN/planning/INDEX.md +1 -0
  216. package/templates-zh-CN/planning/brief.md +26 -7
  217. package/templates-zh-CN/planning/module_brief.md +24 -2
  218. package/templates-zh-CN/planning/module_plan.md +35 -29
  219. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  220. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  221. package/templates-zh-CN/planning/review.md +1 -1
  222. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  223. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  224. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  225. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  226. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  227. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  228. package/templates-zh-CN/reference/pull-request-standard.md +1 -1
  229. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  230. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  231. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  232. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  233. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  234. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  235. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  236. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  237. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  238. package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
@@ -21,7 +21,7 @@ function statusStrip() {
21
21
  const displayState = snapshotOnly ? "snapshot" : status;
22
22
  const failures = bundle.status?.checkState?.failures || 0;
23
23
  const warnings = bundle.status?.checkState?.warnings || 0;
24
- const tasks = bundle.status?.tasks || [];
24
+ const tasks = normalCycleTasks();
25
25
  const summary = bundle.status?.summary || {};
26
26
  const visual = summary.visualMapCoverage || {};
27
27
  const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
@@ -53,7 +53,7 @@ function nextActionText() {
53
53
  if (dataOnly && !isWorkbenchRuntime()) return t("snapshotNotValidated");
54
54
  const failures = bundle.status?.checkState?.failures || 0;
55
55
  if (failures > 0) return t("resolveBlockers");
56
- const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
56
+ const missingBriefs = normalCycleTasks().filter((task) => task.briefSource !== "standalone").length;
57
57
  if (missingBriefs > 0) return `${missingBriefs} ${t("missingBriefs")}`;
58
58
  const warnings = bundle.status?.checkState?.warnings || 0;
59
59
  if (warnings > 0) return t("reviewAdvice");
@@ -66,7 +66,7 @@ function isWorkbenchRuntime() {
66
66
  }
67
67
 
68
68
  function flowPanel() {
69
- const tasks = bundle.status?.tasks || [];
69
+ const tasks = normalCycleTasks();
70
70
  const total = tasks.length;
71
71
  if (total === 0) return "";
72
72
  const active = tasks.filter((task) => isActiveTaskState(task.state)).length;
@@ -125,14 +125,14 @@ function projectMermaid() {
125
125
 
126
126
  function usesAggregateFlow() {
127
127
  const graph = bundle.graph || { nodes: [], edges: [] };
128
- const taskCount = (bundle.status?.tasks || []).length;
128
+ const taskCount = normalCycleTasks().length;
129
129
  const taskNodes = (graph.nodes || []).filter((node) => node.type === "task").length;
130
130
  const usefulEdges = (graph.edges || []).filter((edge) => ["depends_on", "current_step"].includes(edge.type)).length;
131
131
  return taskCount > 80 || taskNodes > 80 || ((graph.nodes || []).length > 80 && usefulEdges < 6);
132
132
  }
133
133
 
134
134
  function migrationAggregateMermaid() {
135
- const tasks = bundle.status?.tasks || [];
135
+ const tasks = normalCycleTasks();
136
136
  const warnings = warningQueue();
137
137
  const activeContracts = warnings.filter((warning) => warning.phase === "active-task-contracts").length;
138
138
  const moduleCount = new Set(tasks.map(taskModuleKey)).size;
@@ -148,7 +148,7 @@ function migrationAggregateMermaid() {
148
148
  }
149
149
 
150
150
  function migrationRunwayBreakdown() {
151
- const tasks = bundle.status?.tasks || [];
151
+ const tasks = normalCycleTasks();
152
152
  const warnings = warningQueue();
153
153
  const phases = [
154
154
  ["baseline", t("runwayBaseline"), tasks.length, t("tasks"), "#/tasks"],
@@ -170,7 +170,7 @@ function mermaidFromBriefs() {
170
170
 
171
171
  function graphSummary() {
172
172
  const graph = bundle.graph || { nodes: [], edges: [] };
173
- if (usesAggregateFlow()) return `${t("aggregateMigrationView")} · ${(bundle.status?.tasks || []).length} ${t("tasks")}`;
173
+ if (usesAggregateFlow()) return `${t("aggregateMigrationView")} · ${normalCycleTasks().length} ${t("tasks")}`;
174
174
  return `${graph.nodes?.length || 0} ${t("nodes")} · ${graph.edges?.length || 0} ${t("edges")}`;
175
175
  }
176
176
 
@@ -194,7 +194,7 @@ function activeTaskBriefs() {
194
194
  }
195
195
 
196
196
  function activeTasks() {
197
- const tasks = bundle.status?.tasks || [];
197
+ const tasks = normalCycleTasks();
198
198
  const active = tasks.filter((task) => isActiveTaskState(task.state) || ["planned", "not_started"].includes(task.state));
199
199
  if (active.length > 0) return sortTasksByTime(active);
200
200
  return sortTasksByTime(tasks.filter((task) => task.briefSource === "standalone"));
@@ -8,6 +8,22 @@ function stateToColorVar(state) {
8
8
  return map[state] || "--muted";
9
9
  }
10
10
 
11
+ function taskStatRows(tasks) {
12
+ return [
13
+ { state: "in_progress", label: t("statInProgress"), className: "in-progress" },
14
+ { state: "review", label: t("statReview"), className: "review" },
15
+ { state: "blocked", label: t("statBlocked"), className: "blocked" },
16
+ { state: "done", label: t("statDone"), className: "done" },
17
+ { state: "planned", label: label("planned"), className: "planned" },
18
+ { state: "not_started", label: label("not_started"), className: "not-started" },
19
+ { state: "unknown", label: label("unknown"), className: "unknown" },
20
+ ].map((row) => ({
21
+ ...row,
22
+ count: tasks.filter((task) => task.state === row.state).length,
23
+ colorVar: stateToColorVar(row.state),
24
+ })).filter((row) => row.count > 0);
25
+ }
26
+
11
27
  function taskSortLabel() {
12
28
  return state.taskSortOrder === "asc" ? t("sortOldest") : t("sortNewest");
13
29
  }
@@ -42,6 +58,23 @@ function sortTasksByTime(tasks) {
42
58
  return [...tasks].sort(compareTasksByTime);
43
59
  }
44
60
 
61
+ function isArchivedTask(task) {
62
+ const archiveState = String(task?.archiveMetadata?.state || "").toLowerCase();
63
+ return task?.deletionState === "archived" || archiveState === "archived";
64
+ }
65
+
66
+ function normalCycleTasks() {
67
+ return (bundle.status?.tasks || []).filter((task) => !isArchivedTask(task));
68
+ }
69
+
70
+ function archivedTasks() {
71
+ return (bundle.status?.tasks || []).filter(isArchivedTask);
72
+ }
73
+
74
+ function archiveBucket(task) {
75
+ return task?.archiveMetadata?.["retention bucket"] || task?.archiveMetadata?.["Retention Bucket"] || t("archiveUnclassified");
76
+ }
77
+
45
78
  function taskFolderName(task) {
46
79
  const fromPath = String(task?.path || "").split("/").filter(Boolean).pop();
47
80
  const fromId = String(task?.id || "").split("/").filter(Boolean).pop();
@@ -70,7 +103,7 @@ function taskToolbarCard(filteredCount) {
70
103
  <div class="select-group">
71
104
  <label>${t("stateFilter")}</label>
72
105
  <select data-state-filter aria-label="${t("stateFilter")}">
73
- ${["all", "in_progress", "review", "blocked", "planned", "done", "unknown"].map((value) => `<option value="${value}" ${state.taskState === value ? "selected" : ""}>${label(value)}</option>`).join("")}
106
+ ${["all", "in_progress", "review", "blocked", "planned", "not_started", "done", "unknown"].map((value) => `<option value="${value}" ${state.taskState === value ? "selected" : ""}>${label(value)}</option>`).join("")}
74
107
  </select>
75
108
  </div>
76
109
  <div class="select-group">
@@ -90,6 +123,10 @@ function taskToolbarCard(filteredCount) {
90
123
  <svg style="width:12px;height:12px;vertical-align:middle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
91
124
  ${t("layoutGrid")}
92
125
  </button>
126
+ <button class="layout-btn ${state.taskLayout === "swimlane" ? "active" : ""}" data-layout="swimlane" aria-label="${t("layoutSwimlane")}">
127
+ <svg style="width:12px;height:12px;vertical-align:middle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/><path d="M8 4v16"/><path d="M16 4v16"/></svg>
128
+ ${t("layoutSwimlane")}
129
+ </button>
93
130
  </div>
94
131
  </div>
95
132
  <div class="select-group">
@@ -104,13 +141,13 @@ function taskToolbarCard(filteredCount) {
104
141
  </div>
105
142
  </div>
106
143
  <div class="search-stats">
107
- ${t("showing")} <strong>${filteredCount}</strong> / ${(bundle.status?.tasks || []).length} ${t("tasks")}
144
+ ${t("showing")} <strong>${filteredCount}</strong> / ${normalCycleTasks().length} ${t("tasks")}
108
145
  </div>
109
146
  </section>`;
110
147
  }
111
148
 
112
149
  function taskStatsCard() {
113
- const allTasks = bundle.status?.tasks || [];
150
+ const allTasks = normalCycleTasks();
114
151
  const avgCompletion = allTasks.length ? clampCompletion(allTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / allTasks.length) : 0;
115
152
  return `<section class="sidebar-card">
116
153
  <h3>${t("releaseHealth")}</h3>
@@ -119,13 +156,7 @@ function taskStatsCard() {
119
156
  <span class="gauge-label">${t("statOverall")}</span>
120
157
  </div>
121
158
  <div class="stats-breakdown">
122
- ${[
123
- { state: "in_progress", label: t("statInProgress"), colorVar: "--accent" },
124
- { state: "review", label: t("statReview"), colorVar: "--accent-2" },
125
- { state: "blocked", label: t("statBlocked"), colorVar: "--danger" },
126
- { state: "done", label: t("statDone"), colorVar: "--ok" }
127
- ].map(({ state, label, colorVar }) => {
128
- const count = allTasks.filter(t => t.state === state).length;
159
+ ${taskStatRows(allTasks).map(({ label, colorVar, count }) => {
129
160
  return `<div class="stats-breakdown-row">
130
161
  <span class="stat-label">
131
162
  <span class="state-dot" style="background:var(${colorVar})"></span>
@@ -161,11 +192,7 @@ function taskLegendCard() {
161
192
  }
162
193
 
163
194
  function taskStatsBar() {
164
- const allTasks = bundle.status?.tasks || [];
165
- const inProgress = allTasks.filter(t => t.state === "in_progress").length;
166
- const blocked = allTasks.filter(t => t.state === "blocked").length;
167
- const done = allTasks.filter(t => t.state === "done").length;
168
- const review = allTasks.filter(t => t.state === "review").length;
195
+ const allTasks = normalCycleTasks();
169
196
  const avgCompletion = allTasks.length ? clampCompletion(allTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / allTasks.length) : 0;
170
197
 
171
198
  return `<section class="task-stats-bar">
@@ -173,22 +200,10 @@ function taskStatsBar() {
173
200
  <span class="stat-value">${allTasks.length}</span>
174
201
  <span class="stat-label">${t("statTotal")}</span>
175
202
  </div>
176
- <div class="stat-chip in-progress">
177
- <span class="stat-value">${inProgress}</span>
178
- <span class="stat-label">${t("statInProgress")}</span>
179
- </div>
180
- <div class="stat-chip review">
181
- <span class="stat-value">${review}</span>
182
- <span class="stat-label">${t("statReview")}</span>
183
- </div>
184
- <div class="stat-chip blocked">
185
- <span class="stat-value">${blocked}</span>
186
- <span class="stat-label">${t("statBlocked")}</span>
187
- </div>
188
- <div class="stat-chip done">
189
- <span class="stat-value">${done}</span>
190
- <span class="stat-label">${t("statDone")}</span>
191
- </div>
203
+ ${taskStatRows(allTasks).map((row) => `<div class="stat-chip ${escapeAttr(row.className)}" style="--stat-color: var(${row.colorVar})">
204
+ <span class="stat-value">${row.count}</span>
205
+ <span class="stat-label">${escapeHtml(row.label)}</span>
206
+ </div>`).join("")}
192
207
  <div class="stat-chip completion">
193
208
  <div class="stat-bar-track"><div class="stat-bar-fill" style="width:${avgCompletion}%"></div></div>
194
209
  <div style="text-align:right">
@@ -205,12 +220,14 @@ function taskRow(task) {
205
220
  const mapReady = !!taskDocument(task, "visual_map.md");
206
221
  const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
207
222
  const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
223
+ const moduleLabel = taskModuleLabel(task);
224
+ const lifecycle = [task.lifecycleState, task.reviewStatus, task.closeoutStatus].filter(Boolean).map((item) => label(item)).join(" · ");
208
225
 
209
226
  return `<article class="task-row-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateToColorVar(task.state)})">
210
227
  <div class="row-accent-bar"></div>
211
228
  <div class="row-main">
212
229
  <strong>${escapeHtml(task.title)}</strong>
213
- <span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(taskModuleKey(task))}</span>
230
+ <span class="row-meta">${escapeHtml(task.id)} · ${escapeHtml(moduleLabel)}${lifecycle ? ` · ${escapeHtml(lifecycle)}` : ""}</span>
214
231
  ${taskCopyButton(task, "row-copy")}
215
232
  </div>
216
233
  <div class="row-status">${tag(task.state)}</div>
@@ -240,15 +257,16 @@ function taskIndex() {
240
257
  const groupPageCount = Math.max(1, Math.ceil(orderedGroups.length / taskGroupsPerPage));
241
258
  const groupPage = Math.min(Math.max(1, Number(state.taskGroupPage) || 1), groupPageCount);
242
259
  const visibleGroups = orderedGroups.slice((groupPage - 1) * taskGroupsPerPage, groupPage * taskGroupsPerPage);
260
+ const swimlane = state.taskLayout === "swimlane";
243
261
 
244
262
  return `<div class="tasks-grid">
245
263
  <div class="tasks-main stack">
246
264
  ${taskStatsBar()}
247
- ${visibleGroups.map(([group, groupTasks]) => taskGroup(group, groupTasks)).join("")}
248
- <section class="group-pager">
265
+ ${swimlane ? taskSwimlane(tasks) : visibleGroups.map(([group, groupTasks]) => taskGroup(group, groupTasks)).join("")}
266
+ ${swimlane ? "" : `<section class="group-pager">
249
267
  <span>${t("showingGroups")} ${visibleGroups.length ? (groupPage - 1) * taskGroupsPerPage + 1 : 0}-${Math.min(groupPage * taskGroupsPerPage, orderedGroups.length)} / ${orderedGroups.length}</span>
250
268
  ${pager("task-groups", groupPage, groupPageCount)}
251
- </section>
269
+ </section>`}
252
270
  </div>
253
271
  <aside class="tasks-sidebar stack">
254
272
  ${taskToolbarCard(tasks.length)}
@@ -311,6 +329,7 @@ function taskGroup(group, tasks) {
311
329
  const start = (page - 1) * taskPageSize;
312
330
  const visibleTasks = orderedTasks.slice(start, start + taskPageSize);
313
331
  const avgCompletion = orderedTasks.length ? clampCompletion(orderedTasks.reduce((sum, task) => sum + clampCompletion(task.completion), 0) / orderedTasks.length) : 0;
332
+ const groupContext = taskGroupContext(group, orderedTasks);
314
333
 
315
334
  const isGrid = state.taskLayout === "grid";
316
335
  const layoutClass = isGrid ? "task-card-grid" : "task-list";
@@ -326,8 +345,10 @@ function taskGroup(group, tasks) {
326
345
  return `<section class="task-group">
327
346
  <div class="section-head">
328
347
  <div>
329
- <h2>${taskGroupLabel(group)}</h2>
330
- <p class="subtle">${t("showing")} ${Math.min(start + 1, orderedTasks.length)}-${Math.min(start + visibleTasks.length, orderedTasks.length)} / ${orderedTasks.length}</p>
348
+ <p class="eyebrow">${escapeHtml(groupContext.eyebrow)}</p>
349
+ <h2>${escapeHtml(groupContext.title)}</h2>
350
+ <p class="subtle">${escapeHtml(groupContext.summary)} · ${t("showing")} ${Math.min(start + 1, orderedTasks.length)}-${Math.min(start + visibleTasks.length, orderedTasks.length)} / ${orderedTasks.length}</p>
351
+ ${groupContext.chips.length ? `<div class="module-chip-row">${groupContext.chips.map((chip) => `<span class="module-chip">${escapeHtml(chip)}</span>`).join("")}</div>` : ""}
331
352
  </div>
332
353
  <div class="group-actions">
333
354
  <div class="group-progress" aria-label="${escapeAttr(t("groupCompletion"))}">
@@ -351,6 +372,7 @@ function taskCard(task) {
351
372
  const mapReady = !!taskDocument(task, "visual_map.md");
352
373
  const briefLabel = briefReady ? t("briefReady") : t("briefMissing");
353
374
  const mapLabel = mapReady ? t("mapReady") : t("mapMissing");
375
+ const lifecycle = [task.lifecycleState, task.reviewStatus, task.closeoutStatus].filter(Boolean).map((item) => label(item)).join(" · ");
354
376
 
355
377
  return `<article class="task-card" data-open-drawer="${escapeAttr(task.id)}" style="--row-accent: var(${stateColor})">
356
378
  <div class="card-header">
@@ -364,8 +386,9 @@ function taskCard(task) {
364
386
  <div class="card-meta">
365
387
  <span class="meta-module" title="${escapeAttr(taskModuleKey(task))}">
366
388
  <svg style="width:12px;height:12px;vertical-align:middle;margin-right:2px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
367
- ${escapeHtml(taskModuleKey(task))}
389
+ ${escapeHtml(taskModuleLabel(task))}
368
390
  </span>
391
+ ${lifecycle ? `<span class="meta-lifecycle" title="${escapeAttr(lifecycle)}">${escapeHtml(lifecycle)}</span>` : ""}
369
392
  </div>
370
393
  <div class="card-progress">
371
394
  <div class="card-progress-track"><div class="card-progress-fill" style="width:${completion}%"></div></div>
@@ -388,7 +411,7 @@ function taskGroupLabel(group) {
388
411
  if (group === "active") return t("activeCurrent");
389
412
  if (group === "brief-ready") return t("briefReadyGroup");
390
413
  if (group.startsWith("legacy:")) return `${t("legacyMonth")} ${group.slice("legacy:".length)}`;
391
- if (group.startsWith("module:")) return `${t("inferredModule")} · ${group.slice("module:".length)}`;
414
+ if (group.startsWith("module:")) return taskGroupContext(group, []).title;
392
415
  if (group.startsWith("month:")) return `${t("legacyMonth")} ${group.slice("month:".length)}`;
393
416
  if (group.startsWith("state:")) return `${t("columnState")} · ${label(group.slice("state:".length))}`;
394
417
  return label(group);
@@ -396,7 +419,7 @@ function taskGroupLabel(group) {
396
419
 
397
420
  function filteredTasks() {
398
421
  const query = state.query.trim().toLowerCase();
399
- return sortTasksByTime((bundle.status?.tasks || []).filter((task) => {
422
+ return sortTasksByTime(normalCycleTasks().filter((task) => {
400
423
  const stateMatch = state.taskState === "all" || task.state === state.taskState;
401
424
  if (!stateMatch) return false;
402
425
  if (!query) return true;
@@ -407,3 +430,72 @@ function filteredTasks() {
407
430
  function taskModuleKey(task) {
408
431
  return task.module || task.inferredModule || "legacy-unclassified";
409
432
  }
433
+
434
+ function taskModuleDisplayLabel(key) {
435
+ if (key === "base") return t("baseModule");
436
+ if (key === "legacy-unclassified") return t("unclassifiedModule");
437
+ return key;
438
+ }
439
+
440
+ function archiveView() {
441
+ const tasks = sortTasksByTime(archivedTasks());
442
+ const groups = Object.entries(groupBy(tasks, archiveBucket)).sort(([left], [right]) => left.localeCompare(right));
443
+ return `<main class="stack archive-view">
444
+ <section class="flow-panel">
445
+ <div class="section-head">
446
+ <div>
447
+ <p class="eyebrow">${t("archive")}</p>
448
+ <h2>${t("archiveView")}</h2>
449
+ <p class="subtle">${t("archiveSubtitle")}</p>
450
+ </div>
451
+ <a href="#/tasks">${t("openTaskIndex")}</a>
452
+ </div>
453
+ <div class="archive-summary-grid">
454
+ ${metric(t("archivedTasks"), tasks.length)}
455
+ ${metric(t("archiveBuckets"), groups.length)}
456
+ </div>
457
+ </section>
458
+ ${groups.map(([bucket, bucketTasks]) => archiveGroup(bucket, bucketTasks)).join("") || emptyState(t("noArchivedTasks"))}
459
+ </main>`;
460
+ }
461
+
462
+ function archiveGroup(bucket, tasks) {
463
+ const orderedTasks = sortTasksByTime(tasks);
464
+ return `<section class="archive-group">
465
+ <div class="section-head">
466
+ <div>
467
+ <h2>${escapeHtml(bucket)}</h2>
468
+ <p class="subtle">${orderedTasks.length} ${t("tasks")}</p>
469
+ </div>
470
+ </div>
471
+ <div class="archive-task-list">
472
+ ${orderedTasks.map(archiveTaskRow).join("")}
473
+ </div>
474
+ </section>`;
475
+ }
476
+
477
+ function archiveTaskRow(task) {
478
+ const archiveMetadata = task.archiveMetadata || {};
479
+ const archivedBy = archiveMetadata?.["archived by"] || t("unknown");
480
+ const archivedAt = archiveMetadata?.["archived at"] || "";
481
+ const reviewConfirmedBy = archiveMetadata?.["review confirmed by"] || t("unknown");
482
+ const reviewConfirmedAt = archiveMetadata?.["review confirmed at"] || "";
483
+ const reviewConfirmationId = archiveMetadata?.["review confirmation id"] || "";
484
+ const releasePackage = archiveMetadata?.["release package"] || "";
485
+ const reason = task.deleteReason || archiveMetadata?.reason || "";
486
+ return `<article class="archive-task-row">
487
+ <div class="archive-task-main">
488
+ <a href="#/tasks/${encodeURIComponent(task.id)}">${escapeHtml(task.title || task.id)}</a>
489
+ <span>${escapeHtml(task.id)}</span>
490
+ ${reason ? `<p>${escapeHtml(reason)}</p>` : ""}
491
+ </div>
492
+ <dl class="archive-meta-grid">
493
+ <div><dt>${t("archivedBy")}</dt><dd>${escapeHtml(archivedBy)}</dd></div>
494
+ <div><dt>${t("archivedAt")}</dt><dd>${escapeHtml(archivedAt || t("unknown"))}</dd></div>
495
+ <div><dt>${t("reviewConfirmedBy")}</dt><dd>${escapeHtml(reviewConfirmedBy)}</dd></div>
496
+ <div><dt>${t("reviewConfirmedAt")}</dt><dd>${escapeHtml(reviewConfirmedAt || t("unknown"))}</dd></div>
497
+ ${reviewConfirmationId ? `<div><dt>${t("reviewConfirmationId")}</dt><dd>${escapeHtml(reviewConfirmationId)}</dd></div>` : ""}
498
+ ${releasePackage ? `<div><dt>${t("releasePackage")}</dt><dd>${escapeHtml(releasePackage)}</dd></div>` : ""}
499
+ </dl>
500
+ </article>`;
501
+ }