gsd-pi 2.82.0-dev.725028083 → 2.82.0-dev.9d5798940

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 (210) hide show
  1. package/README.md +3 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +3 -1
  4. package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
  5. package/dist/resources/extensions/cmux/index.js +5 -0
  6. package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +13 -6
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +232 -126
  9. package/dist/resources/extensions/gsd/auto-prompts.js +2 -2
  10. package/dist/resources/extensions/gsd/auto-recovery.js +31 -1
  11. package/dist/resources/extensions/gsd/auto-start.js +7 -3
  12. package/dist/resources/extensions/gsd/auto-verification.js +28 -22
  13. package/dist/resources/extensions/gsd/auto-worktree.js +96 -0
  14. package/dist/resources/extensions/gsd/auto.js +128 -52
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -1
  16. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
  18. package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
  19. package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
  20. package/dist/resources/extensions/gsd/commands/handlers/core.js +37 -0
  21. package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
  22. package/dist/resources/extensions/gsd/db/unit-dispatches.js +2 -2
  23. package/dist/resources/extensions/gsd/export-html.js +27 -425
  24. package/dist/resources/extensions/gsd/md-importer.js +1 -1
  25. package/dist/resources/extensions/gsd/migrate/command.js +5 -0
  26. package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
  27. package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
  28. package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
  29. package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
  30. package/dist/resources/extensions/gsd/native-git-bridge.js +8 -3
  31. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
  32. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -1
  33. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
  34. package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
  35. package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
  36. package/dist/resources/extensions/gsd/worktree-lifecycle.js +49 -9
  37. package/dist/resources/extensions/shared/html-shell.js +388 -0
  38. package/dist/resources/extensions/subagent/index.js +448 -78
  39. package/dist/resources/extensions/subagent/launch.js +77 -0
  40. package/dist/resources/extensions/subagent/run-store.js +148 -0
  41. package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
  42. package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
  43. package/dist/resources/extensions/visual-brief/index.js +5 -0
  44. package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
  45. package/dist/resources/extensions/visual-brief/prompts.js +140 -0
  46. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  47. package/dist/web/standalone/.next/BUILD_ID +1 -1
  48. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  49. package/dist/web/standalone/.next/build-manifest.json +3 -3
  50. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  51. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  52. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  62. package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  72. package/dist/web/standalone/.next/server/app/index.html +1 -1
  73. package/dist/web/standalone/.next/server/app/index.rsc +4 -7
  74. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -7
  76. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
  78. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  79. package/dist/web/standalone/.next/server/app/page.js +2 -2
  80. package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
  81. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  83. package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
  84. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  88. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  89. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  90. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  91. package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
  92. package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
  93. package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
  94. package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
  95. package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
  96. package/package.json +3 -3
  97. package/packages/contracts/dist/rpc.test.js +7 -0
  98. package/packages/contracts/dist/rpc.test.js.map +1 -1
  99. package/packages/contracts/dist/workflow.d.ts +21 -0
  100. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  101. package/packages/contracts/dist/workflow.js +24 -0
  102. package/packages/contracts/dist/workflow.js.map +1 -1
  103. package/packages/contracts/src/rpc.test.ts +8 -0
  104. package/packages/contracts/src/workflow.ts +24 -0
  105. package/packages/mcp-server/README.md +13 -4
  106. package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
  107. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  108. package/packages/mcp-server/dist/workflow-tools.js +80 -0
  109. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  110. package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
  111. package/packages/mcp-server/src/workflow-tools.ts +168 -0
  112. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  113. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  115. package/packages/pi-tui/dist/tui.js +5 -0
  116. package/packages/pi-tui/dist/tui.js.map +1 -1
  117. package/packages/pi-tui/src/tui.ts +6 -0
  118. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  119. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  120. package/src/resources/GSD-WORKFLOW.md +3 -1
  121. package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
  122. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
  123. package/src/resources/extensions/cmux/index.ts +6 -0
  124. package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
  125. package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
  126. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -6
  127. package/src/resources/extensions/gsd/auto-post-unit.ts +265 -138
  128. package/src/resources/extensions/gsd/auto-prompts.ts +2 -2
  129. package/src/resources/extensions/gsd/auto-recovery.ts +29 -0
  130. package/src/resources/extensions/gsd/auto-start.ts +7 -3
  131. package/src/resources/extensions/gsd/auto-verification.ts +36 -34
  132. package/src/resources/extensions/gsd/auto-worktree.ts +104 -0
  133. package/src/resources/extensions/gsd/auto.ts +136 -51
  134. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +6 -1
  135. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
  136. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
  137. package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
  138. package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
  139. package/src/resources/extensions/gsd/commands/handlers/core.ts +40 -0
  140. package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
  141. package/src/resources/extensions/gsd/db/unit-dispatches.ts +3 -3
  142. package/src/resources/extensions/gsd/export-html.ts +27 -427
  143. package/src/resources/extensions/gsd/md-importer.ts +1 -1
  144. package/src/resources/extensions/gsd/migrate/command.ts +5 -0
  145. package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
  146. package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
  147. package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
  148. package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
  149. package/src/resources/extensions/gsd/native-git-bridge.ts +8 -3
  150. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
  151. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
  152. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
  153. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
  154. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
  155. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
  156. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
  157. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
  158. package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
  159. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
  160. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
  161. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +57 -2
  162. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +39 -0
  163. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
  164. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
  165. package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
  166. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
  167. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
  168. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
  169. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +25 -0
  170. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
  171. package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
  172. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
  173. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +10 -0
  174. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
  175. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +39 -0
  176. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
  177. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
  178. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +64 -12
  179. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
  180. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +47 -0
  181. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  182. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
  183. package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
  184. package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
  185. package/src/resources/extensions/gsd/worktree-lifecycle.ts +54 -9
  186. package/src/resources/extensions/shared/html-shell.ts +412 -0
  187. package/src/resources/extensions/subagent/index.ts +567 -103
  188. package/src/resources/extensions/subagent/launch.ts +131 -0
  189. package/src/resources/extensions/subagent/run-store.ts +218 -0
  190. package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
  191. package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
  192. package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
  193. package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
  194. package/src/resources/extensions/visual-brief/index.ts +8 -0
  195. package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
  196. package/src/resources/extensions/visual-brief/prompts.ts +183 -0
  197. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
  198. package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
  199. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  200. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
  201. package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
  202. package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
  203. package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  204. package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  205. package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  206. package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  207. package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  208. package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  209. /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → BdZQhe8yKl6bdKLiXVEzh}/_buildManifest.js +0 -0
  210. /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → BdZQhe8yKl6bdKLiXVEzh}/_ssgManifest.js +0 -0
