gsd-pi 2.82.0-dev.2841a1e44 → 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 (118) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
  3. package/dist/resources/extensions/gsd/auto-dispatch.js +13 -6
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +69 -8
  5. package/dist/resources/extensions/gsd/auto-recovery.js +31 -1
  6. package/dist/resources/extensions/gsd/auto-start.js +7 -3
  7. package/dist/resources/extensions/gsd/auto-worktree.js +96 -0
  8. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -1
  9. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +13 -1
  10. package/dist/resources/extensions/gsd/commands/handlers/core.js +17 -1
  11. package/dist/resources/extensions/gsd/db/unit-dispatches.js +2 -2
  12. package/dist/resources/extensions/gsd/export-html.js +27 -425
  13. package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
  14. package/dist/resources/extensions/gsd/native-git-bridge.js +8 -3
  15. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
  16. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -1
  17. package/dist/resources/extensions/gsd/unit-context-manifest.js +7 -8
  18. package/dist/resources/extensions/gsd/worktree-lifecycle.js +28 -7
  19. package/dist/resources/extensions/shared/html-shell.js +388 -0
  20. package/dist/resources/extensions/visual-brief/page-contract.js +2 -0
  21. package/dist/resources/extensions/visual-brief/prompts.js +29 -0
  22. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  23. package/dist/web/standalone/.next/BUILD_ID +1 -1
  24. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  25. package/dist/web/standalone/.next/build-manifest.json +3 -3
  26. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  37. package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  47. package/dist/web/standalone/.next/server/app/index.html +1 -1
  48. package/dist/web/standalone/.next/server/app/index.rsc +4 -7
  49. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -7
  51. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
  53. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  54. package/dist/web/standalone/.next/server/app/page.js +2 -2
  55. package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
  56. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  58. package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
  59. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  62. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  63. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  64. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  65. package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
  66. package/dist/web/standalone/.next/static/chunks/{webpack-6a95bc41e0f7ec89.js → webpack-9a4db269f9ed63ad.js} +1 -1
  67. package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
  68. package/package.json +1 -1
  69. package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
  70. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
  71. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -6
  72. package/src/resources/extensions/gsd/auto-post-unit.ts +76 -6
  73. package/src/resources/extensions/gsd/auto-recovery.ts +29 -0
  74. package/src/resources/extensions/gsd/auto-start.ts +7 -3
  75. package/src/resources/extensions/gsd/auto-worktree.ts +104 -0
  76. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +6 -1
  77. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +16 -1
  78. package/src/resources/extensions/gsd/commands/handlers/core.ts +17 -1
  79. package/src/resources/extensions/gsd/db/unit-dispatches.ts +3 -3
  80. package/src/resources/extensions/gsd/export-html.ts +27 -427
  81. package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
  82. package/src/resources/extensions/gsd/native-git-bridge.ts +8 -3
  83. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
  84. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
  85. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
  87. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
  88. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +57 -2
  89. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +39 -0
  90. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
  91. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
  92. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +25 -0
  93. package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
  94. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
  95. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +10 -0
  96. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +39 -0
  97. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +65 -7
  98. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +64 -12
  99. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +31 -0
  100. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  101. package/src/resources/extensions/gsd/unit-context-manifest.ts +12 -9
  102. package/src/resources/extensions/gsd/worktree-lifecycle.ts +34 -7
  103. package/src/resources/extensions/shared/html-shell.ts +412 -0
  104. package/src/resources/extensions/visual-brief/page-contract.ts +2 -0
  105. package/src/resources/extensions/visual-brief/prompts.ts +37 -1
  106. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +40 -0
  107. package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
  108. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
  109. package/dist/web/standalone/.next/static/css/0262768ec1b89d34.css +0 -1
  110. package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
  111. package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  112. package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  113. package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  114. package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  115. package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  116. package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  117. /package/dist/web/standalone/.next/static/{Qgr2B_MRhPxC0z8fwv4vT → BdZQhe8yKl6bdKLiXVEzh}/_buildManifest.js +0 -0
  118. /package/dist/web/standalone/.next/static/{Qgr2B_MRhPxC0z8fwv4vT → BdZQhe8yKl6bdKLiXVEzh}/_ssgManifest.js +0 -0
@@ -48,6 +48,11 @@ import { nativeCheckoutBranch } from "./native-git-bridge.js";
48
48
  // is the only Module that calls them, and they live alongside the Module's
