coding-agent-harness 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +96 -4
  6. package/README.zh-CN.md +75 -4
  7. package/SKILL.md +52 -51
  8. package/dist/build-dist.mjs +189 -0
  9. package/dist/check-dist-observation.mjs +428 -0
  10. package/dist/check-harness.mjs +489 -0
  11. package/dist/check-import-graph.mjs +511 -0
  12. package/dist/check-runtime-emit.mjs +304 -0
  13. package/dist/check-type-boundaries.mjs +139 -0
  14. package/dist/commands/dashboard-command.mjs +80 -0
  15. package/dist/commands/migration-command.mjs +152 -0
  16. package/dist/commands/preset-command.mjs +91 -0
  17. package/dist/commands/task-command.mjs +324 -0
  18. package/dist/harness.mjs +304 -0
  19. package/dist/lib/capability-registry.mjs +643 -0
  20. package/dist/lib/check-module-parallel.mjs +227 -0
  21. package/dist/lib/check-profiles.mjs +414 -0
  22. package/dist/lib/check-task-contracts.mjs +54 -0
  23. package/dist/lib/core-shared.mjs +254 -0
  24. package/dist/lib/dashboard-data.mjs +608 -0
  25. package/dist/lib/dashboard-workbench.mjs +334 -0
  26. package/dist/lib/dashboard-writer.mjs +200 -0
  27. package/dist/lib/git-status-summary.mjs +45 -0
  28. package/dist/lib/governance-index-generator.mjs +236 -0
  29. package/dist/lib/governance-sync.mjs +617 -0
  30. package/dist/lib/governance-table-boundary.mjs +161 -0
  31. package/{scripts → dist}/lib/harness-core.mjs +3 -0
  32. package/dist/lib/harness-paths.mjs +338 -0
  33. package/dist/lib/lesson-maintenance.mjs +139 -0
  34. package/dist/lib/markdown-utils.mjs +193 -0
  35. package/dist/lib/migration-planner.mjs +439 -0
  36. package/dist/lib/migration-support.mjs +317 -0
  37. package/dist/lib/phase-kind.mjs +46 -0
  38. package/dist/lib/preset-audit-contracts.mjs +40 -0
  39. package/dist/lib/preset-engine.mjs +516 -0
  40. package/dist/lib/preset-registry.mjs +831 -0
  41. package/dist/lib/preset-resource-contracts.mjs +83 -0
  42. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  43. package/dist/lib/status-builder.mjs +87 -0
  44. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
  45. package/dist/lib/structure-migration.mjs +404 -0
  46. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  47. package/dist/lib/task-audit-metadata.mjs +376 -0
  48. package/dist/lib/task-audit-migration.mjs +355 -0
  49. package/dist/lib/task-completion-consistency.mjs +29 -0
  50. package/dist/lib/task-index.mjs +133 -0
  51. package/dist/lib/task-lesson-candidates.mjs +239 -0
  52. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  53. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  54. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  55. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  56. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  57. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  58. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  59. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  60. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  61. package/dist/lib/task-lifecycle.mjs +611 -0
  62. package/dist/lib/task-metadata.mjs +116 -0
  63. package/dist/lib/task-review-model.mjs +474 -0
  64. package/dist/lib/task-scanner.mjs +439 -0
  65. package/dist/lib/task-tombstone-commands.mjs +125 -0
  66. package/dist/postinstall.mjs +14 -0
  67. package/dist/run-built-tests.mjs +84 -0
  68. package/docs-release/README.md +1 -0
  69. package/docs-release/architecture/overview.md +13 -13
  70. package/docs-release/architecture/overview.zh-CN.md +13 -13
  71. package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
  72. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  73. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  74. package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
  75. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  76. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
  77. package/docs-release/architecture/system-explainer/README.md +67 -0
  78. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
  79. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  80. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  81. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
  82. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  83. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
  84. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  85. package/docs-release/guides/agent-installation.en-US.md +22 -15
  86. package/docs-release/guides/agent-installation.md +23 -15
  87. package/docs-release/guides/contributing.md +3 -3
  88. package/docs-release/guides/contributing.zh-CN.md +3 -3
  89. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  90. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  91. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  92. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  93. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  94. package/docs-release/guides/migration-playbook.md +59 -1
  95. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  96. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  97. package/docs-release/guides/preset-development.md +28 -4
  98. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  99. package/docs-release/guides/repository-operating-models.md +21 -21
  100. package/docs-release/guides/task-state-machine.en-US.md +35 -18
  101. package/docs-release/guides/task-state-machine.md +35 -18
  102. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  103. package/examples/minimal-project/AGENTS.md +2 -2
  104. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  105. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
  106. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  107. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  108. package/package.json +22 -13
  109. package/presets/legacy-migration/preset.yaml +5 -5
  110. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  111. package/presets/lesson-sedimentation/preset.yaml +3 -3
  112. package/presets/module/preset.yaml +2 -2
  113. package/presets/module/templates/execution_strategy.append.md +1 -1
  114. package/presets/module/templates/task_plan.append.md +3 -3
  115. package/presets/standard-task/preset.yaml +2 -2
  116. package/references/adversarial-review-standard.md +2 -2
  117. package/references/agents-md-pattern.md +14 -14
  118. package/references/cadence-ledger.md +1 -1
  119. package/references/ci-cd-standard.md +1 -1
  120. package/references/delivery-operating-model-standard.md +4 -4
  121. package/references/docs-directory-standard.md +65 -159
  122. package/references/external-source-intake-standard.md +10 -10
  123. package/references/harness-ledger.md +6 -6
  124. package/references/legacy-12-phase-bootstrap.md +2 -2
  125. package/references/lessons-governance.md +15 -15
  126. package/references/long-running-task-standard.md +6 -6
  127. package/references/module-parallel-standard.md +34 -34
  128. package/references/planning-loop.md +6 -6
  129. package/references/project-onboarding-audit.md +4 -4
  130. package/references/regression-system.md +2 -2
  131. package/references/repo-governance-standard.md +4 -4
  132. package/references/review-routing-standard.md +1 -1
  133. package/references/ssot-governance.md +19 -19
  134. package/references/taskr-gap-analysis.md +5 -5
  135. package/references/walkthrough-closeout.md +14 -14
  136. package/references/worktree-parallel.md +3 -3
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  139. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  140. package/templates/AGENTS.md.template +31 -29
  141. package/templates/architecture/README.md +4 -4
  142. package/templates/architecture/service-catalog.md +2 -2
  143. package/templates/architecture/services/service-template.md +1 -1
  144. package/templates/dashboard/assets/app-src/00-state.js +12 -0
  145. package/templates/dashboard/assets/app-src/10-router.js +3 -0
  146. package/templates/dashboard/assets/app-src/20-overview.js +13 -3
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
  148. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  149. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  150. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  151. package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
  152. package/templates/dashboard/assets/app.css +583 -0
  153. package/templates/dashboard/assets/app.css.manifest.json +1 -0
  154. package/templates/dashboard/assets/app.js +585 -11
  155. package/templates/dashboard/assets/app.manifest.json +1 -0
  156. package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
  158. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  159. package/templates/dashboard/assets/i18n.js +144 -4
  160. package/templates/development/README.md +10 -10
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +2 -2
  163. package/templates/development/external-source-packs/README.md +4 -4
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +2 -2
  166. package/templates/integrations/event-contract.md +2 -2
  167. package/templates/integrations/third-party/vendor-template.md +2 -2
  168. package/templates/integrations/webhook-contract.md +2 -2
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/planning/INDEX.md +88 -0
  171. package/templates/planning/brief.md +1 -1
  172. package/templates/planning/module_session_prompt.md +2 -1
  173. package/templates/planning/review.md +0 -18
  174. package/templates/planning/task_plan.md +5 -44
  175. package/templates/planning/visual_map.md +13 -9
  176. package/templates/planning/visual_map.simple.md +52 -0
  177. package/templates/planning/walkthrough.md +47 -0
  178. package/templates/reference/docs-library-standard.md +8 -8
  179. package/templates/reference/execution-workflow-standard.md +29 -2
  180. package/templates/reference/external-source-intake-standard.md +15 -15
  181. package/templates/reference/repo-governance-standard.md +1 -1
  182. package/templates/ssot/Module-Registry.md +1 -1
  183. package/templates/walkthrough/walkthrough-template.md +2 -2
  184. package/templates-zh-CN/AGENTS.md.template +31 -29
  185. package/templates-zh-CN/CLAUDE.md.template +1 -1
  186. package/templates-zh-CN/architecture/README.md +4 -4
  187. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  188. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  189. package/templates-zh-CN/development/README.md +10 -10
  190. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  191. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  192. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  193. package/templates-zh-CN/integrations/README.md +4 -4
  194. package/templates-zh-CN/integrations/api-contract.md +2 -2
  195. package/templates-zh-CN/integrations/event-contract.md +2 -2
  196. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  197. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  198. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  199. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  200. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  201. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  202. package/templates-zh-CN/planning/INDEX.md +87 -0
  203. package/templates-zh-CN/planning/brief.md +1 -1
  204. package/templates-zh-CN/planning/module_session_prompt.md +12 -11
  205. package/templates-zh-CN/planning/review.md +0 -18
  206. package/templates-zh-CN/planning/task_plan.md +3 -63
  207. package/templates-zh-CN/planning/visual_map.md +14 -7
  208. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  209. package/templates-zh-CN/planning/walkthrough.md +47 -0
  210. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  211. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  212. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  213. package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
  214. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  215. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  216. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  217. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  218. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  219. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  220. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  221. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  222. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  223. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  224. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  225. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  226. package/tsconfig.dist.json +16 -0
  227. package/tsconfig.json +25 -0
  228. package/tsconfig.runtime.json +24 -0
  229. package/examples/minimal-project/.harness-capabilities.json +0 -8
  230. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  231. package/scripts/check-harness.mjs +0 -508
  232. package/scripts/commands/dashboard-command.mjs +0 -67
  233. package/scripts/commands/migration-command.mjs +0 -96
  234. package/scripts/commands/preset-command.mjs +0 -73
  235. package/scripts/commands/task-command.mjs +0 -327
  236. package/scripts/harness.mjs +0 -287
  237. package/scripts/lib/capability-registry.mjs +0 -591
  238. package/scripts/lib/check-module-parallel.mjs +0 -237
  239. package/scripts/lib/check-profiles.mjs +0 -418
  240. package/scripts/lib/check-task-contracts.mjs +0 -47
  241. package/scripts/lib/core-shared.mjs +0 -196
  242. package/scripts/lib/dashboard-data.mjs +0 -412
  243. package/scripts/lib/dashboard-workbench.mjs +0 -257
  244. package/scripts/lib/dashboard-writer.mjs +0 -198
  245. package/scripts/lib/git-status-summary.mjs +0 -46
  246. package/scripts/lib/governance-index-generator.mjs +0 -174
  247. package/scripts/lib/governance-sync.mjs +0 -514
  248. package/scripts/lib/governance-table-boundary.mjs +0 -175
  249. package/scripts/lib/lesson-maintenance.mjs +0 -152
  250. package/scripts/lib/markdown-utils.mjs +0 -158
  251. package/scripts/lib/migration-planner.mjs +0 -478
  252. package/scripts/lib/migration-support.mjs +0 -312
  253. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  254. package/scripts/lib/preset-engine.mjs +0 -497
  255. package/scripts/lib/preset-registry.mjs +0 -627
  256. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  257. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  258. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  259. package/scripts/lib/task-completion-consistency.mjs +0 -16
  260. package/scripts/lib/task-index.mjs +0 -93
  261. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  262. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  263. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
  264. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
  265. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  266. package/scripts/lib/task-lifecycle.mjs +0 -649
  267. package/scripts/lib/task-review-model.mjs +0 -469
  268. package/scripts/lib/task-scanner.mjs +0 -576
  269. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  270. package/scripts/postinstall.mjs +0 -14
  271. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  272. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  273. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  274. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  275. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  276. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  277. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  278. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  279. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
