specpipe 1.0.0

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 (60) hide show
  1. package/README.md +1319 -0
  2. package/bin/devkit.js +3 -0
  3. package/package.json +61 -0
  4. package/src/cli.js +76 -0
  5. package/src/commands/check.js +33 -0
  6. package/src/commands/diff.js +84 -0
  7. package/src/commands/init-adopt.js +54 -0
  8. package/src/commands/init-agents.js +118 -0
  9. package/src/commands/init-global.js +102 -0
  10. package/src/commands/init.js +311 -0
  11. package/src/commands/list.js +54 -0
  12. package/src/commands/remove.js +133 -0
  13. package/src/commands/upgrade.js +215 -0
  14. package/src/lib/agent-guards.js +100 -0
  15. package/src/lib/agent-install.js +161 -0
  16. package/src/lib/agents.js +280 -0
  17. package/src/lib/claude-global.js +183 -0
  18. package/src/lib/detector.js +93 -0
  19. package/src/lib/hasher.js +21 -0
  20. package/src/lib/installer.js +213 -0
  21. package/src/lib/logger.js +16 -0
  22. package/src/lib/manifest.js +102 -0
  23. package/src/lib/reconcile.js +56 -0
  24. package/templates/.claude/CLAUDE.md +79 -0
  25. package/templates/.claude/hooks/comment-guard.js +126 -0
  26. package/templates/.claude/hooks/file-guard.js +216 -0
  27. package/templates/.claude/hooks/glob-guard.js +104 -0
  28. package/templates/.claude/hooks/path-guard.sh +118 -0
  29. package/templates/.claude/hooks/self-review.sh +27 -0
  30. package/templates/.claude/hooks/sensitive-guard.sh +227 -0
  31. package/templates/.claude/settings.json +68 -0
  32. package/templates/docs/WORKFLOW.md +325 -0
  33. package/templates/docs/specs/.gitkeep +0 -0
  34. package/templates/hooks/specpipe-read-guard.sh +42 -0
  35. package/templates/hooks/specpipe-shell-guard.sh +65 -0
  36. package/templates/rules/specpipe-guards.md +40 -0
  37. package/templates/scripts/test-hooks.sh +66 -0
  38. package/templates/skills/sp-build/SKILL.md +776 -0
  39. package/templates/skills/sp-challenge/SKILL.md +255 -0
  40. package/templates/skills/sp-commit/SKILL.md +174 -0
  41. package/templates/skills/sp-explore/SKILL.md +730 -0
  42. package/templates/skills/sp-fix/SKILL.md +266 -0
  43. package/templates/skills/sp-humanize/SKILL.md +212 -0
  44. package/templates/skills/sp-investigate/SKILL.md +648 -0
  45. package/templates/skills/sp-md-render/SKILL.md +200 -0
  46. package/templates/skills/sp-md-render/components.md +415 -0
  47. package/templates/skills/sp-md-render/template.html +283 -0
  48. package/templates/skills/sp-plan/SKILL.md +947 -0
  49. package/templates/skills/sp-review/SKILL.md +268 -0
  50. package/templates/skills/sp-scaffold/SKILL.md +237 -0
  51. package/templates/skills/sp-scaffold/references/ARCHITECTURE.md.tmpl +228 -0
  52. package/templates/skills/sp-scaffold/references/DESIGN.md.tmpl +113 -0
  53. package/templates/skills/sp-scaffold/references/adr/NNNN-template.md +92 -0
  54. package/templates/skills/sp-scaffold/references/stack-profiles/react.md +36 -0
  55. package/templates/skills/sp-spec-render/SKILL.md +254 -0
  56. package/templates/skills/sp-spec-render/components.md +418 -0
  57. package/templates/skills/sp-spec-render/examples/user-auth.html +749 -0
  58. package/templates/skills/sp-spec-render/examples/user-auth.md +114 -0
  59. package/templates/skills/sp-spec-render/template.html +222 -0
  60. package/templates/skills/sp-voices/SKILL.md +1184 -0