49
49
  // other primitives in `auto-worktree.ts`.
50
50
  import { autoWorktreeBranch, createAutoWorktree, enterAutoWorktree, enterBranchModeForMilestone, getAutoWorktreePath, isInAutoWorktree, teardownAutoWorktree, } from "./auto-worktree.js";
51
+ const recentWorktreeMergeFailures = new Map();
52
+ const MERGE_FAILURE_DEDUPE_MS = 60_000;
53
+ export function resetRecentWorktreeMergeFailuresForTest() {
54
+ recentWorktreeMergeFailures.clear();
55
+ }
51
56
  /**
52
57
  * Internal sentinel — thrown by `_mergeBranchMode` when it has already
53
58
  * emitted a user-visible error. The outer `mergeAndExit` catches the type
@@ -477,6 +482,28 @@ function rebuildGitService(s, deps) {
477
482
  // GitService cast.
478
483
  s.gitService = deps.gitServiceFactory(s.basePath);
479
484
  }
485
+ function emitWorktreeMergeFailedOnce(basePath, milestoneId, err) {
486
+ const msg = err instanceof Error ? err.message : String(err);
487
+ const errorCategory = err instanceof Error ? err.name : "Error";
488
+ const now = Date.now();
489
+ const key = `${basePath}\0${milestoneId}\0${errorCategory}`;
490
+ const previous = recentWorktreeMergeFailures.get(key);
491
+ if (previous && now - previous < MERGE_FAILURE_DEDUPE_MS)
492
+ return;
493
+ for (const [candidate, ts] of recentWorktreeMergeFailures) {
494
+ if (now - ts >= MERGE_FAILURE_DEDUPE_MS) {
495
+ recentWorktreeMergeFailures.delete(candidate);
496
+ }
497
+ }
498
+ emitJournalEvent(basePath, {
499
+ ts: new Date().toISOString(),
500
+ flowId: randomUUID(),
501
+ seq: 0,
502
+ eventType: "worktree-merge-failed",
503
+ data: { milestoneId, error: msg },
504
+ });
505
+ recentWorktreeMergeFailures.set(key, now);
506
+ }
480
507
  // ─── Session-less merge entry (ADR-016 phase 2 / A1) ─────────────────────
481
508
  /**
482
509
  * Worktree-mode merge body. Session-less — operates on a `MergeContext`.
@@ -590,13 +617,7 @@ function _mergeWorktreeModeImpl(deps, mctx) {
590
617
  error: msg,
591
618
  fallback: "chdir-to-project-root",
592
619
  });
593
- emitJournalEvent(originalBasePath || worktreeBasePath, {
594
- ts: new Date().toISOString(),
595
- flowId: randomUUID(),
596
- seq: 0,
597
- eventType: "worktree-merge-failed",
598
- data: { milestoneId, error: msg },
599
- });
620
+ emitWorktreeMergeFailedOnce(originalBasePath || worktreeBasePath, milestoneId, err);
600
621
  // Surface a clear, actionable error. Worktree and milestone branch
601
622
  // are intentionally preserved — nothing has been deleted. User can
602
623
  // retry /gsd dispatch complete-milestone or merge manually once the
@@ -0,0 +1,388 @@
1
+ export function renderHtmlShell(options) {
2
+ const version = options.version ? `v${esc(options.version)}` : "";
3
+ const documentTitle = options.documentTitle ?? `${options.kind} - ${options.title}`;
4
+ const subtitle = options.subtitle ? `<span class="header-path">${esc(options.subtitle)}</span>` : "";
5
+ const toc = options.toc?.length
6
+ ? `<nav class="toc" aria-label="Report sections">
7
+ <ul>
8
+ ${options.toc.map((item) => ` <li><a href="${esc(item.href)}">${esc(item.label)}</a></li>`).join("\n")}
9
+ </ul>
10
+ </nav>`
11
+ : "";
12
+ const actions = options.headerActionsHtml ? `${options.headerActionsHtml}` : "";
13
+ const footerNote = options.footerNote ? `<span class="sep">/</span>\n <span>${esc(options.footerNote)}</span>` : "";
14
+ return `<!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ <title>${esc(documentTitle)}</title>
20
+ <style>${HTML_SHELL_CSS}</style>
21
+ </head>
22
+ <body>
23
+ <header>
24
+ <div class="header-inner">
25
+ <div class="branding">
26
+ <span class="logo">GSD</span>
27
+ ${version ? `<span class="version">${version}</span>` : ""}
28
+ </div>
29
+ <div class="header-meta">
30
+ <h1>${esc(options.title)}</h1>
31
+ ${subtitle}
32
+ </div>
33
+ <div class="header-right">
34
+ ${actions}
35
+ <span class="kind-chip">${esc(options.kind)}</span>
36
+ <div class="generated">${formatDateLong(options.generatedAt)}</div>
37
+ </div>
38
+ </div>
39
+ </header>
40
+ ${toc}
41
+ <main>
42
+ ${options.mainHtml}
43
+ </main>
44
+ <footer>
45
+ <div class="footer-inner">
46
+ <span>GSD${version ? ` ${version}` : ""}</span>
47
+ <span class="sep">/</span>
48
+ <span>${esc(options.kind)}</span>
49
+ ${footerNote}
50
+ <span class="sep">/</span>
51
+ <span>${formatDateLong(options.generatedAt)}</span>
52
+ </div>
53
+ </footer>
54
+ <script>${HTML_SHELL_JS}</script>
55
+ </body>
56
+ </html>`;
57
+ }
58
+ export function renderHtmlShellTemplate(options) {
59
+ return renderHtmlShell({
60
+ ...options,
61
+ generatedAt: options.generatedAtPlaceholder ?? "{{GENERATED_AT}}",
62
+ mainHtml: options.mainPlaceholder,
63
+ });
64
+ }
65
+ export function formatDateLong(iso) {
66
+ if (/^\{\{[A-Z_]+\}\}$/.test(iso))
67
+ return iso;
68
+ try {
69
+ const d = new Date(iso);
70
+ return d.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' });
71
+ }
72
+ catch {
73
+ return iso;
74
+ }
75
+ }
76
+ export function esc(s) {
77
+ if (s == null)
78
+ return '';
79
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
80
+ }
81
+ export const HTML_SHELL_CSS = `
82
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
83
+ :root{
84
+ --bg-0:#0f1115;--bg-1:#16181d;--bg-2:#1e2028;--bg-3:#272a33;
85
+ --border-1:#2b2e38;--border-2:#3b3f4c;
86
+ --text-0:#ededef;--text-1:#a1a1aa;--text-2:#71717a;
87
+ --accent:#5e6ad2;--accent-subtle:rgba(94,106,210,.12);
88
+ --ok:#22c55e;--ok-subtle:rgba(34,197,94,.12);--warn:#ef4444;--caution:#eab308;
89
+ /* Chart palette - 6 hues for bar charts */
90
+ --c0:#5e6ad2;--c1:#e5796d;--c2:#14b8a6;--c3:#a78bfa;--c4:#f59e0b;--c5:#10b981;
91
+ /* Token breakdown - 4 distinct hues */
92
+ --tk-input:#5e6ad2;--tk-output:#e5796d;--tk-cache-r:#2dd4bf;--tk-cache-w:#64748b;
93
+ --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
94
+ --mono:'JetBrains Mono','Fira Code',ui-monospace,SFMono-Regular,monospace;
95
+ }
96
+ html{scroll-behavior:smooth;font-size:13px}
97
+ body{background:var(--bg-0);color:var(--text-0);font-family:var(--font);line-height:1.6;-webkit-font-smoothing:antialiased}
98
+ a{color:var(--accent);text-decoration:none}
99
+ a:hover{text-decoration:underline}
100
+ code{font-family:var(--mono);font-size:12px;background:var(--bg-3);padding:1px 5px;border-radius:3px}
101
+ .mono{font-family:var(--mono);font-size:12px}
102
+ .muted{color:var(--text-2)}
103
+ .accent{color:var(--accent)}
104
+ .sep{color:var(--border-2);margin:0 4px}
105
+ .empty{color:var(--text-2);padding:8px 0;font-size:13px}
106
+ .indent{padding-left:12px}
107
+ .num{font-variant-numeric:tabular-nums;text-align:right}
108
+ .dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0;vertical-align:middle}
109
+ .dot-sm{width:6px;height:6px}
110
+ .dot-complete{background:var(--ok);opacity:.6}
111
+ .dot-active{background:var(--accent)}
112
+ .dot-pending{background:transparent;border:1.5px solid var(--border-2)}
113
+ .dot-parked{background:var(--warn);opacity:.5}
114
+ header{background:var(--bg-1);border-bottom:1px solid var(--border-1);padding:12px 32px;position:sticky;top:0;z-index:200}
115
+ .header-inner{display:flex;align-items:center;gap:16px;max-width:1280px;margin:0 auto}
116
+ .branding{display:flex;align-items:baseline;gap:6px;flex-shrink:0}
117
+ .logo{font-size:18px;font-weight:800;letter-spacing:-.5px;color:var(--text-0)}
118
+ .version{font-size:10px;color:var(--text-2);font-family:var(--mono)}
119
+ .header-meta{flex:1;min-width:0}
120
+ .header-meta h1{font-size:15px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
121
+ .header-path{font-size:11px;color:var(--text-2);font-family:var(--mono);display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
122
+ .header-right{text-align:right;flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}
123
+ .generated{font-size:11px;color:var(--text-2)}
124
+ .kind-chip{font-size:10px;font-weight:600;color:var(--accent);background:var(--accent-subtle);border:1px solid rgba(94,106,210,.25);border-radius:3px;padding:2px 7px;text-transform:uppercase;letter-spacing:.4px}
125
+ .back-link{font-size:12px;color:var(--text-1)}
126
+ .back-link:hover{color:var(--accent)}
127
+ .toc{background:var(--bg-1);border-bottom:1px solid var(--border-1);overflow-x:auto}
128
+ .toc ul{display:flex;list-style:none;max-width:1280px;margin:0 auto;padding:0 32px}
129
+ .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}
130
+ .toc a:hover{color:var(--text-0);border-bottom-color:var(--border-2)}
131
+ .toc a.active{color:var(--text-0);border-bottom-color:var(--accent)}
132
+ main{max-width:1280px;margin:0 auto;padding:32px;display:flex;flex-direction:column;gap:48px}
133
+ section{scroll-margin-top:82px}
134
+ 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}
135
+ h3{font-size:13px;font-weight:600;color:var(--text-1);margin:20px 0 8px}
136
+ .count{font-size:11px;font-weight:500;color:var(--text-2);background:var(--bg-3);border-radius:3px;padding:1px 6px}
137
+ .count-warn{color:var(--caution)}
138
+ .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}
139
+ .kv{background:var(--bg-1);padding:10px 16px;display:flex;flex-direction:column;gap:2px;min-width:110px;flex:1}
140
+ .kv-val{font-size:18px;font-weight:600;color:var(--text-0);font-variant-numeric:tabular-nums}
141
+ .kv-lbl{font-size:10px;color:var(--text-2);text-transform:uppercase;letter-spacing:.4px}
142
+ .progress-wrap{display:flex;align-items:center;gap:10px;margin-bottom:12px}
143
+ .progress-track{flex:1;height:4px;background:var(--bg-3);border-radius:2px;overflow:hidden}
144
+ .progress-fill{height:100%;background:var(--accent);border-radius:2px}
145
+ .progress-label{font-size:12px;font-weight:600;color:var(--text-1);min-width:40px;text-align:right}
146
+ .active-info{font-size:12px;color:var(--text-1);margin-bottom:4px}
147
+ .activity-line{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-1);padding:6px 0}
148
+ .tbl{width:100%;border-collapse:collapse;font-size:12px}
149
+ .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}
150
+ .tbl td{padding:6px 12px;border-bottom:1px solid var(--border-1);vertical-align:top}
151
+ .tbl tr:last-child td{border-bottom:none}
152
+ .tbl tbody tr:hover td{background:var(--accent-subtle)}
153
+ .tbl-kv td:first-child{color:var(--text-2);width:180px}
154
+ .table-scroll{overflow-x:auto;border:1px solid var(--border-1);border-radius:4px}
155
+ .table-scroll .tbl{border:none}
156
+ .h-ok td:first-child{color:var(--text-1)}
157
+ .h-caution td{color:var(--caution)}
158
+ .h-warn td{color:var(--warn)}
159
+ .label{font-size:10px;font-weight:500;color:var(--accent);text-transform:uppercase;letter-spacing:.4px}
160
+ .risk{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;flex-shrink:0}
161
+ .risk-low{color:var(--text-2)}
162
+ .risk-medium{color:var(--caution)}
163
+ .risk-high{color:var(--warn)}
164
+ .risk-unknown{color:var(--text-2)}
165
+ .tag-row{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
166
+ .tag{font-size:11px;font-family:var(--mono);color:var(--text-2);background:var(--bg-3);border-radius:3px;padding:1px 6px}
167
+ .verif{font-size:12px;color:var(--text-1);padding:4px 0;margin-bottom:6px}
168
+ .verif-blocker{color:var(--warn)}
169
+ .detail-block{font-size:12px;color:var(--text-2);margin-bottom:6px}
170
+ .detail-label{font-weight:600;color:var(--text-1);display:block;margin-bottom:2px}
171
+ .detail-block ul{padding-left:16px;margin-top:2px}
172
+ .detail-block li{margin-bottom:1px}
173
+ .ms-block{border:1px solid var(--border-1);border-radius:4px;overflow:hidden;margin-bottom:8px}
174
+ .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}
175
+ .ms-summary:hover{background:var(--bg-2)}
176
+ .ms-summary::-webkit-details-marker{display:none}
177
+ .ms-id{font-weight:600}
178
+ .ms-title{flex:1;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
179
+ .ms-body{padding:6px 12px 8px 24px;display:flex;flex-direction:column;gap:4px}
180
+ .sl-block{border:1px solid var(--border-1);border-radius:3px;overflow:hidden}
181
+ .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}
182
+ .sl-summary:hover{background:var(--bg-3)}
183
+ .sl-summary::-webkit-details-marker{display:none}
184
+ .sl-crit{border-left:2px solid var(--accent)}
185
+ .sl-deps::before{content:'\\2190 ';color:var(--border-2)}
186
+ .sl-detail{padding:8px 12px;background:var(--bg-0);border-top:1px solid var(--border-1)}
187
+ .task-list{list-style:none;padding:4px 0 0;display:flex;flex-direction:column;gap:2px}
188
+ .task-row{display:flex;align-items:center;gap:6px;font-size:12px;padding:3px 6px;border-radius:2px}
189
+ .dep-block{margin-bottom:28px}
190
+ .dep-legend{display:flex;gap:14px;font-size:12px;color:var(--text-2);margin-bottom:8px;align-items:center}
191
+ .dep-legend span{display:flex;align-items:center;gap:4px}
192
+ .dep-wrap{overflow-x:auto;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:16px}
193
+ .dep-svg{display:block}
194
+ .edge{fill:none;stroke:var(--border-2);stroke-width:1.5}
195
+ .edge-crit{stroke:var(--accent);stroke-width:2}
196
+ .node rect{fill:var(--bg-2);stroke:var(--border-2);stroke-width:1}
197
+ .n-done rect{fill:var(--ok-subtle);stroke:rgba(34,197,94,.4)}
198
+ .n-active rect{fill:var(--accent-subtle);stroke:var(--accent)}
199
+ .n-crit rect{stroke:var(--accent)!important;stroke-width:1.5!important}
200
+ .n-id{font-family:var(--mono);font-size:10px;fill:var(--text-1);font-weight:600;text-anchor:middle}
201
+ .n-title{font-size:9px;fill:var(--text-2);text-anchor:middle}
202
+ .n-active .n-id{fill:var(--accent)}
203
+ .token-block{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px;margin-bottom:16px}
204
+ .token-bar{display:flex;height:16px;border-radius:2px;overflow:hidden;gap:1px;margin-bottom:8px}
205
+ .tseg{height:100%;min-width:2px}
206
+ .seg-1{background:var(--tk-input)}
207
+ .seg-2{background:var(--tk-output)}
208
+ .seg-3{background:var(--tk-cache-r)}
209
+ .seg-4{background:var(--tk-cache-w)}
210
+ .token-legend{display:flex;flex-wrap:wrap;gap:12px}
211
+ .leg-item{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text-2)}
212
+ .leg-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
213
+ .chart-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin-bottom:16px}
214
+ .chart-block{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px}
215
+ .bar-row{display:grid;grid-template-columns:120px 1fr 68px;align-items:center;gap:6px;margin-bottom:2px}
216
+ .bar-lbl{font-size:12px;color:var(--text-2);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
217
+ .bar-track{height:14px;background:var(--bg-3);border-radius:2px;overflow:hidden}
218
+ .bar-fill{height:100%;border-radius:2px;background:var(--c0)}
219
+ .bar-c0{background:var(--c0)}.bar-c1{background:var(--c1)}.bar-c2{background:var(--c2)}
220
+ .bar-c3{background:var(--c3)}.bar-c4{background:var(--c4)}.bar-c5{background:var(--c5)}
221
+ .bar-val{font-size:11px;font-variant-numeric:tabular-nums;color:var(--text-1)}
222
+ .bar-sub{font-size:10px;color:var(--text-2);padding-left:128px;margin-bottom:6px}
223
+ .cl-entry{border-bottom:1px solid var(--border-1);padding:12px 0}
224
+ .cl-entry:last-child{border-bottom:none}
225
+ .cl-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}
226
+ .cl-title{flex:1;font-weight:500}
227
+ .cl-date{margin-left:auto;white-space:nowrap}
228
+ .cl-liner{font-size:13px;color:var(--text-1);margin-bottom:6px}
229
+ .files-detail summary{font-size:12px;cursor:pointer}
230
+ .file-list{list-style:none;padding-left:10px;margin-top:4px;display:flex;flex-direction:column;gap:2px}
231
+ .file-list li{font-size:12px;color:var(--text-1)}
232
+ footer{border-top:1px solid var(--border-1);padding:20px 32px;margin-top:40px}
233
+ .footer-inner{display:flex;align-items:center;gap:6px;justify-content:center;font-size:11px;color:var(--text-2);flex-wrap:wrap}
234
+ .exec-summary{font-size:13px;color:var(--text-1);margin-bottom:12px;line-height:1.7}
235
+ .eta-line{font-size:12px;color:var(--accent);margin-top:4px}
236
+ .cost-svg{display:block;margin:8px 0;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px}
237
+ .cost-line{fill:none;stroke:var(--accent);stroke-width:2}
238
+ .cost-area{fill:var(--accent-subtle);stroke:none}
239
+ .cost-axis{fill:var(--text-2);font-family:var(--mono);font-size:10px}
240
+ .cost-grid{stroke:var(--border-1);stroke-width:1;stroke-dasharray:4,4}
241
+ .burndown-wrap{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px;margin-bottom:16px}
242
+ .burndown-bar{display:flex;height:20px;border-radius:3px;overflow:hidden;gap:1px;margin-bottom:8px}
243
+ .burndown-spent{background:var(--accent);height:100%}
244
+ .burndown-projected{background:var(--caution);height:100%;opacity:.6}
245
+ .burndown-overshoot{background:var(--warn);height:100%;opacity:.7}
246
+ .burndown-legend{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--text-2)}
247
+ .burndown-legend span{display:flex;align-items:center;gap:4px}
248
+ .burndown-dot{display:inline-block;width:8px;height:8px;border-radius:2px}
249
+ .blocker-card{border-left:3px solid var(--warn);background:var(--bg-1);border-radius:0 4px 4px 0;padding:10px 14px;margin-bottom:8px}
250
+ .blocker-id{font-family:var(--mono);font-size:12px;color:var(--warn);margin-bottom:2px}
251
+ .blocker-text{font-size:12px;color:var(--text-1)}
252
+ .blocker-risk{font-size:11px;color:var(--caution);margin-top:2px}
253
+ .gantt-wrap{overflow-x:auto;background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:16px;margin-top:16px}
254
+ .gantt-svg{display:block}
255
+ .gantt-bar-done{fill:var(--ok);opacity:.7}
256
+ .gantt-bar-active{fill:var(--accent)}
257
+ .gantt-bar-pending{fill:var(--border-2)}
258
+ .gantt-label{fill:var(--text-2);font-family:var(--mono);font-size:10px}
259
+ .gantt-axis{fill:var(--text-2);font-family:var(--mono);font-size:9px}
260
+ .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}
261
+ .tl-filter:focus{border-color:var(--accent)}
262
+ .tl-filter::placeholder{color:var(--text-2)}
263
+ .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}
264
+ .sec-toggle:hover{border-color:var(--text-1);color:var(--text-1)}
265
+ .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)}
266
+ .theme-toggle:hover{border-color:var(--accent);color:var(--accent)}
267
+ .callout-info,.callout-warn,.callout-ok{border-left:3px solid var(--accent);background:var(--bg-1);border-radius:0 4px 4px 0;padding:10px 14px}
268
+ .callout-warn{border-left-color:var(--caution)}
269
+ .callout-ok{border-left-color:var(--ok)}
270
+ .card-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}
271
+ .card{background:var(--bg-1);border:1px solid var(--border-1);border-radius:4px;padding:14px}
272
+ .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}
273
+ @media(max-width:768px){
274
+ header{padding:10px 16px}
275
+ .header-inner{flex-wrap:wrap;gap:8px}
276
+ .header-meta h1{font-size:13px}
277
+ main{padding:16px}
278
+ .kv-grid{gap:1px}
279
+ .kv{min-width:80px;padding:8px 10px}
280
+ .kv-val{font-size:14px}
281
+ .chart-row{grid-template-columns:1fr}
282
+ .toc ul{padding:0 16px}
283
+ .toc a{padding:6px 8px;font-size:11px}
284
+ .bar-row{grid-template-columns:80px 1fr 56px}
285
+ .ms-body{padding-left:12px}
286
+ }
287
+ @media(max-width:480px){
288
+ .kv{min-width:60px;padding:6px 8px}
289
+ .kv-val{font-size:12px}
290
+ .kv-lbl{font-size:9px}
291
+ .bar-row{grid-template-columns:60px 1fr 48px}
292
+ .bar-lbl{font-size:10px}
293
+ .toc ul{flex-wrap:wrap}
294
+ .header-right{display:none}
295
+ .gantt-wrap{overflow-x:auto}
296
+ }
297
+ @media print{
298
+ header,nav.toc{position:static}
299
+ body{background:#fff;color:#1a1a1a}
300
+ :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}
301
+ section{page-break-inside:avoid}
302
+ .table-scroll{overflow:visible}
303
+ }
304
+ `;
305
+ export const HTML_SHELL_JS = `
306
+ (function(){
307
+ const sections=document.querySelectorAll('section[id]');
308
+ const links=document.querySelectorAll('.toc a');
309
+ if(!sections.length||!links.length)return;
310
+ const obs=new IntersectionObserver(entries=>{
311
+ for(const e of entries){
312
+ if(!e.isIntersecting)continue;
313
+ for(const l of links)l.classList.remove('active');
314
+ const a=document.querySelector('.toc a[href="#'+e.target.id+'"]');
315
+ if(a)a.classList.add('active');
316
+ }
317
+ },{rootMargin:'-10% 0px -80% 0px',threshold:0});
318
+ for(const s of sections)obs.observe(s);
319
+ })();
320
+ (function(){
321
+ var tl=document.getElementById('timeline');
322
+ if(!tl)return;
323
+ var table=tl.querySelector('.tbl');
324
+ if(!table)return;
325
+ var input=document.createElement('input');
326
+ input.className='tl-filter';
327
+ input.placeholder='Filter timeline\\u2026';
328
+ input.type='text';
329
+ table.parentNode.insertBefore(input,table);
330
+ var rows=table.querySelectorAll('tbody tr');
331
+ input.addEventListener('input',function(){
332
+ var q=this.value.toLowerCase();
333
+ for(var i=0;i<rows.length;i++){
334
+ rows[i].style.display=rows[i].textContent.toLowerCase().indexOf(q)>-1?'':'none';
335
+ }
336
+ });
337
+ })();
338
+ function safeLocalStorageSet(key,value){
339
+ try{localStorage.setItem(key,value)}catch(e){}
340
+ }
341
+ function safeLocalStorageGet(key){
342
+ try{return localStorage.getItem(key)}catch(e){return null}
343
+ }
344
+ (function(){
345
+ var saved={};
346
+ try{saved=JSON.parse(safeLocalStorageGet('gsd-collapsed')||'{}')}catch(e){}
347
+ document.querySelectorAll('section[id]').forEach(function(sec){
348
+ var h2=sec.querySelector('h2');
349
+ if(!h2)return;
350
+ var btn=document.createElement('button');
351
+ btn.className='sec-toggle';
352
+ btn.textContent=saved[sec.id]?'+':'-';
353
+ btn.setAttribute('aria-label','Toggle section');
354
+ h2.prepend(btn);
355
+ if(saved[sec.id])toggleSection(sec,true);
356
+ btn.addEventListener('click',function(e){
357
+ e.preventDefault();
358
+ var collapsed=btn.textContent==='-';
359
+ toggleSection(sec,collapsed);
360
+ btn.textContent=collapsed?'+':'-';
361
+ saved[sec.id]=collapsed;
362
+ safeLocalStorageSet('gsd-collapsed',JSON.stringify(saved));
363
+ });
364
+ });
365
+ function toggleSection(sec,hide){
366
+ var children=sec.children;
367
+ for(var i=0;i<children.length;i++){
368
+ if(children[i].tagName!=='H2')children[i].style.display=hide?'none':'';
369
+ }
370
+ }
371
+ })();
372
+ (function(){
373
+ var hr=document.querySelector('.header-right');
374
+ if(!hr)return;
375
+ var stored=safeLocalStorageGet('gsd-theme');
376
+ var btn=document.createElement('button');
377
+ btn.className='theme-toggle';
378
+ btn.textContent=stored==='light'?'Dark':'Light';
379
+ if(stored==='light')document.documentElement.classList.add('light-theme');
380
+ btn.addEventListener('click',function(){
381
+ document.documentElement.classList.toggle('light-theme');
382
+ var isLight=document.documentElement.classList.contains('light-theme');
383
+ btn.textContent=isLight?'Dark':'Light';
384
+ safeLocalStorageSet('gsd-theme',isLight?'light':'dark');
385
+ });
386
+ hr.prepend(btn);
387
+ })();
388
+ `;
@@ -115,6 +115,8 @@ export function getVisualBriefModeProfile(mode, slides) {
115
115
  "Assumptions, limitations, and source references",
116
116
  ],
117
117
  };
118
+ default:
119
+ throw new Error(`Unknown visual brief mode: ${mode}`);
118
120
  }
119
121
  }
120
122
  export function formatPageRules(rules = VISUAL_BRIEF_PAGE_RULES) {
@@ -1,6 +1,7 @@
1
1
  // GSD-2 + Visual Brief prompt construction
2
2
  import { createVisualBriefArtifactPolicy, formatArtifactPolicy, } from "./artifact-policy.js";
3
3
  import { formatPageRules, getVisualBriefModeProfile, } from "./page-contract.js";
4
+ import { renderHtmlShellTemplate } from "../shared/html-shell.js";
4
5
  export const VISUAL_BRIEF_MODES = [
5
6
  {
6
7
  mode: "diagram",
@@ -36,6 +37,14 @@ export const VISUAL_BRIEF_MODES = [
36
37
  },
37
38
  ];
38
39
  export const VISUAL_BRIEF_USAGE = "Usage: /gsd brief <diagram|plan|diff|recap|table|slides> [topic] [--slides]";
40
+ const VISUAL_BRIEF_KIND = {
41
+ diff: "Diff Brief",
42
+ recap: "Project Recap",
43
+ plan: "Implementation Plan",
44
+ diagram: "Diagram",
45
+ table: "Comparison",
46
+ slides: "Slide Deck",
47
+ };
39
48
  export function parseVisualBriefArgs(args) {
40
49
  const rawArgs = args.trim();
41
50
  if (!rawArgs)
@@ -63,6 +72,16 @@ export function buildVisualBriefPrompt(request, options) {
63
72
  const profile = getVisualBriefModeProfile(request.mode, request.slides);
64
73
  const artifactPolicy = createVisualBriefArtifactPolicy(options.outputDir);
65
74
  const outputFormat = request.slides ? "slide deck" : "scrollable explanation page";
75
+ const version = options.version?.trim() || undefined;
76
+ const shell = renderHtmlShellTemplate({
77
+ title: request.subject,
78
+ documentTitle: `GSD ${VISUAL_BRIEF_KIND[request.mode]} - ${request.subject}`,
79
+ subtitle: "Visual brief",
80
+ kind: VISUAL_BRIEF_KIND[request.mode],
81
+ version,
82
+ mainPlaceholder: "{{MAIN_HTML}}",
83
+ footerNote: request.subject,
84
+ });
66
85
  return `Create a visual brief as a single HTML file.
67
86
 
68
87
  ## Request
@@ -85,6 +104,16 @@ ${formatPageRules()}
85
104
  3. Write the HTML file.
86
105
  ${formatArtifactPolicy(artifactPolicy).split("\n").map((line) => ` ${line}`).join("\n")}
87
106
 
107
+ ## Required HTML shell
108
+
109
+ Use this scaffold verbatim. Replace {{MAIN_HTML}} with the authored <main> contents and {{GENERATED_AT}} with the current ISO timestamp. Do not add new <style> blocks or replace the header, footer, CSS, or script. The <h1> is the subject only; the kind chip is the only visual carrier of artifact type.
110
+
111
+ \`\`\`html
112
+ ${shell}
113
+ \`\`\`
114
+
115
+ Author only section-level content for {{MAIN_HTML}}. Use the shell utility classes where they fit: .kv-grid, .tbl, .card-row, .card, .callout-info, .callout-warn, .callout-ok, .dot-active, .dot-complete, .dot-pending.
116
+
88
117
  ## Page sections
89
118
 
90
119
  ${profile.sections.map((section) => `- ${section}`).join("\n")}