4
+ import { readFileSafe, toPosix } from "./core-shared.mjs";
5
+ export function validatePresetResourcesForTask(target, task, presetPackage) {
6
+ const failures = [];
7
+ const taskRelativePath = String(task.path || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
8
+ if (!taskRelativePath)
9
+ return failures;
10
+ const taskDir = path.join(target.projectRoot, taskRelativePath);
11
+ const referenceIndex = readFileSafe(path.join(taskDir, "references/INDEX.md"));
12
+ const referenceRows = parseMarkdownRows(referenceIndex, ["ID", "Path"]);
13
+ const artifactIndex = readFileSafe(path.join(taskDir, "artifacts/INDEX.md"));
14
+ const artifactRows = parseMarkdownRows(artifactIndex, ["ID", "Path"]);
15
+ const taskPlan = task.taskPlanPath ? readFileSafe(path.join(target.projectRoot, String(task.taskPlanPath).replace(/^TARGET:/, "").replace(/^\/+/, ""))) : "";
16
+ const requiredReadRows = parseMarkdownRows(taskPlan, ["Reference", "Path"]);
17
+ const expectedReferencePaths = new Map();
18
+ for (const resource of Object.values(presetPackage.resources?.references || {})) {
19
+ const relativePath = toPosix(path.join(taskRelativePath, resource.path));
20
+ expectedReferencePaths.set(resource.index.id, `TARGET:${relativePath}`);
21
+ if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
22
+ failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
23
+ }
24
+ if (!hasIndexedResource(referenceRows, resource.index.id, `TARGET:${relativePath}`)) {
25
+ failures.push(`${task.path} ${task.taskPreset} preset reference index missing ${resource.index.id}`);
26
+ }
27
+ }
28
+ for (const resource of Object.values(presetPackage.resources?.artifacts || {})) {
29
+ const relativePath = toPosix(path.join(taskRelativePath, resource.path));
30
+ if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
31
+ failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
32
+ }
33
+ if (!hasIndexedResource(artifactRows, resource.index.id, `TARGET:${relativePath}`)) {
34
+ failures.push(`${task.path} ${task.taskPreset} preset artifact index missing ${resource.index.id}`);
35
+ }
36
+ }
37
+ for (const requiredRead of presetPackage.context?.requiredReads || []) {
38
+ const expectedPath = expectedReferencePaths.get(requiredRead);
39
+ if (!referenceRows.some((row) => row.ID === requiredRead && (!expectedPath || row.Path === expectedPath))) {
40
+ failures.push(`${task.path} ${task.taskPreset} preset required read missing from references index: ${requiredRead}`);
41
+ }
42
+ if (!requiredReadRows.some((row) => row.Reference === requiredRead && (!expectedPath || row.Path === expectedPath))) {
43
+ failures.push(`${task.path} ${task.taskPreset} preset required read missing from task plan: ${requiredRead}`);
44
+ }
45
+ }
46
+ return failures;
47
+ }
48
+ function hasIndexedResource(rows, id, expectedPath) {
49
+ return rows.some((row) => row.ID === id && row.Path === expectedPath);
50
+ }
51
+ function parseMarkdownRows(markdown, requiredColumns) {
52
+ const rows = [];
53
+ const lines = String(markdown || "").split(/\r?\n/);
54
+ for (let index = 0; index < lines.length; index += 1) {
55
+ const line = lines[index].trim();
56
+ const next = lines[index + 1]?.trim() || "";
57
+ if (!isTableRow(line) || !isTableSeparator(next))
58
+ continue;
59
+ const header = splitTableRow(line);
60
+ if (!requiredColumns.every((column) => header.includes(column)))
61
+ continue;
62
+ index += 2;
63
+ while (index < lines.length && isTableRow(lines[index].trim())) {
64
+ const cells = splitTableRow(lines[index].trim());
65
+ if (cells.length === header.length)
66
+ rows.push(Object.fromEntries(header.map((column, cellIndex) => [column, cells[cellIndex] || ""])));
67
+ index += 1;
68
+ }
69
+ index -= 1;
70
+ }
71
+ return rows;
72
+ }
73
+ function isTableRow(line) {
74
+ return line.startsWith("|") && line.endsWith("|");
75
+ }
76
+ function isTableSeparator(line) {
77
+ if (!isTableRow(line))
78
+ return false;
79
+ return splitTableRow(line).every((cell) => /^:?-{3,}:?$/.test(cell));
80
+ }
81
+ function splitTableRow(line) {
82
+ return line.slice(1, -1).split("|").map((cell) => cell.trim());
83
+ }
@@ -0,0 +1,244 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ // @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
5
+ import { toPosix } from "./core-shared.mjs";
6
+ export class ReviewConfirmGitGateError extends Error {
7
+ code;
8
+ status;
9
+ details;
10
+ recovery;
11
+ constructor(message, { code = "review-confirm-git-gate-failed", status = 409, details = {}, recovery = [] } = {}) {
12
+ super(message);
13
+ this.name = "ReviewConfirmGitGateError";
14
+ this.code = code;
15
+ this.status = status;
16
+ this.details = details;
17
+ this.recovery = recovery;
18
+ }
19
+ }
20
+ export function prepareReviewConfirmGitGate(projectRoot, allowedFilesAbs) {
21
+ const root = path.resolve(projectRoot);
22
+ const resolvedGitRoot = requireGitRoot(root);
23
+ if (real(resolvedGitRoot) !== real(root)) {
24
+ throw new ReviewConfirmGitGateError("Target must be the Git repository root for review confirmation auto-commit.", {
25
+ code: "git-root-mismatch",
26
+ details: { targetRoot: root, gitRoot: resolvedGitRoot },
27
+ recovery: [
28
+ "Run review-confirm from the repository root for the target task.",
29
+ "For private harness tasks, run against the private harness repository root, not the public parent.",
30
+ ],
31
+ });
32
+ }
33
+ const gitRoot = root;
34
+ const allowedPaths = allowedFilesAbs.map((filePath) => toPosix(path.relative(gitRoot, path.resolve(filePath))));
35
+ assertAllowedPaths(allowedPaths);
36
+ assertCleanWorkingTree(gitRoot);
37
+ assertCommitIdentity(gitRoot);
38
+ return { gitRoot, allowedPaths };
39
+ }
40
+ export function commitReviewConfirmationGate(gate, { taskId, reviewPath, writeFinalAudit, message = "" }) {
41
+ const subjectSuffix = taskId.replace(/[^A-Za-z0-9._/-]+/g, "-");
42
+ assertOnlyAllowedChanged(gate.gitRoot, gate.allowedPaths);
43
+ git(gate.gitRoot, ["add", "--", ...gate.allowedPaths]);
44
+ assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
45
+ const confirmCommit = commit(gate.gitRoot, `chore: confirm review ${subjectSuffix}`, {
46
+ recovery: [
47
+ "Review confirmation files were written but not committed.",
48
+ `Inspect and either fix hooks then run: git add -- ${gate.allowedPaths.join(" ")} && git commit`,
49
+ "Or manually revert the written review confirmation files if the confirmation should not proceed.",
50
+ ],
51
+ });
52
+ writeFinalAudit(confirmCommit);
53
+ const reviewRelativePath = toPosix(path.relative(gate.gitRoot, path.resolve(reviewPath)));
54
+ git(gate.gitRoot, ["add", "--", reviewRelativePath]);
55
+ assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
56
+ const auditCommit = commit(gate.gitRoot, `chore: record review confirmation audit ${subjectSuffix}`, {
57
+ recovery: [
58
+ "The confirmation commit was created, but final audit metadata could not be committed.",
59
+ `Confirmation commit SHA: ${confirmCommit}`,
60
+ `Fix hooks, then stage ${reviewRelativePath} and commit the audit metadata.`,
61
+ ],
62
+ });
63
+ assertCleanWorkingTree(gate.gitRoot);
64
+ return {
65
+ commitSha: confirmCommit,
66
+ auditCommitSha: auditCommit,
67
+ auditStatus: "committed",
68
+ allowedPaths: gate.allowedPaths,
69
+ message,
70
+ };
71
+ }
72
+ export function validateReviewConfirmationGitAudit({ projectRoot, taskId, reviewPath, progressPath, commitSha }) {
73
+ const issues = [];
74
+ const addIssue = (code) => issues.push(code);
75
+ const root = projectRoot ? path.resolve(projectRoot) : "";
76
+ const reviewRelativePath = root && reviewPath ? toPosix(path.relative(root, path.resolve(reviewPath))) : "";
77
+ const progressRelativePath = root && progressPath ? toPosix(path.relative(root, path.resolve(progressPath))) : "";
78
+ const expectedPaths = [reviewRelativePath, progressRelativePath].filter(Boolean).sort();
79
+ if (!root)
80
+ addIssue("git-audit-context-missing");
81
+ if (!commitSha)
82
+ addIssue("git-audit-commit-missing");
83
+ if (issues.length > 0)
84
+ return { valid: false, issues };
85
+ const gitRootResult = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
86
+ if (gitRootResult.status !== 0) {
87
+ return { valid: false, issues: ["git-audit-repository-missing"] };
88
+ }
89
+ const gitRoot = path.resolve(gitRootResult.stdout.trim());
90
+ if (real(gitRoot) !== real(root))
91
+ addIssue("git-audit-root-mismatch");
92
+ const commitResult = git(root, ["rev-parse", "--verify", `${commitSha}^{commit}`], { allowFailure: true });
93
+ if (commitResult.status !== 0) {
94
+ return { valid: false, issues: [...issues, "git-audit-commit-missing"] };
95
+ }
96
+ const fullCommitSha = commitResult.stdout.trim();
97
+ const reachable = git(root, ["merge-base", "--is-ancestor", fullCommitSha, "HEAD"], { allowFailure: true });
98
+ if (reachable.status !== 0)
99
+ addIssue("git-audit-commit-not-reachable");
100
+ const subject = git(root, ["show", "-s", "--format=%s", fullCommitSha], { allowFailure: true }).stdout.trim();
101
+ const expectedSubject = `chore: confirm review ${String(taskId || "").replace(/[^A-Za-z0-9._/-]+/g, "-")}`;
102
+ if (subject !== expectedSubject)
103
+ addIssue("git-audit-subject-mismatch");
104
+ const changedPaths = git(root, ["diff-tree", "--no-commit-id", "--name-only", "-r", fullCommitSha], { allowFailure: true }).stdout
105
+ .split(/\r?\n/)
106
+ .filter(Boolean)
107
+ .map(toPosix)
108
+ .sort();
109
+ if (expectedPaths.length === 0)
110
+ addIssue("git-audit-allowlist-missing");
111
+ if (changedPaths.join("\n") !== expectedPaths.join("\n"))
112
+ addIssue("git-audit-allowlist-mismatch");
113
+ return {
114
+ valid: issues.length === 0,
115
+ issues,
116
+ commitSha: fullCommitSha,
117
+ changedPaths,
118
+ expectedPaths,
119
+ subject,
120
+ };
121
+ }
122
+ function requireGitRoot(root) {
123
+ const result = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
124
+ if (result.status !== 0) {
125
+ throw new ReviewConfirmGitGateError("Review confirmation auto-commit requires a Git repository.", {
126
+ code: "git-repository-missing",
127
+ details: { root, stderr: result.stderr.trim() },
128
+ recovery: ["Initialize Git for the target project or run review-confirm from the correct repository root."],
129
+ });
130
+ }
131
+ return path.resolve(result.stdout.trim());
132
+ }
133
+ function assertAllowedPaths(paths) {
134
+ const disallowed = paths.filter((relativePath) => {
135
+ if (!relativePath || relativePath.startsWith("../") || path.isAbsolute(relativePath))
136
+ return true;
137
+ if (relativePath === "AGENTS.md" || relativePath === "CLAUDE.md")
138
+ return true;
139
+ if (relativePath === "docs" || relativePath.startsWith("docs/"))
140
+ return false;
141
+ if (relativePath === ".harness-private" || relativePath.startsWith(".harness-private/"))
142
+ return true;
143
+ return false;
144
+ });
145
+ if (disallowed.length > 0) {
146
+ throw new ReviewConfirmGitGateError("Review confirmation write allowlist contains forbidden paths.", {
147
+ code: "git-allowlist-forbidden-path",
148
+ details: { disallowed },
149
+ recovery: ["Limit review-confirm writes to the current task INDEX.md file."],
150
+ });
151
+ }
152
+ }
153
+ function assertCleanWorkingTree(gitRoot) {
154
+ const entries = statusEntries(gitRoot);
155
+ if (entries.length > 0) {
156
+ throw new ReviewConfirmGitGateError("Git working tree is not clean; refusing review confirmation auto-commit.", {
157
+ code: "git-dirty-working-tree",
158
+ details: { entries },
159
+ recovery: [
160
+ "Commit, move, or intentionally discard unrelated changes before review-confirm.",
161
+ "Do not stash/reset automatically; resolve ownership of the dirty files first.",
162
+ ],
163
+ });
164
+ }
165
+ }
166
+ function assertCommitIdentity(gitRoot) {
167
+ const name = git(gitRoot, ["config", "--get", "user.name"], { allowFailure: true }).stdout.trim();
168
+ const email = git(gitRoot, ["config", "--get", "user.email"], { allowFailure: true }).stdout.trim();
169
+ if (!name || !email) {
170
+ throw new ReviewConfirmGitGateError("Git commit identity is missing; refusing review confirmation auto-commit.", {
171
+ code: "git-identity-missing",
172
+ details: { hasName: Boolean(name), hasEmail: Boolean(email) },
173
+ recovery: [
174
+ "Set a local Git identity for this repository:",
175
+ "git config user.name \"Your Name\"",
176
+ "git config user.email \"you@example.com\"",
177
+ ],
178
+ });
179
+ }
180
+ }
181
+ function assertOnlyAllowedChanged(gitRoot, allowedPaths) {
182
+ const entries = statusEntries(gitRoot);
183
+ const outside = entries.filter((entry) => !allowedPaths.includes(entry.path));
184
+ if (outside.length > 0) {
185
+ throw new ReviewConfirmGitGateError("Review confirmation produced changes outside the write allowlist.", {
186
+ code: "git-allowlist-violation",
187
+ details: { entries, allowedPaths },
188
+ recovery: [
189
+ "Inspect the extra files and do not commit them through review-confirm.",
190
+ "Revert only the unintended review-confirm side effects, then retry.",
191
+ ],
192
+ });
193
+ }
194
+ }
195
+ function assertOnlyAllowedStaged(gitRoot, allowedPaths) {
196
+ const entries = statusEntries(gitRoot);
197
+ const stagedOutside = entries.filter((entry) => entry.index !== " " && !allowedPaths.includes(entry.path));
198
+ if (stagedOutside.length > 0) {
199
+ throw new ReviewConfirmGitGateError("Git index contains staged files outside the review confirmation allowlist.", {
200
+ code: "git-index-allowlist-violation",
201
+ details: { stagedOutside, allowedPaths },
202
+ recovery: ["Unstage unrelated files before retrying review-confirm."],
203
+ });
204
+ }
205
+ }
206
+ function commit(gitRoot, message, { recovery }) {
207
+ const result = git(gitRoot, ["commit", "-m", message], { allowFailure: true });
208
+ if (result.status !== 0) {
209
+ throw new ReviewConfirmGitGateError("Git commit failed during review confirmation auto-commit.", {
210
+ code: "git-commit-failed",
211
+ details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
212
+ recovery,
213
+ });
214
+ }
215
+ return git(gitRoot, ["rev-parse", "HEAD"]).stdout.trim();
216
+ }
217
+ function statusEntries(gitRoot) {
218
+ const output = git(gitRoot, ["status", "--porcelain=v1", "--untracked-files=all"]).stdout;
219
+ return output.split(/\r?\n/).filter(Boolean).map((line) => ({
220
+ index: line.slice(0, 1),
221
+ worktree: line.slice(1, 2),
222
+ path: parseStatusPath(line.slice(3)),
223
+ raw: line,
224
+ }));
225
+ }
226
+ function parseStatusPath(value) {
227
+ const unquoted = value.replace(/^"|"$/g, "");
228
+ const renamed = unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() : unquoted;
229
+ return renamed || "";
230
+ }
231
+ function git(cwd, args, { allowFailure = false } = {}) {
232
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
233
+ if (!allowFailure && result.status !== 0) {
234
+ throw new ReviewConfirmGitGateError(`git ${args.join(" ")} failed`, {
235
+ code: "git-command-failed",
236
+ details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
237
+ recovery: ["Inspect the Git error and retry review-confirm after resolving it."],
238
+ });
239
+ }
240
+ return result;
241
+ }
242
+ function real(filePath) {
243
+ return fs.realpathSync(filePath);
244
+ }
@@ -0,0 +1,87 @@
1
+ // @ts-nocheck
2
+ import path from "node:path";
3
+ import { normalizeTarget, toPosix } from "./core-shared.mjs";
4
+ import { capabilityDefinitions, readCapabilityRegistry } from "./capability-registry.mjs";
5
+ import { summarizeGitState } from "./git-status-summary.mjs";
6
+ import { collectTasks, taskCutoverCounters } from "./task-scanner.mjs";
7
+ export function buildStatusData(targetInput, options = {}) {
8
+ const target = targetInput?.projectRoot ? targetInput : normalizeTarget(targetInput);
9
+ const validationMode = options.validationMode || "data-only";
10
+ const gitState = options.gitState || summarizeGitState(target);
11
+ const registry = options.capabilityState?.registry || readCapabilityRegistry(target);
12
+ const detected = options.capabilityState?.detected || [];
13
+ const capabilityWarnings = options.capabilityState?.warnings || [];
14
+ const failures = [...(options.failures || [])];
15
+ const warnings = [...(options.warnings || [])];
16
+ const legacy = options.legacy || { status: "skipped", code: 0, stdout: "", stderr: "" };
17
+ const tasks = options.tasks || collectTasks(target, {
18
+ requireGeneratedScaffoldProvenance: options.requireGeneratedScaffoldProvenance === true,
19
+ taskPlanPaths: options.taskPlanPaths,
20
+ closeoutContent: options.closeoutContent,
21
+ });
22
+ const briefReady = tasks.filter((task) => task.briefSource === "standalone").length;
23
+ const briefMissing = tasks.length - briefReady;
24
+ const capabilityNames = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
25
+ for (const capability of detected) {
26
+ if (!capabilityNames.has(capability))
27
+ capabilityNames.set(capability, { name: capability, state: "configured" });
28
+ }
29
+ const cutoverCounters = taskCutoverCounters(tasks);
30
+ const fullCutoverEligible = validationMode === "validated" &&
31
+ failures.length === 0 &&
32
+ warnings.length === 0 &&
33
+ cutoverCounters.legacyVisualOnlyCount === 0 &&
34
+ cutoverCounters.unknownClassificationCount === 0 &&
35
+ cutoverCounters.weakBriefCount === 0 &&
36
+ cutoverCounters.missingCanonicalVisualMapCount === 0;
37
+ return {
38
+ project: {
39
+ name: path.basename(target.projectRoot),
40
+ root: `TARGET:${target.docsOnly ? toPosix(path.relative(target.projectRoot, target.docsRoot)) : "."}`,
41
+ docsOnly: target.docsOnly,
42
+ },
43
+ schemaVersion: 2,
44
+ generatedAt: options.generatedAt || new Date().toISOString(),
45
+ mode: registry.mode,
46
+ checkState: {
47
+ status: failures.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass",
48
+ validationMode,
49
+ failures: failures.length,
50
+ warnings: warnings.length,
51
+ details: { failures, warnings },
52
+ legacy,
53
+ },
54
+ git: gitState.summary,
55
+ summary: {
56
+ tasks: tasks.length,
57
+ briefCoverage: {
58
+ ready: briefReady,
59
+ missing: briefMissing,
60
+ total: tasks.length,
61
+ },
62
+ visualMapCoverage: {
63
+ canonical: tasks.filter((task) => task.visualMapSource === "canonical").length,
64
+ legacyOnly: cutoverCounters.legacyVisualOnlyCount,
65
+ missing: tasks.filter((task) => task.visualMapStatus === "missing").length,
66
+ total: tasks.length,
67
+ },
68
+ fullCutoverEligible,
69
+ legacyVisualOnlyCount: cutoverCounters.legacyVisualOnlyCount,
70
+ unknownClassificationCount: cutoverCounters.unknownClassificationCount,
71
+ weakBriefCount: cutoverCounters.weakBriefCount,
72
+ visualMapRequiredCount: cutoverCounters.visualMapRequiredCount,
73
+ missingCanonicalVisualMapCount: cutoverCounters.missingCanonicalVisualMapCount,
74
+ },
75
+ capabilities: [...capabilityNames.values()].map((capability) => ({
76
+ name: capability.name,
77
+ state: capability.state || "configured",
78
+ dependencyStatus: capabilityDefinitions[capability.name]?.dependencies.every((dependency) => capabilityNames.has(dependency))
79
+ ? "valid"
80
+ : "invalid",
81
+ warnings: capabilityWarnings.filter((warning) => warning.includes(capability.name)),
82
+ })),
83
+ tasks,
84
+ handoffs: tasks.flatMap((task) => task.handoffs || []),
85
+ recentActivity: tasks.slice(0, 8).map((task) => ({ at: new Date().toISOString(), type: "task", summary: task.title })),
86
+ };
87
+ }
@@ -1,24 +1,24 @@
1
+ import { implementationPhases } from "./phase-kind.mjs";
1
2
  export function renderDashboard(status) {
2
- const taskCards = status.tasks
3
- .map((task) => {
4
- const phases = task.phases
5
- .map(
6
- (phase) => `<div class="phase ${escapeHtml(phase.state)}">
7
- <div class="phase-top"><strong>${escapeHtml(phase.id)}</strong><span>${phase.completion}%</span></div>
3
+ const taskCards = status.tasks
4
+ .map((task) => {
5
+ const phases = task.phases
6
+ .map((phase) => `<div class="phase ${escapeHtml(phase.state)} ${escapeHtml(phase.kind || "execution")}">
7
+ <div class="phase-top"><strong>${escapeHtml(phase.id)}</strong><span>${escapeHtml(phase.kind || "execution")} · ${phase.completion}%</span></div>
8
8
  <div class="phase-output">${escapeHtml(phase.output)}</div>
9
9
  <div class="meter"><i style="width:${phase.completion}%"></i></div>
10
- <div class="muted">${escapeHtml(phase.state)} · evidence ${escapeHtml(phase.evidenceStatus)}</div>
11
- </div>`,
12
- )
13
- .join("");
14
- const risks = task.risks
15
- .map((risk) => `<span class="risk ${risk.open || risk.blocksRelease ? "open" : ""}">${escapeHtml(risk.severity)} ${escapeHtml(risk.summary)}</span>`)
16
- .join("");
17
- const evidence = task.evidence
18
- .map((item) => `<span class="evidence">${escapeHtml(item.type)} · ${escapeHtml(item.summary)}</span>`)
19
- .join("");
20
- const evidenceMeter = evidenceCompletion(task.phases);
21
- return `<section class="task">
10
+ <div class="muted">${escapeHtml(phase.state)} · actor ${escapeHtml(phase.actor || "agent")} · evidence ${escapeHtml(phase.evidenceStatus)}</div>
11
+ ${phase.exitCommand ? `<div class="muted">exit ${escapeHtml(phase.exitCommand)}</div>` : ""}
12
+ </div>`)
13
+ .join("");
14
+ const risks = task.risks
15
+ .map((risk) => `<span class="risk ${risk.open || risk.blocksRelease ? "open" : ""}">${escapeHtml(risk.severity)} ${escapeHtml(risk.summary)}</span>`)
16
+ .join("");
17
+ const evidence = task.evidence
18
+ .map((item) => `<span class="evidence">${escapeHtml(item.type)} · ${escapeHtml(item.summary)}</span>`)
19
+ .join("");
20
+ const evidenceMeter = evidenceCompletion(task.phases);
21
+ return `<section class="task">
22
22
  <div class="task-head">
23
23
  <div><h2>${escapeHtml(task.title)}</h2><p>${escapeHtml(task.path)}</p></div>
24
24
  <div class="score">${task.completion}%</div>
@@ -29,19 +29,19 @@ export function renderDashboard(status) {
29
29
  <div class="risks">${risks || '<span class="ok">No open visual risk</span>'}</div>
30
30
  </section>`;
31
31
  })
32
- .join("");
33
- const chips = status.capabilities
34
- .map((capability) => `<span class="chip ${escapeHtml(capability.state)}">${escapeHtml(capability.name)} · ${escapeHtml(capability.state)}</span>`)
35
- .join("");
36
- const failures = status.checkState.details.failures.map((failure) => `<li>${escapeHtml(failure)}</li>`).join("");
37
- const warnings = status.checkState.details.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
38
- const handoffs = status.handoffs
39
- .map((handoff) => `<span class="handoff">${escapeHtml(handoff.state)} · ${escapeHtml(handoff.summary)}</span>`)
40
- .join("");
41
- const activity = status.recentActivity
42
- .map((item) => `<li><strong>${escapeHtml(item.type)}</strong> ${escapeHtml(item.summary)}</li>`)
43
- .join("");
44
- return `<!doctype html>
32
+ .join("");
33
+ const chips = status.capabilities
34
+ .map((capability) => `<span class="chip ${escapeHtml(capability.state)}">${escapeHtml(capability.name)} · ${escapeHtml(capability.state)}</span>`)
35
+ .join("");
36
+ const failures = status.checkState.details.failures.map((failure) => `<li>${escapeHtml(failure)}</li>`).join("");
37
+ const warnings = status.checkState.details.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
38
+ const handoffs = status.handoffs
39
+ .map((handoff) => `<span class="handoff">${escapeHtml(handoff.state)} · ${escapeHtml(handoff.summary)}</span>`)
40
+ .join("");
41
+ const activity = status.recentActivity
42
+ .map((item) => `<li><strong>${escapeHtml(item.type)}</strong> ${escapeHtml(item.summary)}</li>`)
43
+ .join("");
44
+ return `<!doctype html>
45
45
  <html lang="zh-CN">
46
46
  <head>
47
47
  <meta charset="utf-8">
@@ -81,22 +81,23 @@ export function renderDashboard(status) {
81
81
  <section class="panel"><h2>Failures</h2><ul>${failures || "<li>None</li>"}</ul><h2>Warnings</h2><ul>${warnings || "<li>None</li>"}</ul></section>
82
82
  </main></body></html>`;
83
83
  }
84
-
85
84
  function escapeHtml(value) {
86
- return String(value ?? "")
87
- .replaceAll("&", "&amp;")
88
- .replaceAll("<", "&lt;")
89
- .replaceAll(">", "&gt;")
90
- .replaceAll('"', "&quot;");
85
+ return String(value ?? "")
86
+ .replaceAll("&", "&amp;")
87
+ .replaceAll("<", "&lt;")
88
+ .replaceAll(">", "&gt;")
89
+ .replaceAll('"', "&quot;");
91
90
  }
92
-
93
- function evidenceCompletion(phases) {
94
- const scored = phases.filter((phase) => phase.state !== "skipped");
95
- if (scored.length === 0) return 0;
96
- const score = scored.reduce((sum, phase) => {
97
- if (["present", "waived"].includes(phase.evidenceStatus)) return sum + 100;
98
- if (phase.evidenceStatus === "partial") return sum + 50;
99
- return sum;
100
- }, 0);
101
- return Math.round(score / scored.length);
91
+ function evidenceCompletion(phases = []) {
92
+ const scored = implementationPhases(phases);
93
+ if (scored.length === 0)
94
+ return 0;
95
+ const score = scored.reduce((sum, phase) => {
96
+ if (["present", "waived"].includes(String(phase.evidenceStatus)))
97
+ return sum + 100;
98
+ if (phase.evidenceStatus === "partial")
99
+ return sum + 50;
100
+ return sum;
101
+ }, 0);
102
+ return Math.round(score / scored.length);
102
103
  }