coding-agent-harness 1.0.1 → 1.0.2

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 (159) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.en-US.md +14 -0
  3. package/README.md +111 -86
  4. package/README.zh-CN.md +270 -0
  5. package/SKILL.md +116 -189
  6. package/docs-release/README.md +72 -5
  7. package/docs-release/architecture/overview.md +286 -28
  8. package/docs-release/architecture/overview.zh-CN.md +288 -0
  9. package/docs-release/assets/dashboard-overview-en.png +0 -0
  10. package/docs-release/assets/harness-architecture.svg +163 -0
  11. package/docs-release/assets/harness-workflow.svg +64 -0
  12. package/docs-release/guides/agent-installation.en-US.md +214 -0
  13. package/docs-release/guides/agent-installation.md +123 -26
  14. package/docs-release/guides/document-audience-and-surfaces.en-US.md +112 -0
  15. package/docs-release/guides/document-audience-and-surfaces.md +112 -0
  16. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +334 -0
  17. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +334 -0
  18. package/docs-release/guides/legacy-migration-agent-prompt.md +384 -0
  19. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +361 -0
  20. package/docs-release/guides/migration-playbook.en-US.md +325 -0
  21. package/docs-release/guides/migration-playbook.md +329 -0
  22. package/docs-release/guides/parent-control-repository-pattern.en-US.md +252 -0
  23. package/docs-release/guides/parent-control-repository-pattern.md +252 -0
  24. package/docs-release/guides/repository-operating-models.en-US.md +196 -0
  25. package/docs-release/guides/repository-operating-models.md +196 -0
  26. package/docs-release/intl/README.md +15 -0
  27. package/docs-release/intl/de-DE.md +18 -0
  28. package/docs-release/intl/en-US.md +18 -0
  29. package/docs-release/intl/es-ES.md +18 -0
  30. package/docs-release/intl/fr-FR.md +18 -0
  31. package/docs-release/intl/ja-JP.md +18 -0
  32. package/docs-release/intl/ko-KR.md +18 -0
  33. package/docs-release/intl/zh-CN.md +18 -0
  34. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/brief.md +13 -0
  35. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/lesson_candidates.md +24 -0
  36. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +1 -1
  37. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +4 -2
  38. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/{visual_roadmap.md → visual_map.md} +9 -1
  39. package/package.json +3 -1
  40. package/references/agents-md-pattern.md +3 -3
  41. package/references/docs-directory-standard.md +47 -3
  42. package/references/external-source-intake-standard.md +75 -0
  43. package/references/harness-ledger.md +5 -3
  44. package/references/legacy-12-phase-bootstrap.md +41 -0
  45. package/references/lessons-governance.md +23 -6
  46. package/references/planning-loop.md +41 -3
  47. package/references/project-onboarding-audit.md +10 -0
  48. package/references/repo-governance-standard.md +2 -0
  49. package/references/testing-standard.md +50 -0
  50. package/references/walkthrough-closeout.md +6 -5
  51. package/scripts/check-harness.mjs +76 -35
  52. package/scripts/harness.mjs +303 -12
  53. package/scripts/lib/capability-registry.mjs +533 -0
  54. package/scripts/lib/check-profiles.mjs +510 -0
  55. package/scripts/lib/core-shared.mjs +186 -0
  56. package/scripts/lib/dashboard-data.mjs +389 -0
  57. package/scripts/lib/dashboard-workbench.mjs +217 -0
  58. package/scripts/lib/dashboard-writer.mjs +93 -2
  59. package/scripts/lib/harness-core.mjs +10 -1318
  60. package/scripts/lib/lesson-maintenance.mjs +145 -0
  61. package/scripts/lib/markdown-utils.mjs +158 -0
  62. package/scripts/lib/migration-planner.mjs +478 -0
  63. package/scripts/lib/migration-support.mjs +312 -0
  64. package/scripts/lib/task-lifecycle.mjs +755 -0
  65. package/scripts/lib/task-scanner.mjs +682 -0
  66. package/scripts/smoke-dashboard.mjs +22 -0
  67. package/scripts/test-harness.mjs +926 -14
  68. package/templates/AGENTS.md.template +41 -30
  69. package/templates/architecture/Architecture-SSoT.md +21 -0
  70. package/templates/architecture/README.md +49 -0
  71. package/templates/architecture/critical-flows.md +22 -0
  72. package/templates/architecture/local-repo-context.md +20 -0
  73. package/templates/architecture/service-catalog.md +17 -0
  74. package/templates/architecture/services/service-template.md +31 -0
  75. package/templates/architecture/system-map.md +22 -0
  76. package/templates/dashboard/assets/app-src/00-state.js +41 -0
  77. package/templates/dashboard/assets/app-src/10-router.js +76 -0
  78. package/templates/dashboard/assets/app-src/20-overview.js +235 -0
  79. package/templates/dashboard/assets/app-src/30-tasks.js +563 -0
  80. package/templates/dashboard/assets/app-src/40-modules.js +58 -0
  81. package/templates/dashboard/assets/app-src/45-review.js +128 -0
  82. package/templates/dashboard/assets/app-src/50-migration.js +169 -0
  83. package/templates/dashboard/assets/app-src/60-shared.js +61 -0
  84. package/templates/dashboard/assets/app-src/90-bindings.js +382 -0
  85. package/templates/dashboard/assets/app.css +2575 -310
  86. package/templates/dashboard/assets/app.js +1498 -307
  87. package/templates/dashboard/assets/app.manifest.json +11 -0
  88. package/templates/dashboard/assets/i18n.js +429 -44
  89. package/templates/dashboard/assets/mermaid-renderer.js +58 -8
  90. package/templates/development/README.md +52 -0
  91. package/templates/development/codebase-map.md +11 -0
  92. package/templates/development/cross-repo-debugging.md +18 -0
  93. package/templates/development/external-context/service-template.md +33 -0
  94. package/templates/development/external-source-packs/README.md +24 -0
  95. package/templates/development/external-source-packs/digest-template.md +28 -0
  96. package/templates/development/local-setup.md +16 -0
  97. package/templates/development/stubs-and-mocks.md +11 -0
  98. package/templates/integrations/README.md +40 -0
  99. package/templates/integrations/api-contract.md +42 -0
  100. package/templates/integrations/event-contract.md +46 -0
  101. package/templates/integrations/third-party/vendor-template.md +42 -0
  102. package/templates/integrations/webhook-contract.md +41 -0
  103. package/templates/planning/brief.md +32 -0
  104. package/templates/planning/lesson_candidates.md +58 -0
  105. package/templates/planning/long-running-task-contract.md +7 -0
  106. package/templates/planning/module_brief.md +25 -0
  107. package/templates/planning/module_session_prompt.md +6 -0
  108. package/templates/planning/task_plan.md +7 -5
  109. package/templates/planning/{visual_roadmap.md → visual_map.md} +24 -2
  110. package/templates/reference/docs-library-standard.md +31 -0
  111. package/templates/reference/execution-workflow-standard.md +4 -2
  112. package/templates/reference/external-source-intake-standard.md +82 -0
  113. package/templates/reference/harness-ledger-standard.md +1 -0
  114. package/templates/reference/repo-governance-standard.md +6 -4
  115. package/templates/reference/walkthrough-standard.md +2 -1
  116. package/templates/walkthrough/walkthrough-template.md +2 -2
  117. package/templates-zh-CN/AGENTS.md.template +69 -70
  118. package/templates-zh-CN/architecture/Architecture-SSoT.md +21 -0
  119. package/templates-zh-CN/architecture/README.md +51 -0
  120. package/templates-zh-CN/architecture/critical-flows.md +24 -0
  121. package/templates-zh-CN/architecture/local-repo-context.md +20 -0
  122. package/templates-zh-CN/architecture/service-catalog.md +17 -0
  123. package/templates-zh-CN/architecture/services/service-template.md +31 -0
  124. package/templates-zh-CN/architecture/system-map.md +22 -0
  125. package/templates-zh-CN/development/README.md +54 -0
  126. package/templates-zh-CN/development/codebase-map.md +11 -0
  127. package/templates-zh-CN/development/cross-repo-debugging.md +18 -0
  128. package/templates-zh-CN/development/external-context/service-template.md +33 -0
  129. package/templates-zh-CN/development/external-source-packs/README.md +24 -0
  130. package/templates-zh-CN/development/external-source-packs/digest-template.md +28 -0
  131. package/templates-zh-CN/development/local-setup.md +16 -0
  132. package/templates-zh-CN/development/stubs-and-mocks.md +11 -0
  133. package/templates-zh-CN/integrations/README.md +42 -0
  134. package/templates-zh-CN/integrations/api-contract.md +42 -0
  135. package/templates-zh-CN/integrations/event-contract.md +46 -0
  136. package/templates-zh-CN/integrations/third-party/vendor-template.md +42 -0
  137. package/templates-zh-CN/integrations/webhook-contract.md +41 -0
  138. package/templates-zh-CN/planning/brief.md +32 -0
  139. package/templates-zh-CN/planning/lesson_candidates.md +58 -0
  140. package/templates-zh-CN/planning/long-running-task-contract.md +1 -1
  141. package/templates-zh-CN/planning/module_brief.md +25 -0
  142. package/templates-zh-CN/planning/module_plan.md +2 -2
  143. package/templates-zh-CN/planning/module_session_prompt.md +4 -3
  144. package/templates-zh-CN/planning/task_plan.md +10 -4
  145. package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
  146. package/templates-zh-CN/reference/docs-library-standard.md +35 -0
  147. package/templates-zh-CN/reference/execution-workflow-standard.md +9 -2
  148. package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
  149. package/templates-zh-CN/reference/harness-ledger-standard.md +5 -2
  150. package/templates-zh-CN/reference/repo-governance-standard.md +2 -0
  151. package/templates-zh-CN/reference/walkthrough-standard.md +4 -4
  152. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
  153. package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
  154. package/templates-zh-CN/dashboard/assets/app.css +0 -399
  155. package/templates-zh-CN/dashboard/assets/app.js +0 -435
  156. package/templates-zh-CN/dashboard/assets/i18n.js +0 -47
  157. package/templates-zh-CN/dashboard/assets/markdown-reader.js +0 -116
  158. package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +0 -59
  159. package/templates-zh-CN/dashboard/index.html +0 -18