@@ -20,6 +20,7 @@
20
20
  * Design: Linear-inspired — restrained palette, geometric status, no emoji.
21
21
  */
22
22
  import { formatDateShort, formatDuration } from '../shared/format-utils.js';
23
+ import { esc, renderHtmlShell } from '../shared/html-shell.js';
23
24
  import { formatCost, formatTokenCount } from './metrics.js';
24
25
  export function generateHtmlReport(data, opts) {
25
26
  const generated = new Date().toISOString();
@@ -37,69 +38,35 @@ export function generateHtmlReport(data, opts) {
37
38
  buildStatsSection(data),
38
39
  buildDiscussionSection(data),
39
40
  ];
40
- const milestoneTag = opts.milestoneId
41
- ? ` <span class="sep">/</span> <span class="mono accent">${esc(opts.milestoneId)}</span>`
42
- : '';
41
+ const title = opts.milestoneId ? `${opts.projectName} / ${opts.milestoneId}` : opts.projectName;
43
42
  const backLink = opts.indexRelPath
44
43
  ? `<a class="back-link" href="${esc(opts.indexRelPath)}">All Reports</a>`
45
44
  : '';
46
- return `<!DOCTYPE html>
47
- <html lang="en">
48
- <head>
49
- <meta charset="UTF-8">
50
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
51
- <title>GSD Report — ${esc(opts.projectName)}${opts.milestoneId ? ` — ${esc(opts.milestoneId)}` : ''}</title>
52
- <style>${CSS}</style>
53
- </head>
54
- <body>
55
- <header>
56
- <div class="header-inner">
57
- <div class="branding">
58
- <span class="logo">GSD</span>
59
- <span class="version">v${esc(opts.gsdVersion)}</span>
60
- </div>
61
- <div class="header-meta">
62
- <h1>${esc(opts.projectName)}${milestoneTag}</h1>
63
- <span class="header-path">${esc(opts.projectPath)}</span>
64
- </div>
65
- <div class="header-right">
66
- ${backLink}
67
- <div class="generated">${formatDateLong(generated)}</div>
68
- </div>
69
- </div>
70
- </header>
71
- <nav class="toc" aria-label="Report sections">
72
- <ul>
73
- <li><a href="#summary">Summary</a></li>
74
- <li><a href="#blockers">Blockers</a></li>
75
- <li><a href="#progress">Progress</a></li>
76
- <li><a href="#timeline">Timeline</a></li>
77
- <li><a href="#depgraph">Dependencies</a></li>
78
- <li><a href="#metrics">Metrics</a></li>
79
- <li><a href="#health">Health</a></li>
80
- <li><a href="#changelog">Changelog</a></li>
81
- <li><a href="#knowledge">Knowledge</a></li>
82
- <li><a href="#captures">Captures</a></li>
83
- <li><a href="#stats">Artifacts</a></li>
84
- <li><a href="#discussion">Planning</a></li>
85
- </ul>
86
- </nav>
87
- <main>
88
- ${sections.join('\n')}
89
- </main>
90
- <footer>
91
- <div class="footer-inner">
92
- <span>GSD v${esc(opts.gsdVersion)}</span>
93
- <span class="sep">/</span>
94
- <span>${esc(opts.projectName)}</span>
95
- ${opts.milestoneId ? `<span class="sep">/</span><span class="mono">${esc(opts.milestoneId)}</span>` : ''}
96
- <span class="sep">/</span>
97
- <span>${formatDateLong(generated)}</span>
98
- </div>
99
- </footer>
100
- <script>${JS}</script>
101
- </body>
102
- </html>`;
45
+ return renderHtmlShell({
46
+ title,
47
+ documentTitle: `GSD Report — ${opts.projectName}${opts.milestoneId ? ` — ${opts.milestoneId}` : ''}`,
48
+ subtitle: opts.projectPath,
49
+ kind: 'Report',
50
+ version: opts.gsdVersion,
51
+ generatedAt: generated,
52
+ headerActionsHtml: backLink,
53
+ footerNote: opts.milestoneId ? `${opts.projectName} / ${opts.milestoneId}` : opts.projectName,
54
+ toc: [
55
+ { href: '#summary', label: 'Summary' },
56
+ { href: '#blockers', label: 'Blockers' },
57
+ { href: '#progress', label: 'Progress' },
58
+ { href: '#timeline', label: 'Timeline' },
59
+ { href: '#depgraph', label: 'Dependencies' },
60
+ { href: '#metrics', label: 'Metrics' },
61
+ { href: '#health', label: 'Health' },
62
+ { href: '#changelog', label: 'Changelog' },
63
+ { href: '#knowledge', label: 'Knowledge' },
64
+ { href: '#captures', label: 'Captures' },
65
+ { href: '#stats', label: 'Artifacts' },
66
+ { href: '#discussion', label: 'Planning' },
67
+ ],
68
+ mainHtml: sections.join('\n'),
69
+ });
103
70
  }