@@ -0,0 +1,283 @@
1
+ <!doctype html>
2
+ <html lang="{{LANG}}" data-theme="auto">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{TITLE}}</title>
7
+ <style>
8
+ :root {
9
+ --bg:#fff; --bg-elev:#f6f8fa; --bg-sunken:#fafbfc;
10
+ --fg:#0a0c10; --fg-muted:#57606a; --fg-subtle:#8c959f;
11
+ --border:#d0d7de; --border-subtle:#e5e9ef;
12
+ --accent:#d97757; --accent-bg:#fff4ef;
13
+ --note:#0969da; --note-bg:#ddf4ff; --note-border:#54aeff;
14
+ --tip:#1a7f37; --tip-bg:#dafbe1; --tip-border:#4ac26b;
15
+ --warn:#9a6700; --warn-bg:#fff8c5; --warn-border:#d4a72c;
16
+ --danger:#cf222e; --danger-bg:#ffebe9; --danger-border:#ff8182;
17
+ --shadow:0 1px 0 rgba(31,35,40,.04);
18
+ --radius:6px; --radius-lg:10px;
19
+ --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
20
+ --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
21
+ }
22
+ @media (prefers-color-scheme: dark) {
23
+ :root[data-theme="auto"] {
24
+ --bg:#0d1117; --bg-elev:#161b22; --bg-sunken:#010409;
25
+ --fg:#e6edf3; --fg-muted:#8b949e; --fg-subtle:#6e7681;
26
+ --border:#30363d; --border-subtle:#21262d;
27
+ --accent:#f0a378; --accent-bg:#2a1810;
28
+ --note:#58a6ff; --note-bg:#0c2d6b; --note-border:#1f6feb;
29
+ --tip:#3fb950; --tip-bg:#0d2818; --tip-border:#238636;
30
+ --warn:#d29922; --warn-bg:#2d2611; --warn-border:#9e6a03;
31
+ --danger:#f85149; --danger-bg:#2d0e10; --danger-border:#b62324;
32
+ --shadow:0 1px 0 rgba(0,0,0,.3);
33
+ }
34
+ }
35
+ :root[data-theme="dark"]{--bg:#0d1117;--bg-elev:#161b22;--bg-sunken:#010409;--fg:#e6edf3;--fg-muted:#8b949e;--fg-subtle:#6e7681;--border:#30363d;--border-subtle:#21262d;--accent:#f0a378;--accent-bg:#2a1810;--note:#58a6ff;--note-bg:#0c2d6b;--note-border:#1f6feb;--tip:#3fb950;--tip-bg:#0d2818;--tip-border:#238636;--warn:#d29922;--warn-bg:#2d2611;--warn-border:#9e6a03;--danger:#f85149;--danger-bg:#2d0e10;--danger-border:#b62324;--shadow:0 1px 0 rgba(0,0,0,.3);}
36
+ *{box-sizing:border-box}html,body{margin:0;padding:0}
37
+ body{font-family:var(--sans);font-size:14px;line-height:1.6;color:var(--fg);background:var(--bg);-webkit-font-smoothing:antialiased}
38
+ code,.mono{font-family:var(--mono);font-size:.92em}
39
+ a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
40
+ .skip{position:absolute;left:-9999px;top:8px;padding:8px 12px;background:var(--accent);color:#fff;border-radius:6px}.skip:focus{left:8px;z-index:1000}
41
+ .progress{position:fixed;top:0;left:0;height:2px;background:var(--accent);width:0;z-index:60;transition:width 80ms linear}
42
+ .topbar{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;padding:10px 20px;background:var(--bg);border-bottom:1px solid var(--border);font-size:13px}
43
+ .topbar .doc-type{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.05em;padding:2px 6px;background:var(--accent-bg);color:var(--accent);border-radius:4px}
44
+ .topbar .doc-type.investigation{background:color-mix(in srgb,var(--note) 14%,transparent);color:var(--note)}
45
+ .topbar .doc-type.retro{background:color-mix(in srgb,var(--tip) 14%,transparent);color:var(--tip)}
46
+ .topbar .title{font-weight:600;color:var(--fg);max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
47
+ .topbar .meta{color:var(--fg-muted);display:flex;gap:14px;flex-wrap:wrap}
48
+ .topbar .meta .sep{color:var(--fg-subtle)}
49
+ .topbar .spacer{flex:1}
50
+ .icon-btn{background:none;border:1px solid var(--border);color:var(--fg-muted);width:32px;height:32px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background 80ms,color 80ms}
51
+ .icon-btn:hover{background:var(--bg-elev);color:var(--fg)}.icon-btn svg{width:16px;height:16px;fill:currentColor}
52
+ .menu-btn{display:none}
53
+ @media (max-width:900px){.menu-btn{display:inline-flex}}
54
+ .layout{display:grid;grid-template-columns:280px minmax(0,1fr);gap:0}
55
+ @media (max-width:900px){.layout{grid-template-columns:1fr}}
56
+ .sidebar{position:sticky;top:53px;align-self:start;height:calc(100vh - 53px);overflow-y:auto;border-right:1px solid var(--border);padding:16px 12px 32px;background:var(--bg)}
57
+ @media (max-width:900px){
58
+ .sidebar{position:fixed;top:53px;left:0;width:280px;z-index:40;transform:translateX(-100%);transition:transform 160ms;box-shadow:0 8px 24px rgba(0,0,0,.15);border-right:1px solid var(--border)}
59
+ .sidebar.open{transform:translateX(0)}
60
+ }
61
+ .toc-search{width:100%;padding:6px 10px;font:inherit;font-size:13px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);margin-bottom:12px}
62
+ .toc-search:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:var(--accent)}
63
+ .toc-label{font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--fg-subtle);margin:4px 8px 8px}
64
+ .toc{display:flex;flex-direction:column;gap:1px}
65
+ .toc a{display:block;padding:5px 10px;color:var(--fg-muted);border-radius:4px;font-size:13px;line-height:1.45}
66
+ .toc a:hover{background:var(--bg-elev);color:var(--fg);text-decoration:none}
67
+ .toc a.active{background:var(--accent-bg);color:var(--accent);font-weight:500}
68
+ .toc a.h3{padding-left:24px;font-size:12.5px;color:var(--fg-subtle)}
69
+ main{padding:24px 36px 96px;max-width:920px;min-width:0}
70
+ @media (max-width:600px){main{padding:16px}}
71
+ h1{font-size:28px;font-weight:700;margin:4px 0 6px;letter-spacing:-.015em;line-height:1.25}
72
+ h2{font-size:20px;font-weight:600;margin:40px 0 14px;padding-bottom:6px;border-bottom:1px solid var(--border-subtle);letter-spacing:-.005em}
73
+ h3{font-size:16px;font-weight:600;margin:24px 0 10px}
74
+ h4{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--fg-muted);text-transform:uppercase;letter-spacing:.04em}
75
+ p{margin:0 0 12px}
76
+ .subtitle{color:var(--fg-muted);margin:0 0 28px;font-size:15px;line-height:1.5}
77
+ ul,ol{margin:0 0 14px;padding-left:22px}
78
+ li{margin:3px 0}
79
+ li>p:last-child{margin-bottom:0}
80
+ .tldr{background:var(--bg-elev);border:1px solid var(--border-subtle);border-left:3px solid var(--accent);border-radius:var(--radius);padding:14px 18px;margin:8px 0 28px}
81
+ .tldr-label{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.05em;color:var(--accent);text-transform:uppercase;margin-bottom:6px}
82
+ .tldr p{margin:0 0 8px}.tldr ul{margin:6px 0 0;padding-left:18px}.tldr li{margin:2px 0}
83
+ .steps{display:flex;flex-direction:column;gap:10px;margin:0 0 16px;counter-reset:step}
84
+ .step{display:grid;grid-template-columns:32px 1fr;gap:14px;padding:12px 14px;background:var(--bg-elev);border:1px solid var(--border-subtle);border-radius:var(--radius);counter-increment:step}
85
+ .step::before{content:counter(step);grid-row:1/3;display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:var(--accent);color:#fff;font-weight:600;font-size:13px;font-family:var(--mono)}
86
+ .step .step-title{font-weight:600;margin:0 0 4px;font-size:14px}
87
+ .step .step-body{font-size:13.5px;color:var(--fg-muted)}
88
+ .step .step-body :is(p,ul,ol){margin:0}
89
+ .callout{display:grid;grid-template-columns:18px 1fr;gap:10px;padding:12px 14px;border:1px solid;border-radius:var(--radius);margin:0 0 14px;font-size:13.5px}
90
+ .callout .ico{flex-shrink:0;width:18px;height:18px;margin-top:2px}
91
+ .callout-title{font-weight:600;margin:0 0 4px;font-size:12px;font-family:var(--mono);letter-spacing:.05em;text-transform:uppercase}
92
+ .callout p{margin:0 0 6px}.callout p:last-child{margin-bottom:0}
93
+ .callout.note{background:var(--note-bg);border-color:var(--note-border)}
94
+ .callout.note .ico,.callout.note .callout-title{color:var(--note)}
95
+ .callout.tip{background:var(--tip-bg);border-color:var(--tip-border)}
96
+ .callout.tip .ico,.callout.tip .callout-title{color:var(--tip)}
97
+ .callout.warn{background:var(--warn-bg);border-color:var(--warn-border)}
98
+ .callout.warn .ico,.callout.warn .callout-title{color:var(--warn)}
99
+ .callout.danger{background:var(--danger-bg);border-color:var(--danger-border)}
100
+ .callout.danger .ico,.callout.danger .callout-title{color:var(--danger)}
101
+ pre.code{position:relative;background:var(--bg-sunken);border:1px solid var(--border-subtle);border-radius:var(--radius);padding:12px 14px;margin:0 0 14px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.5}
102
+ pre.code code{background:none;padding:0;font-size:inherit}
103
+ pre.code .lang{position:absolute;top:6px;left:12px;font-size:10px;color:var(--fg-subtle);text-transform:uppercase;letter-spacing:.05em}
104
+ pre.code:not(:has(.lang)){padding-top:12px}
105
+ pre.code:has(.lang){padding-top:24px}
106
+ .copy-btn{position:absolute;top:6px;right:8px;background:var(--bg-elev);border:1px solid var(--border);color:var(--fg-muted);font-size:11px;padding:2px 8px;border-radius:4px;cursor:pointer;opacity:0;transition:opacity 120ms}
107
+ pre.code:hover .copy-btn{opacity:1}
108
+ .copy-btn:hover{color:var(--fg);background:var(--bg)}
109
+ .copy-btn.ok{color:var(--tip);border-color:var(--tip)}
110
+ code:not(pre code){background:var(--bg-elev);padding:1px 5px;border-radius:3px;border:1px solid var(--border-subtle)}
111
+ .md-table{width:100%;border-collapse:collapse;font-size:13.5px;margin:0 0 14px;display:block;overflow-x:auto}
112
+ .md-table th,.md-table td{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border-subtle);vertical-align:top}
113
+ .md-table th{font-size:11px;font-weight:600;color:var(--fg-subtle);letter-spacing:.04em;text-transform:uppercase;background:var(--bg-sunken)}
114
+ .md-table tr:last-child td{border-bottom:none}
115
+ .compare{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:0 0 14px}
116
+ @media (max-width:700px){.compare{grid-template-columns:1fr}}
117
+ .compare>div{padding:12px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius);background:var(--bg-elev)}
118
+ .compare h4{margin-top:0}
119
+ .compare.pro>div:first-child{border-left:3px solid var(--tip)}
120
+ .compare.pro>div:last-child{border-left:3px solid var(--danger)}
121
+ details.collapsible{border:1px solid var(--border-subtle);border-radius:var(--radius);background:var(--bg-elev);margin:0 0 14px}
122
+ details.collapsible>summary{list-style:none;cursor:pointer;padding:10px 14px;display:flex;align-items:center;gap:8px;font-weight:500;font-size:13.5px}
123
+ details.collapsible>summary::-webkit-details-marker{display:none}
124
+ details.collapsible>summary::before{content:"›";color:var(--fg-subtle);font-size:18px;line-height:1;transition:transform 120ms}
125
+ details.collapsible[open]>summary::before{transform:rotate(90deg)}
126
+ details.collapsible>div{padding:10px 16px 14px;border-top:1px solid var(--border-subtle);background:var(--bg)}
127
+ blockquote{margin:0 0 14px;padding:6px 14px;border-left:3px solid var(--border);color:var(--fg-muted);background:var(--bg-elev);border-radius:0 var(--radius) var(--radius) 0}
128
+ blockquote p:last-child{margin-bottom:0}
129
+ hr{border:none;border-top:1px solid var(--border-subtle);margin:28px 0}
130
+ img{max-width:100%;height:auto;border-radius:var(--radius);border:1px solid var(--border-subtle)}
131
+ .mermaid{display:flex;justify-content:center;margin:0 0 16px;padding:14px;background:var(--bg-elev);border:1px solid var(--border-subtle);border-radius:var(--radius);overflow-x:auto}
132
+ ul.task-list{list-style:none;padding-left:4px}
133
+ .task-item{display:flex;align-items:flex-start;gap:8px;margin:3px 0}
134
+ .task-item input[type=checkbox]{margin:0;accent-color:var(--accent);flex-shrink:0;margin-top:5px;cursor:default}
135
+ .task-item input:checked~span.task-text{color:var(--fg-subtle);text-decoration:line-through}
136
+ del,s{color:var(--fg-subtle)}
137
+ figure{margin:0 0 18px;text-align:center}
138
+ figure img{display:block;margin:0 auto}
139
+ figcaption{font-size:12px;color:var(--fg-subtle);margin-top:6px;font-style:italic}
140
+ sup.fn-ref{font-size:.75em;vertical-align:super;line-height:0}
141
+ sup.fn-ref a{color:var(--accent);padding:0 2px;text-decoration:none}
142
+ sup.fn-ref a:hover{text-decoration:underline}
143
+ section.footnotes{margin-top:40px;padding-top:16px;border-top:1px solid var(--border-subtle);font-size:13px;color:var(--fg-muted)}
144
+ section.footnotes h4{margin:0 0 8px;color:var(--fg-subtle)}
145
+ section.footnotes ol{padding-left:22px}
146
+ section.footnotes li{margin:6px 0}
147
+ section.footnotes li:target{background:var(--accent-bg);border-radius:4px;padding:4px 8px;margin-left:-8px}
148
+ a.fn-back{margin-left:6px;color:var(--accent);text-decoration:none;font-family:var(--mono)}
149
+ a.fn-back:hover{text-decoration:underline}
150
+ h2,h3{position:relative}
151
+ a.anchor{position:absolute;left:-20px;top:50%;transform:translateY(-50%);color:var(--fg-subtle);opacity:0;text-decoration:none;font-weight:400;font-size:.85em;padding:2px 4px;transition:opacity 120ms,color 120ms;font-family:var(--mono)}
152
+ h2:hover a.anchor,h3:hover a.anchor,a.anchor:focus{opacity:1}
153
+ a.anchor:hover{color:var(--accent);text-decoration:none}
154
+ a.anchor.ok{color:var(--tip);opacity:1}
155
+ @media (max-width:700px){a.anchor{position:static;display:inline-block;transform:none;margin-left:6px;opacity:.45}}
156
+ h1[id],h2[id],h3[id]{scroll-margin-top:70px}
157
+ .toc-hidden{display:none!important}
158
+ @media print{
159
+ .topbar,.sidebar,.icon-btn,.copy-btn,.progress{display:none}
160
+ .layout{grid-template-columns:1fr}main{max-width:none;padding:0}
161
+ details.collapsible>summary::before{display:none}
162
+ details{break-inside:avoid}
163
+ details>div{display:block!important;border-top:1px solid var(--border)}
164
+ }
165
+ @media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
166
+ </style>
167
+ </head>
168
+ <body>
169
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
170
+ <symbol id="i-sun" viewBox="0 0 16 16"><path d="M8 12.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM3.05 3.05a.75.75 0 0 1 1.06 0l1.06 1.06a.75.75 0 1 1-1.06 1.06L3.05 4.11a.75.75 0 0 1 0-1.06zm7.78 7.78a.75.75 0 0 1 1.06 0l1.06 1.06a.75.75 0 1 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM3.05 12.95a.75.75 0 0 1 0-1.06l1.06-1.06a.75.75 0 1 1 1.06 1.06L4.11 12.95a.75.75 0 0 1-1.06 0zm7.78-7.78a.75.75 0 0 1 0-1.06l1.06-1.06a.75.75 0 1 1 1.06 1.06l-1.06 1.06a.75.75 0 0 1-1.06 0z"/></symbol>
171
+ <symbol id="i-menu" viewBox="0 0 16 16"><path d="M1 2.75A.75.75 0 0 1 1.75 2h12.5a.75.75 0 0 1 0 1.5H1.75A.75.75 0 0 1 1 2.75Zm0 5A.75.75 0 0 1 1.75 7h12.5a.75.75 0 0 1 0 1.5H1.75A.75.75 0 0 1 1 7.75ZM1.75 12h12.5a.75.75 0 0 1 0 1.5H1.75a.75.75 0 0 1 0-1.5Z"/></symbol>
172
+ <symbol id="i-note" viewBox="0 0 16 16"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm9 3a1 1 0 1 0-2 0 1 1 0 0 0 2 0ZM7.25 6.75v3.5a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-1.5 0Z"/></symbol>
173
+ <symbol id="i-tip" viewBox="0 0 16 16"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.75.75 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"/></symbol>
174
+ <symbol id="i-warn" viewBox="0 0 16 16"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm.53 4.75v2.5a.75.75 0 0 0 1.5 0v-2.5a.75.75 0 0 0-1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></symbol>
175
+ <symbol id="i-danger" viewBox="0 0 16 16"><path d="M9.005 2.687a1.181 1.181 0 0 0-2.01 0L1.471 11.81a1.115 1.115 0 0 0 .95 1.69H13.58a1.115 1.115 0 0 0 .95-1.69ZM8 5.5a.75.75 0 0 1 .75.75v3a.75.75 0 0 1-1.5 0v-3A.75.75 0 0 1 8 5.5Zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/></symbol>
176
+ </svg>
177
+
178
+ <a class="skip" href="#content">{{SKIP_LABEL}}</a>
179
+ <div class="progress" id="progress" aria-hidden="true"></div>
180
+
181
+ <header class="topbar">
182
+ <button class="icon-btn menu-btn" id="menu-toggle" aria-label="Menu">
183
+ <svg><use href="#i-menu"/></svg>
184
+ </button>
185
+ <span class="doc-type {{DOC_TYPE_CLASS}}">{{DOC_TYPE}}</span>
186
+ <span class="title">{{TITLE}}</span>
187
+ <span class="meta">
188
+ <span>{{UPDATED_LABEL}} {{LAST_UPDATED}}</span>
189
+ {{META_EXTRA}}
190
+ </span>
191
+ <span class="spacer"></span>
192
+ <button class="icon-btn" id="theme-toggle" title="{{THEME_TIP}}" aria-label="{{THEME_TIP}}">
193
+ <svg><use href="#i-sun"/></svg>
194
+ </button>
195
+ </header>
196
+
197
+ <div class="layout">
198
+ <aside class="sidebar" id="sidebar" aria-label="{{TOC_LABEL}}">
199
+ <input class="toc-search" id="toc-search" placeholder="{{SEARCH_PLACEHOLDER}}" type="search">
200
+ <div class="toc-label">{{TOC_LABEL}}</div>
201
+ <nav class="toc" id="toc">
202
+ <!-- TOC_ENTRIES -->
203
+ </nav>
204
+ </aside>
205
+
206
+ <main id="content">
207
+ <h1>{{TITLE}}</h1>
208
+ <p class="subtitle">{{SUBTITLE}}</p>
209
+ <!-- CONTENT_START -->
210
+ <!-- CONTENT_END -->
211
+ </main>
212
+ </div>
213
+
214
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
215
+ <script>
216
+ if(window.mermaid){mermaid.initialize({startOnLoad:true,theme:document.documentElement.getAttribute('data-theme')==='dark'?'dark':'default',securityLevel:'loose'});}
217
+ const root=document.documentElement;
218
+ const themeBtn=document.getElementById('theme-toggle');
219
+ const stored=localStorage.getItem('md-theme');
220
+ if(stored)root.setAttribute('data-theme',stored);
221
+ themeBtn.addEventListener('click',()=>{
222
+ const cur=root.getAttribute('data-theme');
223
+ const next=cur==='dark'?'light':(cur==='light'?'auto':'dark');
224
+ root.setAttribute('data-theme',next);
225
+ localStorage.setItem('md-theme',next);
226
+ });
227
+ const sidebar=document.getElementById('sidebar');
228
+ const menuBtn=document.getElementById('menu-toggle');
229
+ if(menuBtn){menuBtn.addEventListener('click',()=>sidebar.classList.toggle('open'));}
230
+ document.querySelectorAll('.toc a').forEach(a=>a.addEventListener('click',()=>sidebar.classList.remove('open')));
231
+ const tocLinks=document.querySelectorAll('#toc a');
232
+ const tocById=new Map();
233
+ tocLinks.forEach(a=>tocById.set(a.dataset.target,a));
234
+ const targets=[...tocById.keys()].map(id=>document.getElementById(id)).filter(Boolean);
235
+ const setActive=(id)=>{tocLinks.forEach(a=>a.classList.remove('active'));const a=tocById.get(id);if(a)a.classList.add('active');};
236
+ const obs=new IntersectionObserver((entries)=>{
237
+ const visible=entries.filter(e=>e.isIntersecting).sort((a,b)=>a.boundingClientRect.top-b.boundingClientRect.top);
238
+ if(visible[0])setActive(visible[0].target.id);
239
+ },{rootMargin:'-70px 0px -60% 0px',threshold:0});
240
+ targets.forEach(t=>obs.observe(t));
241
+ const search=document.getElementById('toc-search');
242
+ search.addEventListener('input',()=>{
243
+ const q=search.value.trim().toLowerCase();
244
+ tocLinks.forEach(a=>{
245
+ if(!q){a.classList.remove('toc-hidden');return;}
246
+ a.classList.toggle('toc-hidden',!a.textContent.toLowerCase().includes(q));
247
+ });
248
+ });
249
+ const progress=document.getElementById('progress');
250
+ const onScroll=()=>{
251
+ const h=document.documentElement;
252
+ const p=(h.scrollTop)/(h.scrollHeight-h.clientHeight)*100;
253
+ progress.style.width=Math.max(0,Math.min(100,p))+'%';
254
+ };
255
+ document.addEventListener('scroll',onScroll,{passive:true});onScroll();
256
+ document.querySelectorAll('main h2[id],main h3[id]').forEach(h=>{
257
+ const a=document.createElement('a');
258
+ a.className='anchor';a.href='#'+h.id;a.textContent='#';a.setAttribute('aria-label','Copy link');
259
+ a.addEventListener('click',e=>{
260
+ e.preventDefault();
261
+ const url=location.origin+location.pathname+'#'+h.id;
262
+ history.replaceState(null,'',url);
263
+ try{navigator.clipboard.writeText(url);a.classList.add('ok');a.textContent='✓';
264
+ setTimeout(()=>{a.classList.remove('ok');a.textContent='#';},1200);
265
+ }catch(err){}
266
+ });
267
+ h.insertBefore(a,h.firstChild);
268
+ });
269
+ document.querySelectorAll('pre.code').forEach(pre=>{
270
+ const btn=document.createElement('button');
271
+ btn.className='copy-btn';btn.type='button';btn.textContent='{{COPY_LABEL}}';
272
+ btn.addEventListener('click',async()=>{
273
+ const code=pre.querySelector('code');
274
+ try{await navigator.clipboard.writeText(code?code.innerText:pre.innerText);
275
+ btn.textContent='{{COPIED_LABEL}}';btn.classList.add('ok');
276
+ setTimeout(()=>{btn.textContent='{{COPY_LABEL}}';btn.classList.remove('ok');},1400);
277
+ }catch(e){}
278
+ });
279
+ pre.appendChild(btn);
280
+ });
281
+ </script>
282
+ </body>
283
+ </html>