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
@@ -28,6 +28,13 @@ function bind() {
28
28
  localStorage.setItem("harness.taskLayout", state.taskLayout);
29
29
  app();
30
30
  }));
31
+ document.querySelectorAll("[data-task-sort-order]").forEach((btn) => btn.addEventListener("click", () => {
32
+ state.taskSortOrder = btn.dataset.taskSortOrder === "asc" ? "asc" : "desc";
33
+ localStorage.setItem("harness.taskSortOrder", state.taskSortOrder);
34
+ state.taskPageByGroup = {};
35
+ state.taskGroupPage = 1;
36
+ app();
37
+ }));
31
38
  document.querySelectorAll("[data-render-toggle]").forEach((button) => button.addEventListener("click", () => {
32
39
  state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
33
40
  app();
@@ -42,11 +49,89 @@ function bind() {
42
49
  state.warningPage = 1;
43
50
  app();
44
51
  }));
52
+ document.querySelectorAll("[data-preset-search]").forEach((input) => input.addEventListener("input", () => {
53
+ state.presetQuery = input.value;
54
+ app();
55
+ }));
56
+ document.querySelectorAll("[data-preset-source-filter]").forEach((button) => button.addEventListener("click", () => {
57
+ state.presetSourceFilter = button.dataset.presetSourceFilter || "all";
58
+ state.selectedPresetKey = "";
59
+ state.presetUninstallConfirm = "";
60
+ app();
61
+ }));
62
+ document.querySelectorAll("[data-preset-select]").forEach((button) => button.addEventListener("click", () => {
63
+ state.selectedPresetKey = button.dataset.presetSelect || "";
64
+ state.selectedPresetId = "";
65
+ const selectedPreset = (bundle.presetCatalog?.presets || []).find((preset) => presetKey(preset) === state.selectedPresetKey);
66
+ if (selectedPreset && state.presetSourceFilter !== "all" && selectedPreset.source !== state.presetSourceFilter) {
67
+ state.presetSourceFilter = selectedPreset.source;
68
+ }
69
+ if (selectedPreset && !presetMatchesQuery(selectedPreset)) state.presetQuery = "";
70
+ if (selectedPreset && ["project", "user"].includes(selectedPreset.source)) state.presetUninstallScope = selectedPreset.source;
71
+ state.presetUninstallConfirm = "";
72
+ app();
73
+ }));
74
+ document.querySelectorAll("[data-preset-install-source]").forEach((input) => input.addEventListener("input", () => {
75
+ state.presetInstallSource = input.value;
76
+ }));
77
+ document.querySelectorAll("[data-preset-install-scope]").forEach((select) => select.addEventListener("change", () => {
78
+ state.presetInstallScope = select.value || "project";
79
+ }));
80
+ document.querySelectorAll("[data-preset-install-force]").forEach((input) => input.addEventListener("change", () => {
81
+ state.presetInstallForce = input.checked;
82
+ }));
83
+ document.querySelectorAll("[data-preset-seed-scope]").forEach((select) => select.addEventListener("change", () => {
84
+ state.presetSeedScope = select.value || "project";
85
+ }));
86
+ document.querySelectorAll("[data-preset-seed-force]").forEach((input) => input.addEventListener("change", () => {
87
+ state.presetSeedForce = input.checked;
88
+ }));
89
+ document.querySelectorAll("[data-preset-uninstall-scope]").forEach((select) => select.addEventListener("change", () => {
90
+ state.presetUninstallScope = select.value || "project";
91
+ }));
92
+ document.querySelectorAll("[data-preset-uninstall-confirm]").forEach((input) => input.addEventListener("input", () => {
93
+ state.presetUninstallConfirm = input.value;
94
+ }));
95
+ document.querySelectorAll("[data-preset-fill-confirm]").forEach((button) => button.addEventListener("click", () => {
96
+ state.presetUninstallConfirm = button.dataset.presetFillConfirm || "";
97
+ app();
98
+ }));
99
+ document.querySelectorAll("[data-preset-check]").forEach((button) => button.addEventListener("click", () => runPresetAction("check", { id: button.dataset.presetCheck || "" })));
100
+ document.querySelectorAll("[data-preset-install]").forEach((button) => button.addEventListener("click", () => runPresetAction("install", {
101
+ source: state.presetInstallSource,
102
+ scope: state.presetInstallScope,
103
+ force: state.presetInstallForce,
104
+ })));
105
+ document.querySelectorAll("[data-preset-seed]").forEach((button) => button.addEventListener("click", () => runPresetAction("seed", {
106
+ scope: state.presetSeedScope,
107
+ force: state.presetSeedForce,
108
+ })));
109
+ document.querySelectorAll("[data-preset-uninstall]").forEach((button) => button.addEventListener("click", () => runPresetAction("uninstall", {
110
+ id: button.dataset.presetUninstall || "",
111
+ scope: state.presetUninstallScope,
112
+ confirmText: state.presetUninstallConfirm,
113
+ })));
114
+ document.querySelectorAll("[data-review-queue-tab]").forEach((button) => button.addEventListener("click", () => {
115
+ state.reviewQueueTab = button.dataset.reviewQueueTab || "review";
116
+ state.reviewQueuePage = 1;
117
+ app();
118
+ }));
119
+ document.querySelectorAll("[data-review-reason-filter]").forEach((select) => select.addEventListener("change", () => {
120
+ state.reviewReasonFilter = select.value || "all";
121
+ state.reviewQueuePage = 1;
122
+ app();
123
+ }));
124
+ document.querySelectorAll("[data-review-sort]").forEach((select) => select.addEventListener("change", () => {
125
+ state.reviewSort = select.value || "queue";
126
+ state.reviewQueuePage = 1;
127
+ app();
128
+ }));
45
129
  document.querySelectorAll("[data-page-kind]").forEach((button) => button.addEventListener("click", () => {
46
130
  const page = Math.max(1, Number(button.dataset.page) || 1);
47
131
  if (button.dataset.pageKind === "warning") state.warningPage = page;
48
132
  if (button.dataset.pageKind === "task-groups") state.taskGroupPage = page;
49
133
  if (button.dataset.pageKind === "task") state.taskPageByGroup[button.dataset.pageGroup || ""] = page;
134
+ if (button.dataset.pageKind === "review") state.reviewQueuePage = page;
50
135
  app();
51
136
  }));
52
137
  document.querySelectorAll("[data-runway-phase]").forEach((link) => link.addEventListener("click", () => {
@@ -71,6 +156,10 @@ function bind() {
71
156
  const taskId = el.dataset.openDrawer;
72
157
  openDrawer(taskId);
73
158
  }));
159
+ bindCopyTaskNameButtons(document);
160
+ bindPresetCopyButtons(document);
161
+ bindRepairPromptButtons(document);
162
+ bindLessonSedimentationButtons(document);
74
163
  document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
75
164
  e.preventDefault();
76
165
  const lessonId = el.dataset.openLessonDrawer;
@@ -151,6 +240,45 @@ async function completeReviewFromDashboard(taskId) {
151
240
  }
152
241
  }
153
242
 
243
+ async function runPresetAction(action, body) {
244
+ state.presetActionResult = { ok: true, title: t("presetActionRunning"), message: action };
245
+ app();
246
+ try {
247
+ const response = await fetch(`/api/presets/${action}`, {
248
+ method: "POST",
249
+ headers: {
250
+ "content-type": "application/json",
251
+ "x-harness-csrf": state.runtime?.csrfToken || "",
252
+ },
253
+ body: JSON.stringify(body),
254
+ });
255
+ const payload = await response.json();
256
+ if (!response.ok) throw payload;
257
+ state.presetActionResult = {
258
+ ok: true,
259
+ title: t("presetActionSuccess"),
260
+ message: presetActionMessage(action, payload),
261
+ };
262
+ app();
263
+ if (["install", "seed", "uninstall"].includes(action)) setTimeout(() => window.location.reload(), 650);
264
+ } catch (error) {
265
+ state.presetActionResult = {
266
+ ok: false,
267
+ title: t("presetActionFailed"),
268
+ message: error?.error || error?.message || String(error || action),
269
+ };
270
+ app();
271
+ }
272
+ }
273
+
274
+ function presetActionMessage(action, payload) {
275
+ if (action === "check") return `${payload.id || ""} ${payload.status || ""}`.trim();
276
+ if (action === "install") return `${payload.id || ""} -> ${payload.scope || ""}`.trim();
277
+ if (action === "seed") return `${payload.created || 0} ${t("created")} · ${payload.skipped || 0} ${t("skipped")}`;
278
+ if (action === "uninstall") return `${payload.id || ""} ${payload.removed ? t("removed") : t("notInstalled")}`.trim();
279
+ return action;
280
+ }
281
+
154
282
  function renderDrawerContent(taskId) {
155
283
  const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
156
284
  if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
@@ -160,6 +288,7 @@ function renderDrawerContent(taskId) {
160
288
  <div>
161
289
  <h2>${escapeHtml(task.title)}</h2>
162
290
  <p style="font-family: var(--font-mono); font-size: 11px; margin: 4px 0 0; color: var(--muted);">${escapeHtml(task.id)}</p>
291
+ ${taskCopyButton(task, "detail-copy")}
163
292
  </div>
164
293
  <button class="btn-close" data-close-drawer>×</button>
165
294
  </div>
@@ -172,12 +301,16 @@ function renderDrawerContent(taskId) {
172
301
 
173
302
  const body = `
174
303
  <div class="task-drawer-body stack">
175
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; background: var(--paper-2); padding: 12px 16px; border-radius: 8px;">
176
- <div style="font-size: 24px; font-weight: 800; color: var(--accent);">${task.completion}%</div>
177
- <a href="#/tasks/${encodeURIComponent(task.id)}" class="btn-drawer-trigger" style="text-decoration: none;">${t("fullView")}</a>
304
+ <div class="drawer-task-summary">
305
+ <div>
306
+ <span>${t("statOverall")}</span>
307
+ <strong>${task.completion}%</strong>
308
+ </div>
309
+ <a href="#/tasks/${encodeURIComponent(task.id)}" class="btn-drawer-trigger">${t("fullView")}</a>
178
310
  </div>
179
311
  ${taskStateSummary(task)}
180
312
  ${reviewActionPanel(task, { mode: "summary" })}
313
+ ${lessonCandidatePanel(task, { context: "drawer" })}
181
314
  ${timeline}
182
315
  ${documents}
183
316
  ${findings}
@@ -201,18 +334,174 @@ function openDrawer(taskId) {
201
334
  state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
202
335
  openDrawer(taskId);
203
336
  }));
337
+ bindCopyTaskNameButtons(drawer);
338
+ bindRepairPromptButtons(drawer);
339
+ bindLessonSedimentationButtons(drawer);
204
340
  drawer.querySelectorAll("[data-review-complete]").forEach((button) => button.addEventListener("click", () => completeReviewFromDashboard(button.dataset.reviewComplete)));
205
341
  }
206
342
 
343
+ function bindCopyTaskNameButtons(root) {
344
+ root.querySelectorAll("[data-copy-task-name]").forEach((button) => button.addEventListener("click", async (event) => {
345
+ event.preventDefault();
346
+ event.stopPropagation();
347
+ const taskName = button.dataset.copyTaskName || "";
348
+ const defaultText = t("copyTaskNameShort");
349
+ try {
350
+ await copyText(taskName);
351
+ button.textContent = t("copyTaskNameSuccess");
352
+ } catch {
353
+ button.textContent = t("copyTaskNameFailed");
354
+ }
355
+ window.setTimeout(() => {
356
+ button.textContent = defaultText;
357
+ }, 1400);
358
+ }));
359
+ }
360
+
361
+ function bindPresetCopyButtons(root) {
362
+ root.querySelectorAll("[data-copy-preset-id]").forEach((button) => button.addEventListener("click", async (event) => {
363
+ event.preventDefault();
364
+ event.stopPropagation();
365
+ const presetId = button.dataset.copyPresetId || "";
366
+ const defaultText = button.textContent;
367
+ try {
368
+ await copyText(presetId);
369
+ button.textContent = t("copyTaskNameSuccess");
370
+ } catch {
371
+ button.textContent = t("copyTaskNameFailed");
372
+ }
373
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
374
+ }));
375
+ root.querySelectorAll("[data-copy-preset-command]").forEach((button) => button.addEventListener("click", async (event) => {
376
+ event.preventDefault();
377
+ event.stopPropagation();
378
+ const command = button.dataset.copyPresetCommand || "";
379
+ const defaultText = button.textContent;
380
+ try {
381
+ await copyText(command);
382
+ button.textContent = t("copyTaskNameSuccess");
383
+ } catch {
384
+ button.textContent = t("copyTaskNameFailed");
385
+ }
386
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
387
+ }));
388
+ }
389
+
390
+ function bindRepairPromptButtons(root) {
391
+ root.querySelectorAll("[data-copy-repair-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
392
+ event.preventDefault();
393
+ event.stopPropagation();
394
+ const prompt = button.dataset.repairPrompt || "";
395
+ const defaultText = t("copyRepairPrompt");
396
+ try {
397
+ await copyText(prompt);
398
+ button.textContent = t("copyRepairPromptSuccess");
399
+ } catch {
400
+ button.textContent = t("copyTaskNameFailed");
401
+ }
402
+ window.setTimeout(() => {
403
+ button.textContent = defaultText;
404
+ }, 1400);
405
+ }));
406
+ }
407
+
408
+ function bindLessonSedimentationButtons(root) {
409
+ root.querySelectorAll("[data-copy-lesson-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
410
+ event.preventDefault();
411
+ event.stopPropagation();
412
+ const prompt = button.dataset.lessonPrompt || "";
413
+ const defaultText = t("copyLessonPrompt");
414
+ try {
415
+ await copyText(prompt);
416
+ button.textContent = t("copyRepairPromptSuccess");
417
+ } catch {
418
+ button.textContent = t("copyTaskNameFailed");
419
+ }
420
+ window.setTimeout(() => {
421
+ button.textContent = defaultText;
422
+ }, 1400);
423
+ }));
424
+ root.querySelectorAll("[data-create-lesson-sedimentation]").forEach((button) => button.addEventListener("click", async (event) => {
425
+ event.preventDefault();
426
+ event.stopPropagation();
427
+ await createLessonSedimentationFromDashboard(button);
428
+ }));
429
+ }
430
+
431
+ async function createLessonSedimentationFromDashboard(button) {
432
+ const taskId = button.dataset.createLessonSedimentation || "";
433
+ const candidateId = button.dataset.candidateId || "";
434
+ const result = document.querySelector(`[data-lesson-result="${CSS.escape(`${taskId}:${candidateId}`)}"]`);
435
+ if (result) result.textContent = t("lessonTaskCreating");
436
+ button.disabled = true;
437
+ try {
438
+ const response = await fetch("/api/tasks/lesson-sedimentation", {
439
+ method: "POST",
440
+ headers: {
441
+ "content-type": "application/json",
442
+ "x-harness-csrf": state.runtime?.csrfToken || "",
443
+ },
444
+ body: JSON.stringify({ taskId, candidateId }),
445
+ });
446
+ const payload = await response.json();
447
+ if (!response.ok) throw payload;
448
+ if (result) {
449
+ result.innerHTML = lessonSedimentationSuccess(payload);
450
+ bindLessonSedimentationButtons(result);
451
+ result.scrollIntoView({ block: "center", inline: "nearest" });
452
+ }
453
+ } catch (error) {
454
+ button.disabled = false;
455
+ if (result) result.innerHTML = lessonSedimentationFailure(error);
456
+ }
457
+ }
458
+
459
+ function lessonSedimentationSuccess(payload) {
460
+ const followUp = payload?.followUpTask || {};
461
+ const prompt = payload?.prompt || "";
462
+ const taskId = followUp.id || "";
463
+ const openHref = taskId ? `#/tasks/${encodeURIComponent(taskId)}` : "#/review";
464
+ return `<div class="workbench-action-result success">
465
+ <strong>${escapeHtml(t("lessonTaskCreated"))}</strong>
466
+ ${taskId ? `<a href="${openHref}">${escapeHtml(t("openFollowUpTask"))}</a>` : ""}
467
+ ${prompt ? `<button data-copy-lesson-prompt="${escapeAttr(taskId || "follow-up")}" data-lesson-prompt="${escapeAttr(prompt)}">${escapeHtml(t("copyLessonPrompt"))}</button>` : ""}
468
+ </div>`;
469
+ }
470
+
471
+ function lessonSedimentationFailure(error) {
472
+ const message = error?.error || error?.message || t("lessonTaskCreateFailed");
473
+ const recovery = Array.isArray(error?.recovery) ? error.recovery : [];
474
+ const details = error?.details || {};
475
+ const existingTask = details.followUpTask || details.existingTask || "";
476
+ return `<div class="workbench-action-result failed">
477
+ <strong>${escapeHtml(t("lessonTaskCreateFailed"))}</strong>
478
+ <span>${escapeHtml(message)}</span>
479
+ ${existingTask ? `<a href="#/tasks/${encodeURIComponent(existingTask)}">${escapeHtml(t("openFollowUpTask"))}</a>` : ""}
480
+ ${recovery.length ? `<ul>${recovery.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>` : ""}
481
+ </div>`;
482
+ }
483
+
484
+ async function copyText(text) {
485
+ if (navigator.clipboard?.writeText) {
486
+ await navigator.clipboard.writeText(text);
487
+ return;
488
+ }
489
+ const textarea = document.createElement("textarea");
490
+ textarea.value = text;
491
+ textarea.setAttribute("readonly", "");
492
+ textarea.style.position = "fixed";
493
+ textarea.style.left = "-9999px";
494
+ document.body.appendChild(textarea);
495
+ textarea.select();
496
+ const copied = document.execCommand("copy");
497
+ textarea.remove();
498
+ if (!copied) throw new Error("copy failed");
499
+ }
500
+
207
501
  function renderLessonDrawerContent(lessonId) {
208
- const lessonTable = (bundle.tables?.tables || []).find((table) => table.kind === "lessons-ssot");
209
- const row = (lessonTable?.rows || []).find((r) => {
210
- const cells = r.cells || {};
211
- const id = cells.ID || cells.Lesson || cells["Lesson ID"] || cells["ID"] || "";
212
- return id === lessonId;
213
- });
214
-
215
- if (!row) {
502
+ const lesson = lessonDocuments().find((item) => item.id === lessonId);
503
+
504
+ if (!lesson) {
216
505
  return `<div class="task-drawer-header">
217
506
  <h2>${escapeHtml(lessonId)}</h2>
218
507
  <button class="btn-close" data-close-drawer>×</button>
@@ -222,23 +511,13 @@ function renderLessonDrawerContent(lessonId) {
222
511
  </div>`;
223
512
  }
224
513
 
225
- const cells = row.cells || {};
226
- const summary = cells.Summary || cells["\u6458\u8981"] || cells.Pattern || cells.Status || "";
227
- const docPath = cells["\u8be6\u60c5\u6587\u6863"] || cells.Document || cells.document || "";
228
-
229
- let doc = null;
230
- if (docPath) {
231
- doc = findDocument(docPath);
232
- }
233
- if (!doc) {
234
- doc = (bundle.documents?.documents || []).find((d) => d.path.includes(lessonId) || d.path.endsWith(`${lessonId}.md`));
235
- }
514
+ const doc = lesson.doc || findDocument(lesson.path);
236
515
 
237
516
  const header = `
238
517
  <div class="task-drawer-header">
239
518
  <div>
240
519
  <h2>${escapeHtml(lessonId)}</h2>
241
- <p style="font-size: 12px; margin: 4px 0 0; color: var(--muted); font-weight: 600;">${escapeHtml(summary)}</p>
520
+ <p style="font-size: 12px; margin: 4px 0 0; color: var(--muted); font-weight: 600;">${escapeHtml(lesson.title || lesson.path)}</p>
242
521
  </div>
243
522
  <button class="btn-close" data-close-drawer>×</button>
244
523
  </div>
@@ -248,16 +527,10 @@ function renderLessonDrawerContent(lessonId) {
248
527
  if (doc && doc.content) {
249
528
  markdownBody = `<div class="markdown">${window.HarnessMarkdown.render(doc.content, "rendered")}</div>`;
250
529
  } else {
251
- const rowsHtml = Object.entries(cells)
252
- .map(([key, val]) => `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(val)}</td></tr>`)
253
- .join("");
254
530
  markdownBody = `
255
531
  <div style="margin-bottom: 20px; background: var(--paper-2); padding: 16px; border-radius: 8px; border: 1px dashed var(--line);">
256
532
  <p style="margin: 0; font-size: 13px; color: var(--muted);">${t("lessonDocMissing")}</p>
257
533
  </div>
258
- <table class="rendered-table" style="width: 100%;">
259
- <tbody>${rowsHtml}</tbody>
260
- </table>
261
534
  `;
262
535
  }
263
536