104
71
  // ─── Section: Summary ─────────────────────────────────────────────────────────
105
72
  function buildSummarySection(data, opts, _generated) {
@@ -904,368 +871,3 @@ function hRow(label, value, status) {
904
871
  }
905
872
  function shortModel(m) { return m.replace(/^claude-/, '').replace(/^anthropic\//, ''); }
906
873
  function truncStr(s, n) { return s.length > n ? s.slice(0, n - 1) + '\u2026' : s; }
907
- function formatDateLong(iso) {
908
- try {
909
- const d = new Date(iso);
910
- return d.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' });
911
- }
912
- catch {
913
- return iso;
914
- }
915
- }
916
- function esc(s) {
917
- if (s == null)
918
- return '';
919
- return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
920
- }
921
- // ─── CSS ───────────────────────────────────────────────────────────────────────
922
- // Linear-inspired: restrained palette, one accent, no emoji, no gradients.
923
- const CSS = `
924
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
925
- :root{
926
- --bg-0:#0f1115;--bg-1:#16181d;--bg-2:#1e2028;--bg-3:#272a33;
927
- --border-1:#2b2e38;--border-2:#3b3f4c;
928
- --text-0:#ededef;--text-1:#a1a1aa;--text-2:#71717a;
929
- --accent:#5e6ad2;--accent-subtle:rgba(94,106,210,.12);
930
- --ok:#22c55e;--ok-subtle:rgba(34,197,94,.12);--warn:#ef4444;--caution:#eab308;
931
- /* Chart palette — 6 hues for bar charts */
932
- --c0:#5e6ad2;--c1:#e5796d;--c2:#14b8a6;--c3:#a78bfa;--c4:#f59e0b;--c5:#10b981;
933
- /* Token breakdown — 4 distinct hues */
934
- --tk-input:#5e6ad2;--tk-output:#e5796d;--tk-cache-r:#2dd4bf;--tk-cache-w:#64748b;
935
- --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
936
- --mono:'JetBrains Mono','Fira Code',ui-monospace,SFMono-Regular,monospace;
937
- }
938
- html{scroll-behavior:smooth;font-size:13px}
939
- body{background:var(--bg-0);color:var(--text-0);font-family:var(--font);line-height:1.6;-webkit-font-smoothing:antialiased}
940
- a{color:var(--accent);text-decoration:none}
941
- a:hover{text-decoration:underline}
942
- code{font-family:var(--mono);font-size:12px;background:var(--bg-3);padding:1px 5px;border-radius:3px}
943
- .mono{font-family:var(--mono);font-size:12px}
944
- .muted{color:var(--text-2)}
945
- .accent{color:var(--accent)}
946
- .sep{color:var(--border-2);margin:0 4px}
947
- .empty{color:var(--text-2);padding:8px 0;font-size:13px}
948
- .indent{padding-left:12px}
949
- .num{font-variant-numeric:tabular-nums;text-align:right}
950
-
951
- /* Status dots — geometric, no emoji */
952
- .dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0;vertical-align:middle}
953
- .dot-sm{width:6px;height:6px}
954
- .dot-complete{background:var(--ok);opacity:.6}
955
- .dot-active{background:var(--accent)}
956
- .dot-pending{background:transparent;border:1.5px solid var(--border-2)}
957
- .dot-parked{background:var(--warn);opacity:.5}
958
-
959
- /* Header */
960
- header{background:var(--bg-1);border-bottom:1px solid var(--border-1);padding:12px 32px;position:sticky;top:0;z-index:200}
961
- .header-inner{display:flex;align-items:center;gap:16px;max-width:1280px;margin:0 auto}
962
- .branding{display:flex;align-items:baseline;gap:6px;flex-shrink:0}
963
- .logo{font-size:18px;font-weight:800;letter-spacing:-.5px;color:var(--text-0)}
964
- .version{font-size:10px;color:var(--text-2);font-family:var(--mono)}
965
- .header-meta{flex:1;min-width:0}
966
- .header-meta h1{font-size:15px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
967
- .header-path{font-size:11px;color:var(--text-2);font-family:var(--mono);display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
968
- .header-right{text-align:right;flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}
969
- .generated{font-size:11px;color:var(--text-2)}
970
- .back-link{font-size:12px;color:var(--text-1)}
971
- .back-link:hover{color:var(--accent)}
972
-
973
- /* TOC nav */
974
- .toc{background:var(--bg-1);border-bottom:1px solid var(--border-1);overflow-x:auto}
975
- .toc ul{display:flex;list-style:none;max-width:1280px;margin:0 auto;padding:0 32px}
976
- .toc a{display:inline-block;padding:8px 12px;color:var(--text-2);font-size:12px;font-weight:500;border-bottom:2px solid transparent;transition:color .12s,border-color .12s;white-space:nowrap;text-decoration:none}
977
- .toc a:hover{color:var(--text-0);border-bottom-color:var(--border-2)}
978
- .toc a.active{color:var(--text-0);border-bottom-color:var(--accent)}
979
-
980
- /* Layout */
981
- main{max-width:1280px;margin:0 auto;padding:32px;display:flex;flex-direction:column;gap:48px}
982
- section{scroll-margin-top:82px}
983
- section>h2{font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-1);margin-bottom:16px;padding-bottom:8px;border-bottom:1px solid var(--border-1);display:flex;align-items:center;gap:8px}
984
- h3{font-size:13px;font-weight:600;color:var(--text-1);margin:20px 0 8px}
985
- .count{font-size:11px;font-weight:500;color:var(--text-2);background:var(--bg-3);border-radius:3px;padding:1px 6px}
986
- .count-warn{color:var(--caution)}
987
-
988
- /* KV grid (stats/metrics) */
989
- .kv-grid{display:flex;flex-wrap:wrap;gap:1px;background:var(--border-1);border:1px solid var(--border-1);border-radius:4px;overflow:hidden;margin-bottom:16px}
990
- .kv{background:var(--bg-1);padding:10px 16px;display:flex;flex-direction:column;gap:2px;min-width:110px;flex:1}
991
- .kv-val{font-size:18px;font-weight:600;color:var(--text-0);font-variant-numeric:tabular-nums}
992
- .kv-lbl{font-size:10px;color:var(--text-2);text-transform:uppercase;letter-spacing:.4px}
993
-
994
- /* Progress bar */
995
- .progress-wrap{display:flex;align-items:center;gap:10px;margin-bottom:12px}
996
- .progress-track{flex:1;height:4px;background:var(--bg-3);border-radius:2px;overflow:hidden}
997
- .progress-fill{height:100%;background:var(--accent);border-radius:2px}
998
- .progress-label{font-size:12px;font-weight:600;color:var(--text-1);min-width:40px;text-align:right}
999
- .active-info{font-size:12px;color:var(--text-1);margin-bottom:4px}
1000
- .activity-line{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-1);padding:6px 0}
1001
-
1002
- /* Tables */
1003
- .tbl{width:100%;border-collapse:collapse;font-size:12px}
1004
- .tbl th{color:var(--text-2);font-weight:500;padding:6px 12px;text-align:left;border-bottom:1px solid var(--border-1);font-size:11px;text-transform:uppercase;letter-spacing:.3px;white-space:nowrap}
1005
- .tbl td{padding:6px 12px;border-bottom:1px solid var(--border-1);vertical-align:top}
1006
- .tbl tr:last-child td{border-bottom:none}
1007
- .tbl tbody tr:hover td{background:var(--accent-subtle)}
1008
- .tbl-kv td:first-child{color:var(--text-2);width:180px}
1009
- .table-scroll{overflow-x:auto;border:1px solid var(--border-1);border-radius:4px}
1010
- .table-scroll .tbl{border:none}
1011
-
1012
- /* Health */
1013
- .h-ok td:first-child{color:var(--text-1)}
1014
- .h-caution td{color:var(--caution)}
1015
- .h-warn td{color:var(--warn)}
1016
-
1017
- /* Labels */
1018
- .label{font-size:10px;font-weight:500;color:var(--accent);text-transform:uppercase;letter-spacing:.4px}
1019
- .risk{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;flex-shrink:0}
1020
- .risk-low{color:var(--text-2)}
1021
- .risk-medium{color:var(--caution)}
1022
- .risk-high{color:var(--warn)}
1023
- .risk-unknown{color:var(--text-2)}
1024
-
1025
- /* Tags */
1026
- .tag-row{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
1027
- .tag{font-size:11px;font-family:var(--mono);color:var(--text-2);background:var(--bg-3);border-radius:3px;padding:1px 6px}
1028
-
1029
- /* Verification */
1030
- .verif{font-size:12px;color:var(--text-1);padding:4px 0;margin-bottom:6px}
1031
- .verif-blocker{color:var(--warn)}
1032
-
1033
- /* Detail blocks */
1034
- .detail-block{font-size:12px;color:var(--text-2);margin-bottom:6px}
1035
- .detail-label{font-weight:600;color:var(--text-1);display:block;margin-bottom:2px}
1036
- .detail-block ul{padding-left:16px;margin-top:2px}
1037
- .detail-block li{margin-bottom:1px}
1038
-
1039
- /* Progress tree */
1040
- .ms-block{border:1px solid var(--border-1);border-radius:4px;overflow:hidden;margin-bottom:8px}
1041
- .ms-summary{display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;list-style:none;background:var(--bg-1);user-select:none;font-size:13px}
1042
- .ms-summary:hover{background:var(--bg-2)}
1043
- .ms-summary::-webkit-details-marker{display:none}
1044
- .ms-id{font-weight:600}
1045
- .ms-title{flex:1;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1046
- .ms-body{padding:6px 12px 8px 24px;display:flex;flex-direction:column;gap:4px}
1047
-
1048
- .sl-block{border:1px solid var(--border-1);border-radius:3px;overflow:hidden}
1049
- .sl-summary{display:flex;align-items:center;gap:6px;padding:6px 10px;cursor:pointer;list-style:none;background:var(--bg-2);font-size:12px;user-select:none}
1050
- .sl-summary:hover{background:var(--bg-3)}
1051
- .sl-summary::-webkit-details-marker{display:none}
1052
- .sl-crit{border-left:2px solid var(--accent)}
1053
- .sl-deps::before{content:'\\2190 ';color:var(--border-2)}
1054
- .sl-detail{padding:8px 12px;background:var(--bg-0);border-top:1px solid var(--border-1)}
1055
-
1056
- .task-list{list-style:none;padding:4px 0 0;display:flex;flex-direction:column;gap:2px}
1057
- .task-row{display:flex;align-items:center;gap:6px;font-size:12px;padding:3px 6px;border-radius:2px}
1058
-
1059
- /* Dep graph */
1060
- .dep-block{margin-bottom:28px}
1061
- .dep-legend{display:flex;gap:14px;font-size:12px;color:var(--text-2);margin-bottom:8px;align-items:center}
1062
- .dep-legend span{display:flex;align-items:center;gap:4px}
1063
- .dep-wrap{overflow-x:auto;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:16px}
1064
- .dep-svg{display:block}
1065
- .edge{fill:none;stroke:var(--border-2);stroke-width:1.5}
1066
- .edge-crit{stroke:var(--accent);stroke-width:2}
1067
- .node rect{fill:var(--bg-2);stroke:var(--border-2);stroke-width:1}
1068
- .n-done rect{fill:var(--ok-subtle);stroke:rgba(34,197,94,.4)}
1069
- .n-active rect{fill:var(--accent-subtle);stroke:var(--accent)}
1070
- .n-crit rect{stroke:var(--accent)!important;stroke-width:1.5!important}
1071
- .n-id{font-family:var(--mono);font-size:10px;fill:var(--text-1);font-weight:600;text-anchor:middle}
1072
- .n-title{font-size:9px;fill:var(--text-2);text-anchor:middle}
1073
- .n-active .n-id{fill:var(--accent)}
1074
-
1075
- /* Metrics */
1076
- .token-block{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px;margin-bottom:16px}
1077
- .token-bar{display:flex;height:16px;border-radius:2px;overflow:hidden;gap:1px;margin-bottom:8px}
1078
- .tseg{height:100%;min-width:2px}
1079
- .seg-1{background:var(--tk-input)}
1080
- .seg-2{background:var(--tk-output)}
1081
- .seg-3{background:var(--tk-cache-r)}
1082
- .seg-4{background:var(--tk-cache-w)}
1083
- .token-legend{display:flex;flex-wrap:wrap;gap:12px}
1084
- .leg-item{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text-2)}
1085
- .leg-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
1086
- .chart-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin-bottom:16px}
1087
- .chart-block{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px}
1088
- .bar-row{display:grid;grid-template-columns:120px 1fr 68px;align-items:center;gap:6px;margin-bottom:2px}
1089
- .bar-lbl{font-size:12px;color:var(--text-2);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1090
- .bar-track{height:14px;background:var(--bg-3);border-radius:2px;overflow:hidden}
1091
- .bar-fill{height:100%;border-radius:2px;background:var(--c0)}
1092
- .bar-c0{background:var(--c0)}.bar-c1{background:var(--c1)}.bar-c2{background:var(--c2)}
1093
- .bar-c3{background:var(--c3)}.bar-c4{background:var(--c4)}.bar-c5{background:var(--c5)}
1094
- .bar-val{font-size:11px;font-variant-numeric:tabular-nums;color:var(--text-1)}
1095
- .bar-sub{font-size:10px;color:var(--text-2);padding-left:128px;margin-bottom:6px}
1096
-
1097
- /* Changelog */
1098
- .cl-entry{border-bottom:1px solid var(--border-1);padding:12px 0}
1099
- .cl-entry:last-child{border-bottom:none}
1100
- .cl-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}
1101
- .cl-title{flex:1;font-weight:500}
1102
- .cl-date{margin-left:auto;white-space:nowrap}
1103
- .cl-liner{font-size:13px;color:var(--text-1);margin-bottom:6px}
1104
- .files-detail summary{font-size:12px;cursor:pointer}
1105
- .file-list{list-style:none;padding-left:10px;margin-top:4px;display:flex;flex-direction:column;gap:2px}
1106
- .file-list li{font-size:12px;color:var(--text-1)}
1107
-
1108
- /* Footer */
1109
- footer{border-top:1px solid var(--border-1);padding:20px 32px;margin-top:40px}
1110
- .footer-inner{display:flex;align-items:center;gap:6px;justify-content:center;font-size:11px;color:var(--text-2)}
1111
-
1112
- /* Executive summary & ETA */
1113
- .exec-summary{font-size:13px;color:var(--text-1);margin-bottom:12px;line-height:1.7}
1114
- .eta-line{font-size:12px;color:var(--accent);margin-top:4px}
1115
-
1116
- /* Cost over time chart */
1117
- .cost-svg{display:block;margin:8px 0;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px}
1118
- .cost-line{fill:none;stroke:var(--accent);stroke-width:2}
1119
- .cost-area{fill:var(--accent-subtle);stroke:none}
1120
- .cost-axis{fill:var(--text-2);font-family:var(--mono);font-size:10px}
1121
- .cost-grid{stroke:var(--border-1);stroke-width:1;stroke-dasharray:4,4}
1122
-
1123
- /* Budget burndown */
1124
- .burndown-wrap{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px;margin-bottom:16px}
1125
- .burndown-bar{display:flex;height:20px;border-radius:3px;overflow:hidden;gap:1px;margin-bottom:8px}
1126
- .burndown-spent{background:var(--accent);height:100%}
1127
- .burndown-projected{background:var(--caution);height:100%;opacity:.6}
1128
- .burndown-overshoot{background:var(--warn);height:100%;opacity:.7}
1129
- .burndown-legend{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-2)}
1130
- .burndown-legend span{display:flex;align-items:center;gap:4px}
1131
- .burndown-dot{display:inline-block;width:8px;height:8px;border-radius:2px}
1132
-
1133
- /* Blockers */
1134
- .blocker-card{border-left:3px solid var(--warn);background:var(--bg-1);border-radius:0 4px 4px 0;padding:10px 14px;margin-bottom:8px}
1135
- .blocker-id{font-family:var(--mono);font-size:12px;color:var(--warn);margin-bottom:2px}
1136
- .blocker-text{font-size:12px;color:var(--text-1)}
1137
- .blocker-risk{font-size:11px;color:var(--caution);margin-top:2px}
1138
-
1139
- /* Gantt */
1140
- .gantt-wrap{overflow-x:auto;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:16px;margin-top:16px}
1141
- .gantt-svg{display:block}
1142
- .gantt-bar-done{fill:var(--ok);opacity:.7}
1143
- .gantt-bar-active{fill:var(--accent)}
1144
- .gantt-bar-pending{fill:var(--border-2)}
1145
- .gantt-label{fill:var(--text-2);font-family:var(--mono);font-size:10px}
1146
- .gantt-axis{fill:var(--text-2);font-family:var(--mono);font-size:9px}
1147
-
1148
- /* Interactive */
1149
- .tl-filter{display:block;width:100%;padding:6px 10px;margin-bottom:8px;background:var(--bg-2);border:1px solid var(--border-1);border-radius:4px;color:var(--text-0);font-size:12px;font-family:var(--font);outline:none}
1150
- .tl-filter:focus{border-color:var(--accent)}
1151
- .tl-filter::placeholder{color:var(--text-2)}
1152
- .sec-toggle{background:none;border:1px solid var(--border-2);color:var(--text-2);width:20px;height:20px;border-radius:3px;cursor:pointer;font-size:14px;line-height:1;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}
1153
- .sec-toggle:hover{border-color:var(--text-1);color:var(--text-1)}
1154
- .theme-toggle{background:var(--bg-3);border:1px solid var(--border-2);color:var(--text-1);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:11px;font-family:var(--font)}
1155
- .theme-toggle:hover{border-color:var(--accent);color:var(--accent)}
1156
-
1157
- /* Light theme */
1158
- .light-theme{--bg-0:#fff;--bg-1:#fafafa;--bg-2:#f5f5f5;--bg-3:#ebebeb;--border-1:#e5e5e5;--border-2:#d4d4d4;--text-0:#1a1a1a;--text-1:#525252;--text-2:#a3a3a3;--accent:#4f46e5;--accent-subtle:rgba(79,70,229,.08);--ok:#16a34a;--ok-subtle:rgba(22,163,74,.08);--warn:#dc2626;--caution:#ca8a04;--c0:#4f46e5;--c1:#dc2626;--c2:#0d9488;--c3:#7c3aed;--c4:#d97706;--c5:#059669;--tk-input:#4f46e5;--tk-output:#dc2626;--tk-cache-r:#0d9488;--tk-cache-w:#64748b}
1159
-
1160
- /* Responsive */
1161
- @media(max-width:768px){
1162
- header{padding:10px 16px}
1163
- .header-inner{flex-wrap:wrap;gap:8px}
1164
- .header-meta h1{font-size:13px}
1165
- main{padding:16px}
1166
- .kv-grid{gap:1px}
1167
- .kv{min-width:80px;padding:8px 10px}
1168
- .kv-val{font-size:14px}
1169
- .chart-row{grid-template-columns:1fr}
1170
- .toc ul{padding:0 16px}
1171
- .toc a{padding:6px 8px;font-size:11px}
1172
- .bar-row{grid-template-columns:80px 1fr 56px}
1173
- .ms-body{padding-left:12px}
1174
- }
1175
- @media(max-width:480px){
1176
- .kv{min-width:60px;padding:6px 8px}
1177
- .kv-val{font-size:12px}
1178
- .kv-lbl{font-size:9px}
1179
- .bar-row{grid-template-columns:60px 1fr 48px}
1180
- .bar-lbl{font-size:10px}
1181
- .toc ul{flex-wrap:wrap}
1182
- .header-right{display:none}
1183
- .gantt-wrap{overflow-x:auto}
1184
- }
1185
-
1186
- /* Print */
1187
- @media print{
1188
- header,nav.toc{position:static}
1189
- body{background:#fff;color:#1a1a1a}
1190
- :root{--bg-0:#fff;--bg-1:#fafafa;--bg-2:#f5f5f5;--bg-3:#ebebeb;--border-1:#e5e5e5;--border-2:#d4d4d4;--text-0:#1a1a1a;--text-1:#525252;--text-2:#a3a3a3;--accent:#4f46e5;--ok:#16a34a;--ok-subtle:rgba(22,163,74,.08);--c0:#4f46e5;--c1:#dc2626;--c2:#0d9488;--c3:#7c3aed;--c4:#d97706;--c5:#059669;--tk-input:#4f46e5;--tk-output:#dc2626;--tk-cache-r:#0d9488;--tk-cache-w:#64748b}
1191
- section{page-break-inside:avoid}
1192
- .table-scroll{overflow:visible}
1193
- }
1194
- `;
1195
- // ─── JS ────────────────────────────────────────────────────────────────────────
1196
- const JS = `
1197
- (function(){
1198
- const sections=document.querySelectorAll('section[id]');
1199
- const links=document.querySelectorAll('.toc a');
1200
- if(!sections.length||!links.length)return;
1201
- const obs=new IntersectionObserver(entries=>{
1202
- for(const e of entries){
1203
- if(!e.isIntersecting)continue;
1204
- for(const l of links)l.classList.remove('active');
1205
- const a=document.querySelector('.toc a[href="#'+e.target.id+'"]');
1206
- if(a)a.classList.add('active');
1207
- }
1208
- },{rootMargin:'-10% 0px -80% 0px',threshold:0});
1209
- for(const s of sections)obs.observe(s);
1210
- })();
1211
- (function(){
1212
- var tl=document.getElementById('timeline');
1213
- if(!tl)return;
1214
- var table=tl.querySelector('.tbl');
1215
- if(!table)return;
1216
- var input=document.createElement('input');
1217
- input.className='tl-filter';
1218
- input.placeholder='Filter timeline\\u2026';
1219
- input.type='text';
1220
- table.parentNode.insertBefore(input,table);
1221
- var rows=table.querySelectorAll('tbody tr');
1222
- input.addEventListener('input',function(){
1223
- var q=this.value.toLowerCase();
1224
- for(var i=0;i<rows.length;i++){
1225
- rows[i].style.display=rows[i].textContent.toLowerCase().indexOf(q)>-1?'':'none';
1226
- }
1227
- });
1228
- })();
1229
- (function(){
1230
- var saved=JSON.parse(localStorage.getItem('gsd-collapsed')||'{}');
1231
- document.querySelectorAll('section[id]').forEach(function(sec){
1232
- var h2=sec.querySelector('h2');
1233
- if(!h2)return;
1234
- var btn=document.createElement('button');
1235
- btn.className='sec-toggle';
1236
- btn.textContent=saved[sec.id]?'+':'-';
1237
- btn.setAttribute('aria-label','Toggle section');
1238
- h2.prepend(btn);
1239
- if(saved[sec.id])toggleSection(sec,true);
1240
- btn.addEventListener('click',function(e){
1241
- e.preventDefault();
1242
- var collapsed=btn.textContent==='-';
1243
- toggleSection(sec,collapsed);
1244
- btn.textContent=collapsed?'+':'-';
1245
- saved[sec.id]=collapsed;
1246
- localStorage.setItem('gsd-collapsed',JSON.stringify(saved));
1247
- });
1248
- });
1249
- function toggleSection(sec,hide){
1250
- var children=sec.children;
1251
- for(var i=0;i<children.length;i++){
1252
- if(children[i].tagName!=='H2')children[i].style.display=hide?'none':'';
1253
- }
1254
- }
1255
- })();
1256
- (function(){
1257
- var hr=document.querySelector('.header-right');
1258
- if(!hr)return;
1259
- var btn=document.createElement('button');
1260
- btn.className='theme-toggle';
1261
- btn.textContent=localStorage.getItem('gsd-theme')==='light'?'Dark':'Light';
1262
- if(localStorage.getItem('gsd-theme')==='light')document.documentElement.classList.add('light-theme');
1263
- btn.addEventListener('click',function(){
1264
- document.documentElement.classList.toggle('light-theme');
1265
- var isLight=document.documentElement.classList.contains('light-theme');
1266
- btn.textContent=isLight?'Dark':'Light';
1267
- localStorage.setItem('gsd-theme',isLight?'light':'dark');
1268
- });
1269
- hr.prepend(btn);
1270
- })();
1271
- `;
@@ -256,7 +256,7 @@ function importRequirements(gsdDir) {
256
256
  }
257
257
  // ─── Hierarchy Artifact Walker ─────────────────────────────────────────────
258
258
  /** Artifact suffixes to look for at each hierarchy level */
259
- const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT'];
259
+ const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT', 'SUMMARY', 'VALIDATION'];
260
260
  const SLICE_SUFFIXES = ['PLAN', 'SUMMARY', 'RESEARCH', 'CONTEXT', 'ASSESSMENT', 'UAT'];
261
261
  const TASK_SUFFIXES = ['PLAN', 'SUMMARY', 'CONTINUE', 'CONTEXT', 'RESEARCH'];
262
262
  /**
@@ -21,6 +21,9 @@ import { migrateFromMarkdown } from "../md-importer.js";
21
21
  import { invalidateStateCache } from "../state.js";
22
22
  function assertMigrationImportMatchesPreview(imported, preview) {
23
23
  const mismatches = [];
24
+ if (imported.decisions !== preview.decisions.total) {
25
+ mismatches.push(`decisions ${imported.decisions}/${preview.decisions.total}`);
26
+ }
24
27
  if (imported.hierarchy.milestones !== preview.milestoneCount) {
25
28
  mismatches.push(`milestones ${imported.hierarchy.milestones}/${preview.milestoneCount}`);
26
29
  }
@@ -55,6 +58,7 @@ export async function importWrittenMigrationToDb(basePath, preview) {
55
58
  /** Format preview stats for embedding in the review prompt. */
56
59
  function formatPreviewStats(preview) {
57
60
  const lines = [
61
+ `- Decisions: ${preview.decisions.total}`,
58
62
  `- Milestones: ${preview.milestoneCount}`,
59
63
  `- Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
60
64
  `- Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
@@ -124,6 +128,7 @@ export async function handleMigrate(args, ctx, pi) {
124
128
  const preview = generatePreview(project);
125
129
  // ── Build preview text ─────────────────────────────────────────────────────
126
130
  const lines = [
131
+ `Decisions: ${preview.decisions.total}`,
127
132
  `Milestones: ${preview.milestoneCount}`,
128
133
  `Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
129
134
  `Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
@@ -1,5 +1,11 @@
1
1
  // GSD Migration Preview — Pre-write statistics
2
2
  // Pure function, no I/O. Computes counts from a GSDProject.
3
+ function countCanonicalDecisionRows(content) {
4
+ return content
5
+ .split('\n')
6
+ .filter((line) => /^\|\s*D\d+\s*\|/.test(line.trim()))
7
+ .length;
8
+ }
3
9
  /**
4
10
  * Compute pre-write statistics from a GSDProject without performing I/O.
5
11
  * Used to show the user what a migration will produce before writing anything.
@@ -35,6 +41,9 @@ export function generatePreview(project) {
35
41
  reqCounts.total++;
36
42
  }
37
43
  return {
44
+ decisions: {
45
+ total: countCanonicalDecisionRows(project.decisionsContent),
46
+ },
38
47
  milestoneCount: project.milestones.length,
39
48
  totalSlices,
40
49
  totalTasks,
@@ -187,16 +187,48 @@ function normalizeStatus(status) {
187
187
  return 'validated';
188
188
  return 'active';
189
189
  }
190
+ function normalizeRequirementId(id) {
191
+ const match = id.trim().match(/^R(\d+)$/i);
192
+ if (!match)
193
+ return null;
194
+ return `R${match[1].padStart(3, '0')}`;
195
+ }
190
196
  function mapRequirements(reqs) {
191
197
  let autoId = 0;
198
+ const reservedIds = new Set(reqs
199
+ .map((req) => normalizeRequirementId(req.id))
200
+ .filter((id) => id !== null));
201
+ const usedIds = new Set();
202
+ function nextRequirementId() {
203
+ let id = '';
204
+ do {
205
+ autoId++;
206
+ id = padId('R', autoId, 3);
207
+ } while (usedIds.has(id) || reservedIds.has(id));
208
+ usedIds.add(id);
209
+ return id;
210
+ }
192
211
  return reqs.map((req) => {
193
- autoId++;
212
+ const originalId = req.id.trim();
213
+ const canonicalId = normalizeRequirementId(originalId);
214
+ let id;
215
+ let description = req.description;
216
+ if (canonicalId && !usedIds.has(canonicalId)) {
217
+ id = canonicalId;
218
+ usedIds.add(id);
219
+ }
220
+ else {
221
+ id = nextRequirementId();
222
+ if (originalId) {
223
+ description = `Legacy ID: ${originalId}\n\n${description}`;
224
+ }
225
+ }
194
226
  return {
195
- id: req.id && req.id.trim() !== '' ? req.id : padId('R', autoId, 3),
227
+ id,
196
228
  title: req.title,
197
229
  class: 'core-capability',
198
230
  status: normalizeStatus(req.status),
199
- description: req.description,
231
+ description,
200
232
  source: 'inferred',
201
233
  primarySlice: 'none yet',
202
234
  };
@@ -233,7 +265,22 @@ function deriveDecisions(parsed) {
233
265
  }
234
266
  if (decisions.length === 0)
235
267
  return '';
236
- return decisions.map((d) => `- ${d}`).join('\n');
268
+ const lines = [
269
+ '# Decisions Register',
270
+ '',
271
+ '<!-- Append-only. Never edit or remove existing rows.',
272
+ ' To reverse a decision, add a new row that supersedes it.',
273
+ ' Read this file at the start of any planning or research phase. -->',
274
+ '',
275
+ '| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |',
276
+ '|---|------|-------|----------|--------|-----------|------------|---------|',
277
+ ];
278
+ decisions.forEach((decision, index) => {
279
+ const id = padId('D', index + 1, 3);
280
+ const escaped = decision.replace(/\|/g, '\\|');
281
+ lines.push(`| ${id} | migration | migrated-summary | ${escaped} | ${escaped} | Migrated from legacy summary key-decisions | Yes | agent |`);
282
+ });
283
+ return lines.join('\n') + '\n';
237
284
  }
238
285
  // ─── Main Entry Point ──────────────────────────────────────────────────────
239
286
  export function transformToGSD(parsed) {
@@ -296,7 +296,17 @@ export function formatProject(content) {
296
296
  */
297
297
  export function formatDecisions(content) {
298
298
  if (!content || !content.trim()) {
299
- return '# Decisions\n\n<!-- Append-only register of architectural and pattern decisions -->\n\n| ID | Decision | Rationale | Date |\n|----|----------|-----------|------|\n';
299
+ return [
300
+ '# Decisions Register',
301
+ '',
302
+ '<!-- Append-only. Never edit or remove existing rows.',
303
+ ' To reverse a decision, add a new row that supersedes it.',
304
+ ' Read this file at the start of any planning or research phase. -->',
305
+ '',
306
+ '| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |',
307
+ '|---|------|-------|----------|--------|-----------|------------|---------|',
308
+ '',
309
+ ].join('\n');
300
310
  }
301
311
  return content.endsWith('\n') ? content : content + '\n';
302
312
  }