@@ -0,0 +1,382 @@
1
+ window.setModulePage = function(moduleKey, page) {
2
+ state.modulePages = state.modulePages || {};
3
+ state.modulePages[moduleKey] = page;
4
+ app();
5
+ };
6
+
7
+ function bind() {
8
+ document.querySelectorAll("[data-search]").forEach((input) => input.addEventListener("input", () => {
9
+ state.query = input.value;
10
+ state.taskPageByGroup = {};
11
+ state.taskGroupPage = 1;
12
+ app();
13
+ }));
14
+ document.querySelectorAll("[data-state-filter]").forEach((select) => select.addEventListener("change", () => {
15
+ state.taskState = select.value;
16
+ state.taskPageByGroup = {};
17
+ state.taskGroupPage = 1;
18
+ app();
19
+ }));
20
+ document.querySelectorAll("[data-group-mode]").forEach((select) => select.addEventListener("change", () => {
21
+ state.taskGroupMode = select.value;
22
+ state.taskPageByGroup = {};
23
+ state.taskGroupPage = 1;
24
+ app();
25
+ }));
26
+ document.querySelectorAll("[data-layout]").forEach((btn) => btn.addEventListener("click", () => {
27
+ state.taskLayout = btn.dataset.layout;
28
+ localStorage.setItem("harness.taskLayout", state.taskLayout);
29
+ app();
30
+ }));
31
+ document.querySelectorAll("[data-render-toggle]").forEach((button) => button.addEventListener("click", () => {
32
+ state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
33
+ app();
34
+ }));
35
+ document.querySelectorAll("[data-warning-filter]").forEach((button) => button.addEventListener("click", () => {
36
+ state.warningFilter = button.dataset.warningFilter || "all";
37
+ state.warningPage = 1;
38
+ app();
39
+ }));
40
+ document.querySelectorAll("[data-warning-filter-select]").forEach((select) => select.addEventListener("change", () => {
41
+ state.warningFilter = select.value;
42
+ state.warningPage = 1;
43
+ app();
44
+ }));
45
+ document.querySelectorAll("[data-page-kind]").forEach((button) => button.addEventListener("click", () => {
46
+ const page = Math.max(1, Number(button.dataset.page) || 1);
47
+ if (button.dataset.pageKind === "warning") state.warningPage = page;
48
+ if (button.dataset.pageKind === "task-groups") state.taskGroupPage = page;
49
+ if (button.dataset.pageKind === "task") state.taskPageByGroup[button.dataset.pageGroup || ""] = page;
50
+ app();
51
+ }));
52
+ document.querySelectorAll("[data-runway-phase]").forEach((link) => link.addEventListener("click", () => {
53
+ const phase = link.dataset.runwayPhase || "all";
54
+ if (phase === "module-classification") state.taskGroupMode = "module";
55
+ if (["triage", "active-task-contracts", "strict-cutover"].includes(phase)) state.warningFilter = phase === "triage" ? "all" : phase;
56
+ state.warningPage = 1;
57
+ state.taskGroupPage = 1;
58
+ if (link.getAttribute("href") === "#/") app();
59
+ }));
60
+ document.querySelectorAll("[data-theme-toggle]").forEach((button) => button.addEventListener("click", () => {
61
+ state.theme = state.theme === "dark" ? "light" : state.theme === "light" ? "system" : "dark";
62
+ localStorage.setItem("harness.theme", state.theme);
63
+ app();
64
+ }));
65
+ document.querySelectorAll("[data-language-toggle]").forEach((button) => button.addEventListener("click", () => {
66
+ setLocale(locale === "zh" ? "en" : "zh");
67
+ app();
68
+ }));
69
+ document.querySelectorAll("[data-open-drawer]").forEach((el) => el.addEventListener("click", (e) => {
70
+ e.preventDefault();
71
+ const taskId = el.dataset.openDrawer;
72
+ openDrawer(taskId);
73
+ }));
74
+ document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
75
+ e.preventDefault();
76
+ const lessonId = el.dataset.openLessonDrawer;
77
+ openLessonDrawer(lessonId);
78
+ }));
79
+ document.querySelectorAll("[data-review-complete]").forEach((button) => button.addEventListener("click", () => completeReviewFromDashboard(button.dataset.reviewComplete)));
80
+ const overlay = document.getElementById("drawer-overlay");
81
+ if (overlay) overlay.addEventListener("click", closeDrawer);
82
+ }
83
+
84
+ async function loadRuntime() {
85
+ if (state.runtimeLoaded || window.__HARNESS_WORKBENCH__ !== true || !/^https?:$/.test(window.location.protocol)) return;
86
+ state.runtimeLoaded = true;
87
+ try {
88
+ const response = await fetch("/api/runtime", { cache: "no-store" });
89
+ if (!response.ok) return;
90
+ state.runtime = await response.json();
91
+ startRuntimePolling();
92
+ app();
93
+ } catch {
94
+ state.runtime = { mode: "static", csrfToken: "", writableActions: [] };
95
+ }
96
+ }
97
+
98
+ function startRuntimePolling() {
99
+ if (!state.runtime?.autoRefresh || state.runtimePoller) return;
100
+ state.runtimePoller = setInterval(async () => {
101
+ try {
102
+ const response = await fetch("/api/runtime", { cache: "no-store" });
103
+ if (!response.ok) return;
104
+ const nextRuntime = await response.json();
105
+ if (state.runtime?.snapshotVersion && nextRuntime.snapshotVersion !== state.runtime.snapshotVersion) {
106
+ window.location.reload();
107
+ return;
108
+ }
109
+ state.runtime = nextRuntime;
110
+ } catch {
111
+ clearInterval(state.runtimePoller);
112
+ state.runtimePoller = null;
113
+ }
114
+ }, 1500);
115
+ }
116
+
117
+ async function completeReviewFromDashboard(taskId) {
118
+ const result = document.querySelector(`[data-review-result="${CSS.escape(taskId)}"]`);
119
+ const checkbox = document.querySelector(`[data-review-confirm-check="${CSS.escape(taskId)}"]`);
120
+ const confirmInput = document.querySelector(`[data-review-confirm-text="${CSS.escape(taskId)}"]`);
121
+ const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
122
+ if (!checkbox?.checked) {
123
+ if (result) result.textContent = t("reviewChecklistRequired");
124
+ return;
125
+ }
126
+ if (!confirmInput?.value || ![task?.shortId, task?.id].includes(confirmInput.value.trim())) {
127
+ if (result) result.textContent = t("reviewConfirmTextMismatch");
128
+ return;
129
+ }
130
+ if (result) result.textContent = t("reviewSubmitting");
131
+ try {
132
+ const response = await fetch("/api/tasks/review-complete", {
133
+ method: "POST",
134
+ headers: {
135
+ "content-type": "application/json",
136
+ "x-harness-csrf": state.runtime?.csrfToken || "",
137
+ },
138
+ body: JSON.stringify({
139
+ taskId,
140
+ confirmText: confirmInput.value.trim(),
141
+ reviewer: "Human Reviewer",
142
+ message: "confirmed from dashboard workbench",
143
+ }),
144
+ });
145
+ const payload = await response.json();
146
+ if (!response.ok) throw new Error(payload.error || t("reviewCompleteFailed"));
147
+ if (result) result.textContent = t("reviewCompleteSuccess");
148
+ setTimeout(() => window.location.reload(), 500);
149
+ } catch (error) {
150
+ if (result) result.textContent = `${t("reviewCompleteFailed")}: ${error.message}`;
151
+ }
152
+ }
153
+
154
+ function renderDrawerContent(taskId) {
155
+ const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
156
+ if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
157
+
158
+ const header = `
159
+ <div class="task-drawer-header">
160
+ <div>
161
+ <h2>${escapeHtml(task.title)}</h2>
162
+ <p style="font-family: var(--font-mono); font-size: 11px; margin: 4px 0 0; color: var(--muted);">${escapeHtml(task.id)}</p>
163
+ </div>
164
+ <button class="btn-close" data-close-drawer>×</button>
165
+ </div>
166
+ `;
167
+
168
+ const timeline = phaseTimeline(task);
169
+ const documents = taskDocumentLibrary(task, "");
170
+ const findings = openFindings(task);
171
+ const evidence = evidenceList(task);
172
+
173
+ const body = `
174
+ <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>
178
+ </div>
179
+ ${taskStateSummary(task)}
180
+ ${reviewActionPanel(task, { mode: "summary" })}
181
+ ${timeline}
182
+ ${documents}
183
+ ${findings}
184
+ ${evidence}
185
+ </div>
186
+ `;
187
+
188
+ return header + body;
189
+ }
190
+
191
+ function openDrawer(taskId) {
192
+ const drawer = document.getElementById("task-drawer");
193
+ const overlay = document.getElementById("drawer-overlay");
194
+ if (!drawer || !overlay) return;
195
+ drawer.innerHTML = renderDrawerContent(taskId);
196
+ drawer.classList.add("active");
197
+ overlay.classList.add("active");
198
+
199
+ drawer.querySelector("[data-close-drawer]").addEventListener("click", closeDrawer);
200
+ drawer.querySelectorAll("[data-render-toggle]").forEach((button) => button.addEventListener("click", () => {
201
+ state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
202
+ openDrawer(taskId);
203
+ }));
204
+ drawer.querySelectorAll("[data-review-complete]").forEach((button) => button.addEventListener("click", () => completeReviewFromDashboard(button.dataset.reviewComplete)));
205
+ }
206
+
207
+ 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) {
216
+ return `<div class="task-drawer-header">
217
+ <h2>${escapeHtml(lessonId)}</h2>
218
+ <button class="btn-close" data-close-drawer>×</button>
219
+ </div>
220
+ <div class="task-drawer-body">
221
+ <div class="empty">${t("lessonNotFound")}</div>
222
+ </div>`;
223
+ }
224
+
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
+ }
236
+
237
+ const header = `
238
+ <div class="task-drawer-header">
239
+ <div>
240
+ <h2>${escapeHtml(lessonId)}</h2>
241
+ <p style="font-size: 12px; margin: 4px 0 0; color: var(--muted); font-weight: 600;">${escapeHtml(summary)}</p>
242
+ </div>
243
+ <button class="btn-close" data-close-drawer>×</button>
244
+ </div>
245
+ `;
246
+
247
+ let markdownBody = "";
248
+ if (doc && doc.content) {
249
+ markdownBody = `<div class="markdown">${window.HarnessMarkdown.render(doc.content, "rendered")}</div>`;
250
+ } else {
251
+ const rowsHtml = Object.entries(cells)
252
+ .map(([key, val]) => `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(val)}</td></tr>`)
253
+ .join("");
254
+ markdownBody = `
255
+ <div style="margin-bottom: 20px; background: var(--paper-2); padding: 16px; border-radius: 8px; border: 1px dashed var(--line);">
256
+ <p style="margin: 0; font-size: 13px; color: var(--muted);">${t("lessonDocMissing")}</p>
257
+ </div>
258
+ <table class="rendered-table" style="width: 100%;">
259
+ <tbody>${rowsHtml}</tbody>
260
+ </table>
261
+ `;
262
+ }
263
+
264
+ const body = `
265
+ <div class="task-drawer-body stack">
266
+ ${markdownBody}
267
+ </div>
268
+ `;
269
+
270
+ return header + body;
271
+ }
272
+
273
+ function openLessonDrawer(lessonId) {
274
+ const drawer = document.getElementById("task-drawer");
275
+ const overlay = document.getElementById("drawer-overlay");
276
+ if (!drawer || !overlay) return;
277
+ drawer.innerHTML = renderLessonDrawerContent(lessonId);
278
+ drawer.classList.add("active");
279
+ overlay.classList.add("active");
280
+
281
+ drawer.querySelector("[data-close-drawer]").addEventListener("click", closeDrawer);
282
+ }
283
+
284
+ function closeDrawer() {
285
+ const drawer = document.getElementById("task-drawer");
286
+ const overlay = document.getElementById("drawer-overlay");
287
+ if (drawer) drawer.classList.remove("active");
288
+ if (overlay) overlay.classList.remove("active");
289
+ }
290
+
291
+ function ledgerPanel() {
292
+ const ledgerTable = (bundle.tables?.tables || []).find((table) => table.kind === "harness-ledger");
293
+ const rows = ledgerTable?.rows || [];
294
+
295
+ let closedCount = 0;
296
+ let openCount = 0;
297
+ let blockedCount = 0;
298
+
299
+ let lessonsReviewed = 0;
300
+ let lessonsTotal = 0;
301
+
302
+ let evidenceAudited = 0;
303
+ let evidenceTotal = 0;
304
+
305
+ for (const row of rows) {
306
+ const cells = row.cells || {};
307
+ const status = String(cells.Status || cells["\u72b6\u6001"] || "").toLowerCase();
308
+ if (status.includes("close") || status.includes("done") || status.includes("\u7ed3") || status.includes("\u5b8c")) {
309
+ closedCount++;
310
+ } else if (status.includes("block") || status.includes("\u963b")) {
311
+ blockedCount++;
312
+ } else {
313
+ openCount++;
314
+ }
315
+
316
+ const lesson = String(cells.Lessons || cells["\u7ecf\u9a8c"] || cells["\u7ecf\u9a8c\u5ba1\u67e5"] || cells["Lesson"] || "");
317
+ if (lesson) {
318
+ lessonsTotal++;
319
+ if (lesson.toLowerCase().includes("pass") || lesson.includes("\u901a\u8fc7") || lesson.includes("\u5c31\u7eea") || lesson.toLowerCase().includes("checked") || lesson.toLowerCase().includes("done")) {
320
+ lessonsReviewed++;
321
+ }
322
+ }
323
+
324
+ const evidence = String(cells.Evidence || cells["\u8bc1\u636e"] || cells["\u9a8c\u8bc1\u8bc1\u636e"] || cells["Evidence Checked"] || "");
325
+ if (evidence) {
326
+ evidenceTotal++;
327
+ if (evidence.toLowerCase().includes("pass") || evidence.includes("\u901a\u8fc7") || evidence.toLowerCase().includes("present") || evidence.toLowerCase().includes("verified") || evidence.toLowerCase().includes("done")) {
328
+ evidenceAudited++;
329
+ }
330
+ }
331
+ }
332
+
333
+ const total = closedCount + openCount + blockedCount || 1;
334
+ const closedPct = Math.round((closedCount / total) * 100);
335
+ const openPct = Math.round((openCount / total) * 100);
336
+ const blockedPct = total - closedPct - openPct;
337
+
338
+ const lessonsPct = lessonsTotal ? Math.round((lessonsReviewed / lessonsTotal) * 100) : 0;
339
+ const evidencePct = evidenceTotal ? Math.round((evidenceAudited / evidenceTotal) * 100) : 0;
340
+
341
+ if (rows.length === 0) return "";
342
+
343
+ return `<section class="ledger-panel">
344
+ <h2>${t("ssotLedger")}</h2>
345
+ <div class="ledger-split-bar" title="${t("tagClosed")}: ${closedCount}, ${t("tagOpen")}: ${openCount}, ${t("tagBlocked")}: ${blockedCount}">
346
+ <div class="ledger-split-segment closed" style="width: ${closedPct}%"></div>
347
+ <div class="ledger-split-segment open" style="width: ${openPct}%"></div>
348
+ <div class="ledger-split-segment blocked" style="width: ${Math.max(0, blockedPct)}%"></div>
349
+ </div>
350
+ <div class="ledger-split-legend">
351
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot closed"></i>${t("tagClosed")} (${closedCount})</span>
352
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot open"></i>${t("tagOpen")} (${openCount})</span>
353
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot blocked"></i>${t("tagBlocked")} (${blockedCount})</span>
354
+ </div>
355
+ <div class="ledger-gauge-row">
356
+ <div class="ledger-gauge-card">
357
+ <span>${t("lessonsCheckRate")}</span>
358
+ <strong>${lessonsPct}%</strong>
359
+ </div>
360
+ <div class="ledger-gauge-card">
361
+ <span>${t("evidenceAuditRate")}</span>
362
+ <strong>${evidencePct}%</strong>
363
+ </div>
364
+ </div>
365
+ </section>`;
366
+ }
367
+
368
+ function escapeHtml(value) {
369
+ return String(value ?? "")
370
+ .replaceAll("&", "&amp;")
371
+ .replaceAll("<", "&lt;")
372
+ .replaceAll(">", "&gt;")
373
+ .replaceAll('"', "&quot;");
374
+ }
375
+
376
+ function escapeAttr(value) {
377
+ return escapeHtml(value).replaceAll("'", "&#39;");
378
+ }
379
+
380
+ window.addEventListener("hashchange", app);
381
+ app();
382
+ loadRuntime();