@litfamily/litmuse 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +115 -26
  3. package/README_ko-KR.md +112 -23
  4. package/bin/litmuse.mjs +4 -0
  5. package/docs/assets/card/after.webp +0 -0
  6. package/docs/assets/card/idle.webp +0 -0
  7. package/docs/assets/card/working.webp +0 -0
  8. package/docs/assets/handoff/latest.webp +0 -0
  9. package/docs/assets/handoff/saved.webp +0 -0
  10. package/docs/assets/readme/demo.webp +0 -0
  11. package/lib/stage-card.mjs +180 -5
  12. package/package.json +7 -1
  13. package/plugin/.muse-plugin/plugin.json +26 -11
  14. package/plugin/commands/lit-status.md +2 -1
  15. package/plugin/commands/lit.md +1 -1
  16. package/plugin/hooks/lib/digest.mjs +1 -1
  17. package/plugin/hooks/lib/next-step.mjs +68 -0
  18. package/plugin/hooks/lib/routes.mjs +59 -0
  19. package/plugin/hooks/lib/workflow-stack.mjs +318 -0
  20. package/plugin/hooks/session-start.mjs +29 -2
  21. package/plugin/hooks/user-prompt-submit.mjs +138 -7
  22. package/plugin/lib/capped-fetch.mjs +25 -0
  23. package/plugin/lib/pinned-fetch.mjs +79 -0
  24. package/plugin/lib/plan-task-rows.mjs +32 -0
  25. package/plugin/lib/render_pages.py +29 -1
  26. package/plugin/lib/rewind/collect.mjs +679 -0
  27. package/plugin/lib/rewind/gauge.mjs +72 -0
  28. package/plugin/lib/rewind/next-step.mjs +91 -0
  29. package/plugin/lib/rewind/redact.mjs +83 -0
  30. package/plugin/lib/rewind/transcripts.mjs +227 -0
  31. package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
  32. package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
  33. package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
  34. package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  35. package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  36. package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  37. package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  38. package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  39. package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  40. package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  41. package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  42. package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
  43. package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
  44. package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
  45. package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
  46. package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
  47. package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
  48. package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
  49. package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  50. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
  51. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
  52. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
  53. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
  54. package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
  55. package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
  56. package/plugin/skills/lit-adhd/SKILL.md +188 -0
  57. package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
  58. package/plugin/skills/lit-adhd/references/status-taxonomy.md +43 -0
  59. package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
  60. package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
  61. package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
  62. package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
  63. package/plugin/skills/lit-docx/SKILL.md +1 -1
  64. package/plugin/skills/lit-hijack/SKILL.md +589 -0
  65. package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
  66. package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
  67. package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
  68. package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
  69. package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
  70. package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
  71. package/plugin/skills/lit-pptx/SKILL.md +20 -9
  72. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  73. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  74. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  75. package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
  76. package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
  77. package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
  78. package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
  79. package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
  80. package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
  81. package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
  82. package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
  83. package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
  84. package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
  85. package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
  86. package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
  87. package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
  88. package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
  89. package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
  90. package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
  91. package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
  92. package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
  93. package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
  94. package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
  95. package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
  96. package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
  97. package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
  98. package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
  99. package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  100. package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
  101. package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
  102. package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  103. package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  104. package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
  105. package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
  106. package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  107. package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
  108. package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
  109. package/plugin/skills/lit-recap/SKILL.md +3 -0
  110. package/plugin/skills/lit-schematic/SKILL.md +241 -0
  111. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
  112. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
  113. package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
  114. package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
  115. package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
  116. package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
  117. package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
  118. package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
  119. package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
  120. package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
  121. package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
  122. package/plugin/skills/lit-schematic/references/verification.md +24 -0
  123. package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
  124. package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
  125. package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
  126. package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
  127. package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
  128. package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
  129. package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
  130. package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
  131. package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
  132. package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
  133. package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
  134. package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
  135. package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
  136. package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
  137. package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
  138. package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
  139. package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
  140. package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
  141. package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
  142. package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
  143. package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
  144. package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
  145. package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
  146. package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
  147. package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
  148. package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
  149. package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
  150. package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
  151. package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
  152. package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
  153. package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
  154. package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
  155. package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
  156. package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
  157. package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  158. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  159. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  160. package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  161. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
  162. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
  163. package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
  164. package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
  165. package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
  166. package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
  167. package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
  168. package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
  169. package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
  170. package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
  171. package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
  172. package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
  173. package/plugin/skills/readme-studio/SKILL.md +1 -1
  174. package/plugin/skills/readme-studio/templates/hyperframes-cover/package-lock.json +3 -3
  175. package/plugin/skills/readme-studio/templates/remotion-cover/package-lock.json +3 -3
  176. package/plugin/skills/start-work/SKILL.md +6 -0
@@ -1,6 +1,8 @@
1
1
  import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { latestFile, readStage, stageFile, workspaceHash } from '../plugin/hooks/lib/stage.mjs';
4
+ import { gauge } from '../plugin/lib/rewind/gauge.mjs';
5
+ import { REVIEW_LANES } from '../plugin/hooks/lib/workflow-stack.mjs';
4
6
 
5
7
  export function fmtDuration(ms) {
6
8
  const s = Math.max(0, Math.floor(ms / 1000));
@@ -28,9 +30,16 @@ export function planProgress(cwd, planFile) {
28
30
  const rows = [...readFileSync(join(cwd, file), 'utf8').matchAll(/^- \[([ xX])\] (\d+)\.\s*(.*)$/gm)];
29
31
  if (!rows.length) return null;
30
32
  const open = rows.find((r) => r[1] === ' ');
33
+ let mtimeMs = null;
34
+ try {
35
+ mtimeMs = statSync(join(cwd, file)).mtimeMs;
36
+ } catch {
37
+ // Readable just now but stat fails: freshness stays unknown.
38
+ }
31
39
  return {
32
40
  file, total: rows.length, done: rows.filter((r) => r[1] !== ' ').length,
33
41
  next: open ? { row: Number(open[2]), text: open[3].slice(0, 120) } : null,
42
+ mtimeMs,
34
43
  };
35
44
  } catch {
36
45
  return null;
@@ -54,6 +63,7 @@ export function activeGoal(cwd) {
54
63
  slug: best.g.slug ?? null, objective: String(best.g.objective ?? '').slice(0, 200),
55
64
  met: criteria.filter((c) => c.status === 'pass').length, total: criteria.length,
56
65
  open: criteria.filter((c) => c.status !== 'pass').map((c) => ({ id: c.id, text: String(c.text ?? '').slice(0, 120) })),
66
+ mtimeMs: best.m,
57
67
  };
58
68
  } catch {
59
69
  return null;
@@ -67,10 +77,109 @@ export function loadStage({ cwd, session, env = process.env }) {
67
77
  return entry && entry.session ? readStage(stageFile(cwd, entry.session, env)) : null;
68
78
  }
69
79
 
80
+ // One rainbow-tinted line at most: a static ANSI gradient, no animation, no blinking, no
81
+ // full-field luminance swing, so it stays flash-safe on any terminal. It marks the adhd turn.
82
+ const RAINBOW = [196, 208, 226, 46, 51, 21, 201];
83
+ export function rainbowLine(text) {
84
+ let color = 0;
85
+ let out = '';
86
+ for (const ch of String(text)) {
87
+ out += ch === ' ' ? ch : `\u001b[38;5;${RAINBOW[color++ % RAINBOW.length]}m${ch}`;
88
+ }
89
+ return `${out}\u001b[0m`;
90
+ }
91
+
92
+ // Fresh workflow stacks mean recent sessions worth briefing: any stack file touched in 24 h.
93
+ export function freshStacks(cwd, now = Date.now()) {
94
+ try {
95
+ const dir = join(cwd, '.litmuse', 'workflow-stack');
96
+ const day = 24 * 60 * 60 * 1000;
97
+ return readdirSync(dir).some((name) => name.endsWith('.json') && now - statSync(join(dir, name)).mtimeMs <= day);
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+
103
+ // Thread rows for the status card, read from the workflow stacks at render time. Every count
104
+ // resolves from exactly one source, in this order: the frame's recorded k/n, the named plan's
105
+ // rows, the active goal's criteria, otherwise the indeterminate pulse. Frames without a stage
106
+ // (a pure `record` spine) and frames older than 24 h are dropped; sibling sessions older than
107
+ // 15 minutes are skipped. Order is fixed (own stack, then siblings by file name), so a repaint
108
+ // with no change is byte-identical.
109
+ const THREAD_CAP = 6;
110
+ const THREAD_STALE_MS = 24 * 60 * 60 * 1000;
111
+ const SIBLING_FRESH_MS = 15 * 60 * 1000;
112
+
113
+ const readStackFile = (path) => {
114
+ try {
115
+ const text = readFileSync(path, 'utf8');
116
+ if (text.length > 64 * 1024) return null;
117
+ const parsed = JSON.parse(text);
118
+ return Array.isArray(parsed.frames) ? parsed : null;
119
+ } catch {
120
+ return null;
121
+ }
122
+ };
123
+
124
+ const frameGauge = (frame, cwd, goal) => {
125
+ if (Number.isInteger(frame.n) && Number.isInteger(frame.total)) return gauge(frame.n, frame.total);
126
+ if (frame.ref?.plan) {
127
+ const plan = planProgress(cwd, frame.ref.plan);
128
+ if (plan) return gauge(plan.done, plan.total);
129
+ }
130
+ if (goal) return gauge(goal.met, goal.total);
131
+ return gauge(null, null);
132
+ };
133
+
134
+ const frameRow = (frame, session, cwd, goal) => ({
135
+ session,
136
+ skill: typeof frame.skill === 'string' ? frame.skill : 'unknown',
137
+ stage: typeof frame.stage === 'string' ? frame.stage : null,
138
+ status: frame.status === 'paused' ? 'paused' : 'active',
139
+ gauge: frameGauge(frame, cwd, goal),
140
+ verdicts: frame.verdicts && typeof frame.verdicts === 'object' ? { ...frame.verdicts } : undefined,
141
+ });
142
+
143
+ export function threadRows({ cwd, session, now = Date.now() }) {
144
+ const goal = activeGoal(cwd);
145
+ let dir = null;
146
+ try {
147
+ dir = readdirSync(join(cwd, '.litmuse', 'workflow-stack'));
148
+ } catch {
149
+ return [];
150
+ }
151
+ const own = session ? `${String(session).replace(/[^A-Za-z0-9_-]/gu, '_').slice(0, 128)}.json` : null;
152
+ const rows = [];
153
+ if (own) {
154
+ const stack = readStackFile(join(cwd, '.litmuse', 'workflow-stack', own));
155
+ for (const frame of stack?.frames ?? []) {
156
+ if (!frame.stage) continue;
157
+ if (frame.at && now - Date.parse(frame.at) > THREAD_STALE_MS) continue;
158
+ rows.push(frameRow(frame, session, cwd, goal));
159
+ }
160
+ }
161
+ for (const name of dir.filter((entry) => entry.endsWith('.json') && entry !== own).sort()) {
162
+ const path = join(cwd, '.litmuse', 'workflow-stack', name);
163
+ let mtime = 0;
164
+ try {
165
+ mtime = statSync(path).mtimeMs;
166
+ } catch {
167
+ continue;
168
+ }
169
+ if (now - mtime > SIBLING_FRESH_MS) continue;
170
+ const stack = readStackFile(path);
171
+ const top = stack?.frames.at(-1);
172
+ if (!top?.stage) continue;
173
+ rows.push({ ...frameRow(top, name.replace(/\.json$/, ''), cwd, goal), sibling: true });
174
+ }
175
+ return { rows: rows.slice(0, THREAD_CAP), omitted: Math.max(0, rows.length - THREAD_CAP) };
176
+ }
177
+
70
178
  export function buildCard({ cwd, session, env = process.env, now = Date.now() }) {
71
179
  const rec = loadStage({ cwd, session, env });
72
180
  const goal = activeGoal(cwd);
73
- if (!rec) return { active: false, route: null, goal, plan: planProgress(cwd, null), lanes: [], now };
181
+ const threads = threadRows({ cwd, session: rec?.session ?? session ?? null, now });
182
+ if (!rec) return { active: false, route: null, goal, plan: planProgress(cwd, null), lanes: [], threads, idle: freshStacks(cwd, now), now, noColor: Boolean(env.NO_COLOR) };
74
183
  const lanes = (rec.lanes ?? []).map((l) => ({
75
184
  id: l.id, type: l.type, status: l.status,
76
185
  elapsedMs: (l.endedAt ?? now) - l.startedAt,
@@ -78,15 +187,78 @@ export function buildCard({ cwd, session, env = process.env, now = Date.now() })
78
187
  return {
79
188
  active: Boolean(rec.route), route: rec.route ?? null, keyword: rec.keyword ?? null, kind: rec.kind ?? null,
80
189
  session: rec.session ?? null, activeMs: rec.route && rec.activatedAt ? now - rec.activatedAt : null,
81
- plan: planProgress(cwd, rec.plan ?? null), lanes, goal, updated: rec.updated ?? null, now,
190
+ plan: planProgress(cwd, rec.plan ?? null), lanes, goal, threads, idle: !rec.route && freshStacks(cwd, now), updated: rec.updated ?? null, now, noColor: Boolean(env.NO_COLOR),
82
191
  };
83
192
  }
84
193
 
194
+ // Skill looks (BRIEF-mod-looks). Static TTY only: colour plus a label, never animation —
195
+ // the host has no card animation surface, and NO_COLOR strips even the static colour.
196
+ // The registry maps a card state to its headline paint; the lanes strip and the done burst
197
+ // are extra lines drawn from real reports only, never placeholders.
198
+ const paint = (code, text, color) => (color ? `\u001b[${code}m${text}\u001b[0m` : text);
199
+
200
+ // Waiting on the user: a paused thread, or a lane that reported failure. A turn where the
201
+ // model asked a question but left no tool state is not visible to the card.
202
+ const waitingOf = (card) => (card.threads?.rows ?? []).some((row) => row.status === 'paused')
203
+ || (card.lanes ?? []).some((lane) => lane.status === 'failed');
204
+
205
+ const headlineFor = (card, color) => {
206
+ if (!card.active) return 'LIT · no active route in this workspace';
207
+ const route = card.route ?? 'lit-loop';
208
+ if (waitingOf(card)) return paint('33;1', `LIT · ${route} · YOUR TURN`, color);
209
+ if (route === 'lit-adhd') return color ? rainbowLine('LIT · lit-adhd · brief on screen') : 'LIT · lit-adhd · brief on screen';
210
+ return `LIT · ${route}${card.activeMs !== null ? ` · active ${fmtDuration(card.activeMs)}` : ''}${card.kind && card.kind !== 'ignite' ? ` (${card.kind})` : ''}`;
211
+ };
212
+
213
+ // Canonical review-lane display names, in REVIEW_LANES order; the strip reads the same
214
+ // list, so the two cannot drift apart.
215
+ const REVIEW_LANE_LABELS = Object.freeze({ goal: 'scope', quality: 'package', security: 'security', qa: 'tests+evidence', context: 'surface' });
216
+ const VERDICT_DOT = Object.freeze({ pass: ['32;1', '●'], fail: ['31;1', '●'], running: ['33;1', '◐'], pending: ['33', '○'] });
217
+
218
+ const laneSegment = (id, verdict, color) => {
219
+ const label = REVIEW_LANE_LABELS[id] ?? id;
220
+ const known = verdict ? VERDICT_DOT[verdict] : null;
221
+ if (!known) return paint('2', `${label} ○`, color);
222
+ return paint(known[0], `${label} ${known[1]}`, color);
223
+ };
224
+
225
+ const lanesStrip = (card, color) => {
226
+ const frame = (card.threads?.rows ?? []).find((row) => row.skill === 'review-work' && row.verdicts);
227
+ const verdicts = frame?.verdicts ?? {};
228
+ return `review lanes: ${REVIEW_LANES.map(({ id }) => laneSegment(id, verdicts[id], color)).join(' · ')}`;
229
+ };
230
+
231
+ // Static TTY cannot play a one-shot animation and settle: a freshly completed plan or goal
232
+ // prints one celebratory line until the freshness window passes, then the card settles on
233
+ // its own. A published release has no signal the card can see.
234
+ const BURST_FRESH_MS = 15 * 60 * 1000;
235
+ const burstOf = (card, now) => {
236
+ const plan = card.plan;
237
+ if (plan && plan.total > 0 && plan.done >= plan.total && Number.isFinite(plan.mtimeMs) && now - plan.mtimeMs <= BURST_FRESH_MS) {
238
+ return `✨ done: ${plan.file} · ${plan.done}/${plan.total} rows ticked ✨`;
239
+ }
240
+ const goal = card.goal;
241
+ if (goal && goal.total > 0 && goal.met >= goal.total && Number.isFinite(goal.mtimeMs) && now - goal.mtimeMs <= BURST_FRESH_MS) {
242
+ return `✨ goal done: ${goal.objective} ✨`;
243
+ }
244
+ return null;
245
+ };
246
+
85
247
  export function renderCard(card) {
86
248
  const out = [];
87
- out.push(card.active
88
- ? `LIT · ${card.route}${card.activeMs !== null ? ` · active ${fmtDuration(card.activeMs)}` : ''}${card.kind && card.kind !== 'ignite' ? ` (${card.kind})` : ''}`
89
- : 'LIT · no active route in this workspace');
249
+ const color = !card.noColor;
250
+ out.push(headlineFor(card, color));
251
+ if (!card.active && card.idle) out.push('idle — say `lit adhd` for the cross-session brief');
252
+ const threads = card.threads?.rows ?? [];
253
+ if (threads.length) {
254
+ out.push('threads:');
255
+ threads.forEach((thread, index) => {
256
+ const branch = index === threads.length - 1 && !card.threads.omitted ? '└─' : '├─';
257
+ const where = thread.sibling ? ` (${thread.session})` : '';
258
+ out.push(`${branch} ${thread.skill}${thread.stage ? ` ${thread.stage}` : ''} ${thread.status} ${thread.gauge}${where}`);
259
+ });
260
+ if (card.threads.omitted) out.push(`+${card.threads.omitted} more`);
261
+ }
90
262
  if (card.plan) {
91
263
  out.push(`plan ${card.plan.done}/${card.plan.total} · ${card.plan.file}${card.plan.next ? ` · next ${card.plan.next.row}. ${card.plan.next.text}` : ' · all rows ticked'}`);
92
264
  }
@@ -94,9 +266,12 @@ export function renderCard(card) {
94
266
  const finished = card.lanes.filter((l) => l.status !== 'running');
95
267
  if (running.length) out.push(`lanes running: ${running.map((l) => `${l.type} ${fmtDuration(l.elapsedMs)}`).join(', ')}`);
96
268
  if (finished.length) out.push(`lanes finished: ${finished.map((l) => `${l.type} ${l.status} ${fmtDuration(l.elapsedMs)}`).join(', ')}`);
269
+ if (card.route === 'review-work') out.push(lanesStrip(card, color));
97
270
  if (card.goal) {
98
271
  out.push(`goal: ${card.goal.objective} (${card.goal.met}/${card.goal.total} criteria met)`);
99
272
  for (const c of card.goal.open.slice(0, 5)) out.push(` open ${c.id}: ${c.text}`);
100
273
  }
274
+ const burst = burstOf(card, card.now ?? Date.now());
275
+ if (burst) out.push(burst);
101
276
  return out.join('\n');
102
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litmuse",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Installer for LitMuse: skills, hooks and agents that help Meta's Muse Code plan, build, check and hand off work.",
5
5
  "keywords": [
6
6
  "muse",
@@ -38,6 +38,12 @@
38
38
  "docs/hud-preset.md",
39
39
  "docs/assets/cover-motion.webp",
40
40
  "docs/assets/cover-motion-still.webp",
41
+ "docs/assets/readme/demo.webp",
42
+ "docs/assets/card/idle.webp",
43
+ "docs/assets/card/working.webp",
44
+ "docs/assets/card/after.webp",
45
+ "docs/assets/handoff/saved.webp",
46
+ "docs/assets/handoff/latest.webp",
41
47
  "README.md",
42
48
  "README_ko-KR.md",
43
49
  "CHANGELOG.md",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "name": "litmuse",
4
4
  "displayName": "LitMuse",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "description": "LitFamily workflows for Muse Code: bare lit activation, planning, evidence-bound execution, review, research, handoff and Korean prose care.",
7
7
  "compat": {
8
8
  "source": "native",
@@ -115,6 +115,16 @@
115
115
  "path": "skills/lit-handoff/SKILL.md",
116
116
  "enabledDefault": true
117
117
  },
118
+ {
119
+ "id": "lit-hijack",
120
+ "path": "skills/lit-hijack/SKILL.md",
121
+ "enabledDefault": true
122
+ },
123
+ {
124
+ "id": "lit-adhd",
125
+ "path": "skills/lit-adhd/SKILL.md",
126
+ "enabledDefault": true
127
+ },
118
128
  {
119
129
  "id": "lit-init",
120
130
  "path": "skills/lit-init/SKILL.md",
@@ -150,6 +160,11 @@
150
160
  "path": "skills/lit-diagram-drawer/SKILL.md",
151
161
  "enabledDefault": true
152
162
  },
163
+ {
164
+ "id": "lit-schematic",
165
+ "path": "skills/lit-schematic/SKILL.md",
166
+ "enabledDefault": true
167
+ },
153
168
  {
154
169
  "id": "lit-typographic-motion",
155
170
  "path": "skills/lit-typographic-motion/SKILL.md",
@@ -236,7 +251,7 @@
236
251
  "node",
237
252
  "hooks/session-start.mjs",
238
253
  "--digest",
239
- "b8961789243b3149"
254
+ "11ba46df6010f4d8"
240
255
  ],
241
256
  "timeoutMs": 5000
242
257
  },
@@ -247,7 +262,7 @@
247
262
  "node",
248
263
  "hooks/user-prompt-submit.mjs",
249
264
  "--digest",
250
- "91a1d749ba4c3fd3"
265
+ "c55594062398b5ea"
251
266
  ],
252
267
  "timeoutMs": 5000,
253
268
  "statusMessage": "LitMuse route check"
@@ -259,7 +274,7 @@
259
274
  "node",
260
275
  "hooks/pre-tool-use.mjs",
261
276
  "--digest",
262
- "eedfd1f3ebe5cb38"
277
+ "b98a3b323bb1756c"
263
278
  ],
264
279
  "timeoutMs": 5000
265
280
  },
@@ -270,7 +285,7 @@
270
285
  "node",
271
286
  "hooks/post-tool-use.mjs",
272
287
  "--digest",
273
- "dac8628eaa6da79a"
288
+ "1aae7c29d41747b8"
274
289
  ],
275
290
  "timeoutMs": 5000
276
291
  },
@@ -281,7 +296,7 @@
281
296
  "node",
282
297
  "hooks/post-tool-use-failure.mjs",
283
298
  "--digest",
284
- "b905e80e38695ee0"
299
+ "3f1b940d78192ff9"
285
300
  ],
286
301
  "timeoutMs": 5000
287
302
  },
@@ -292,7 +307,7 @@
292
307
  "node",
293
308
  "hooks/stop.mjs",
294
309
  "--digest",
295
- "d7f72526de2e1053"
310
+ "0bd9020bf0bbc1af"
296
311
  ],
297
312
  "timeoutMs": 5000
298
313
  },
@@ -303,7 +318,7 @@
303
318
  "node",
304
319
  "hooks/subagent-start.mjs",
305
320
  "--digest",
306
- "20285c4542661b42"
321
+ "6694ead2b6e3ac2b"
307
322
  ],
308
323
  "timeoutMs": 5000
309
324
  },
@@ -314,7 +329,7 @@
314
329
  "node",
315
330
  "hooks/subagent-stop.mjs",
316
331
  "--digest",
317
- "35624765c5646ff0"
332
+ "023daac9ec9f7aeb"
318
333
  ],
319
334
  "timeoutMs": 5000
320
335
  },
@@ -325,7 +340,7 @@
325
340
  "node",
326
341
  "hooks/session-end.mjs",
327
342
  "--digest",
328
- "6e0bc285570a4cc2"
343
+ "18f92d375de7d7b0"
329
344
  ],
330
345
  "timeoutMs": 5000
331
346
  },
@@ -336,7 +351,7 @@
336
351
  "node",
337
352
  "hooks/pre-compact.mjs",
338
353
  "--digest",
339
- "9c089c69dc07f94a"
354
+ "dfdcd29de71fafe0"
340
355
  ],
341
356
  "timeoutMs": 5000
342
357
  }
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Read-only LIT card for this workspace: route, time active, plan n/m, running lanes, goal, last handoff"
2
+ description: "Read-only LIT card for this workspace: route, plan n/m, lanes, threads, goal, last handoff"
3
3
  argument-hint: "[slug]"
4
4
  ---
5
5
  Print the LitMuse status card for this workspace. Scope: $ARGUMENTS (empty means everything). Write nothing.
@@ -9,6 +9,7 @@ Print the LitMuse status card for this workspace. Scope: $ARGUMENTS (empty means
9
9
  - Route and time active: the newest record from `latest.json` (route, activatedAt, kind), or else the `route` on the last line of the newest `.litmuse/ledger/*.jsonl`.
10
10
  - Plan: the plan named in the record, or else the newest `plans/*.md`. Count `- [x] N.` against all `- [ ] N.` and `- [x] N.` rows, and name the first open row.
11
11
  - Lanes: the record's `lanes` entries with `status` running, done or failed, and their elapsed time (`endedAt` or now, minus `startedAt`).
12
+ - Threads: `.litmuse/workflow-stack/<session>.json` frames (paused spines above running sides) plus other sessions' fresh paused tops: a real gauge when the count is known, the `… N steps` fact line otherwise — never a placeholder. Six rows at most, then `+N more`; an idle card offers `lit adhd` instead.
12
13
  - Goal: each `.litmuse/goals/*.json` whose status is `active`, with its objective and the criteria that are not `pass`.
13
14
  3. Add the first lines of `.litmuse/handoff/latest.md` if present, and the count of tool events and failures in the newest ledger.
14
15
 
@@ -6,7 +6,7 @@ LitMuse route request: $ARGUMENTS
6
6
 
7
7
  Treat the line above as a LitMuse request; the prompt hook has already recorded its route.
8
8
 
9
- 1. The first word picks the route: loop → lit-loop, plan → lit-plan, work → litwork, goal → litgoal, research → litresearch, review → review-work, start → start-work, recap → lit-recap, status → lit-recap, handoff → lit-handoff, init → lit-init, debug → debugging, interview → deep-interview, crucible → lit-crucible, commit → lit-commit, refactor → refactor, burnoff → lit-burnoff, burnoff-file → lit-burnoff-file, brainstorm → lit-brainstorm, brainstorming → lit-brainstorm, ideation → lit-brainstorm, ideate → lit-brainstorm, idea → lit-brainstorm, ideas → lit-brainstorm, team → lit-team, conference → autoconference, rules → rules, lsp → lsp, diagnostics → lsp, lsp-setup → lsp-setup, motion → lit-typographic-motion, film → lit-typographic-motion, video → lit-typographic-motion, 영상 → lit-typographic-motion, explain → lit-comprehend, humanize → lit-humanizer, korean → lit-humanizer, 다듬어 → lit-humanizer, 윤문 → lit-humanizer. Any other first word means lit-loop, except a task that asks to make a film (a film compound such as kinetic type, lyric video, title sequence, cover loop, 모션그래픽, 가사 영상, or a bare video noun with a making verb), which goes to lit-typographic-motion. Skills without a keyword (frontend-ui-ux, readme-studio, visual-qa, comment-checker, wikify, autoresearch, structural-search, browser-drive, lit-code) are reached through lit-loop or their own `/<skill>` command.
9
+ 1. The first word picks the route: loop → lit-loop, plan → lit-plan, work → litwork, goal → litgoal, research → litresearch, review → review-work, start → start-work, recap → lit-recap, status → lit-recap, handoff → lit-handoff, init → lit-init, debug → debugging, interview → deep-interview, crucible → lit-crucible, commit → lit-commit, refactor → refactor, burnoff → lit-burnoff, burnoff-file → lit-burnoff-file, brainstorm → lit-brainstorm, brainstorming → lit-brainstorm, ideation → lit-brainstorm, ideate → lit-brainstorm, idea → lit-brainstorm, ideas → lit-brainstorm, hijack → lit-hijack, hijacking → lit-hijack, 하이재킹 → lit-hijack, 하이잭 → lit-hijack, adhd → lit-adhd, rewind → lit-adhd, approve → start-work, deny → start-work, team → lit-team, conference → autoconference, rules → rules, lsp → lsp, diagnostics → lsp, lsp-setup → lsp-setup, motion → lit-typographic-motion, film → lit-typographic-motion, video → lit-typographic-motion, 영상 → lit-typographic-motion, explain → lit-comprehend, humanize → lit-humanizer, korean → lit-humanizer, 다듬어 → lit-humanizer, 윤문 → lit-humanizer. Any other first word means lit-loop, except a task that asks to make a film (a film compound such as kinetic type, lyric video, title sequence, cover loop, 모션그래픽, 가사 영상, or a bare video noun with a making verb), which goes to lit-typographic-motion. Skills without a keyword (frontend-ui-ux, readme-studio, visual-qa, comment-checker, wikify, autoresearch, structural-search, browser-drive, lit-code) are reached through lit-loop or their own `/<skill>` command.
10
10
  2. Load that skill with read_skill using the id `plugin:litmuse:<route>` before any other tool, and follow it. When the hook context says the route is already active or resuming, skip steps 2 and 3: keep the criteria, plan and ledger you have.
11
11
  3. Your first output, before any tool call or other text, is the line `🔥 **LIT IGNITED · <route>** 🔥` on its own line, once. When the hook context says the route continues, the line is `🔥 **LIT CONTINUES · <route>** 🔥` instead.
12
12
  4. Report in reader mode: the result, any material risk, and the action the user must take.
@@ -10,7 +10,7 @@ const PLUGIN = join(LIB, '..', '..');
10
10
 
11
11
  // Files outside hooks/lib that a hook loads at run time: PostToolUse runs the lit-humanizer detector, and Stop runs the
12
12
  // lit-typographic-motion done-check with every engine module it imports.
13
- const MOTION_DONE = ['node/done.mjs', 'node/cover-assets.mjs', 'core/constants.mjs', 'node/chrome.mjs', 'node/cover-image.mjs', 'node/cover-treatment.mjs', 'node/treatment.mjs', 'node/sound.mjs', 'core/util.mjs', 'node/look.mjs', 'node/stills.mjs', 'node/analysis.mjs', 'core/flash.mjs', 'node/png.mjs'];
13
+ const MOTION_DONE = ['node/done.mjs', 'node/cover-assets.mjs', 'core/constants.mjs', 'node/chrome.mjs', 'node/cover-image.mjs', 'node/cover-treatment.mjs', 'node/treatment.mjs', 'node/sound.mjs', 'node/house.mjs', 'node/styles.mjs', 'node/score.mjs', 'core/presets.mjs', 'core/util.mjs', 'node/look.mjs', 'node/stills.mjs', 'node/analysis.mjs', 'core/flash.mjs', 'node/png.mjs'];
14
14
  export const DIGEST_EXTRAS = ['skills/lit-humanizer/scripts/core.mjs', 'skills/lit-humanizer/rules.json', ...MOTION_DONE.map((f) => `skills/lit-typographic-motion/engine/${f}`)];
15
15
 
16
16
  const listed = (dir, ext) => readdirSync(dir).filter((f) => f.endsWith(ext)).sort().map((f) => join(dir, f));
@@ -0,0 +1,68 @@
1
+ // The `go` half of the lit-adhd brief: the collector writes this session's next-step record
2
+ // under `.litmuse/adhd/`, and a later whole-prompt `go` reads it back and deletes it. The session
3
+ // binding is the file name, so another session's record is never opened. A step older than 30
4
+ // minutes is stale: the brief it came from no longer describes the work.
5
+ import { closeSync, constants, fstatSync, openSync, readFileSync, unlinkSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+
8
+ import { workspaceOf } from './common.mjs';
9
+ import { resolveProjectStateRoot } from './workflow-stack.mjs';
10
+
11
+ // The whole prompt, nothing more: `go`, `lit go`, `ㄱㄱ`, `고` or `진행`, with trailing punctuation.
12
+ const GO_PROMPT = /^\s*(?:(?:lit\s+)?go|ㄱㄱ|고|진행)[\s.!?]*$/iu;
13
+ export const isGoPrompt = (prompt) => typeof prompt === 'string' && prompt.length <= 64 && GO_PROMPT.test(prompt);
14
+
15
+ export const NEXT_STEP_MAX_AGE_MS = 30 * 60_000;
16
+ const KINDS = new Set(['resume-frame', 'approve-boundary', 'continue-work', 'start-plan', 'none']);
17
+
18
+ const nextStepFile = (cwd, sessionId) => {
19
+ const id = String(sessionId ?? 'session').replace(/[^A-Za-z0-9_-]/gu, '_').slice(0, 128) || 'session';
20
+ return join(resolveProjectStateRoot(cwd), '.litmuse', 'adhd', `next-${id}.json`);
21
+ };
22
+
23
+ /**
24
+ * Reads and deletes this session's next-step record. Returns `{kind, target}` when the record is
25
+ * whole and at most 30 minutes old, otherwise null; a stale or unreadable record is deleted all
26
+ * the same. Never throws.
27
+ */
28
+ export const takeNextStep = (payload, now = Date.now()) => {
29
+ const sessionId = typeof payload?.session_id === 'string' && payload.session_id ? payload.session_id : null;
30
+ if (!sessionId) return null;
31
+ let cwd = null;
32
+ try {
33
+ cwd = workspaceOf(payload);
34
+ } catch {
35
+ return null;
36
+ }
37
+ if (!cwd) return null;
38
+ const path = nextStepFile(cwd, sessionId);
39
+ let text;
40
+ try {
41
+ const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
42
+ try {
43
+ const stat = fstatSync(fd);
44
+ if (!stat.isFile() || stat.size > 16_384) return null;
45
+ text = readFileSync(fd, 'utf8');
46
+ } catch {
47
+ return null;
48
+ } finally {
49
+ try {
50
+ closeSync(fd);
51
+ } catch { /* already closed */ }
52
+ }
53
+ } catch {
54
+ return null;
55
+ } finally {
56
+ try {
57
+ unlinkSync(path);
58
+ } catch { /* nothing to consume */ }
59
+ }
60
+ try {
61
+ const record = JSON.parse(text);
62
+ const age = now - Date.parse(record.at);
63
+ if (!KINDS.has(record.kind) || !(age >= -60_000 && age <= NEXT_STEP_MAX_AGE_MS)) return null;
64
+ return { kind: record.kind, target: record.target && typeof record.target === 'object' ? record.target : null };
65
+ } catch {
66
+ return null;
67
+ }
68
+ };
@@ -25,6 +25,8 @@ export const SKILL_ROUTES = [
25
25
  { id: 'lit-humanizer', when: 'polish Korean or English prose into the writer\'s voice' },
26
26
  { id: 'lit-recap', when: 'where does the work stand (read-only)' },
27
27
  { id: 'lit-handoff', when: 'before stopping or switching sessions' },
28
+ { id: 'lit-hijack', when: 'port published know-how into your own skill, clean-room' },
29
+ { id: 'lit-adhd', when: 'brief every recent session: asked, did, waiting, roadmap, one next step' },
28
30
  { id: 'lit-init', when: 'orient in an unfamiliar repository; leaves AGENTS.md' },
29
31
  { id: 'frontend-ui-ux', when: 'build or restyle a user interface' },
30
32
  { id: 'readme-studio', when: 'write or refresh a README from repo facts' },
@@ -32,6 +34,7 @@ export const SKILL_ROUTES = [
32
34
  { id: 'lit-pptx', when: 'a slide deck (needs Python)' },
33
35
  { id: 'lit-scientific-visualization', when: 'a publication-ready figure from data' },
34
36
  { id: 'lit-diagram-drawer', when: 'an architecture, workflow or timeline diagram' },
37
+ { id: 'lit-schematic', when: 'a P&ID, PFD, apparatus figure or isometric cutaway of real hardware' },
35
38
  { id: 'lit-typographic-motion', when: 'make a short film, kinetic type or lyric video, or a seamless README cover loop' },
36
39
  { id: 'autoresearch', when: 'a budgeted experiment loop against a metric' },
37
40
  { id: 'structural-search', when: 'find or rewrite code by syntax shape (ast-grep)' },
@@ -70,6 +73,14 @@ export const KEYWORD_ROUTES = {
70
73
  idea: 'lit-brainstorm',
71
74
  ideas: 'lit-brainstorm',
72
75
  team: 'lit-team',
76
+ hijack: 'lit-hijack',
77
+ hijacking: 'lit-hijack',
78
+ '하이재킹': 'lit-hijack',
79
+ '하이잭': 'lit-hijack',
80
+ adhd: 'lit-adhd',
81
+ rewind: 'lit-adhd',
82
+ approve: 'start-work',
83
+ deny: 'start-work',
73
84
  conference: 'autoconference',
74
85
  rules: 'rules',
75
86
  lsp: 'lsp',
@@ -102,6 +113,44 @@ export function motionIntent(task) {
102
113
  return FILM_COMPOUND.test(text) || (VIDEO_NOUN.test(text) && MAKE_VERB.test(text));
103
114
  }
104
115
 
116
+ // Hardware wording (P&ID, PFD, cutaway, isometric, apparatus, and the Korean plant-drawing
117
+ // words) routes a `lit` prompt to lit-schematic. Software-architecture wording keeps its
118
+ // existing route: the guard below screens those prompts out.
119
+ const SCHEMATIC_WORD = /P&ID|P&I\b|PFD|BFD|schematic|cutaway|isometric|apparatus|Piping and Instrumentation|모식|장치도|공정도|계장도|배관계장/i;
120
+ const NOT_A_SCHEMATIC = /codebase|auth flow|boxes and arrows|software|entity-relationship|org chart|timeline|swimlane|sequence diagram/i;
121
+ function schematicIntent(task) {
122
+ const t = String(task ?? '');
123
+ if (NOT_A_SCHEMATIC.test(t)) return false;
124
+ return SCHEMATIC_WORD.test(t);
125
+ }
126
+
127
+ // A deck asked for by slide wording routes a `lit` prompt to lit-pptx: a slide noun with a making
128
+ // verb, or a slide count beside a slide noun ("덱 10장", "a 10-slide deck") with no verb at all.
129
+ // Code documentation and photo retouching ("사진 10장 보정") stay where they were.
130
+ const SLIDE_NOUN = /\b(?:slides?|slide\s?deck|deck|presentation|pptx?|powerpoint|keynote)\b|발표|슬라이드|피피티|파워포인트|(?<![가-힣])덱(?![가-힣])|(?<![가-힣])덱을|(?<![가-힣])덱으로/i;
131
+ const MAKE_DECK = /\b(?:make|making|made|create|creating|write|writing|draft|drafting|prepare|preparing|build|building|generate|generating|produce|producing)\b|만들|만드|작성|써\s?줘|써\s?주|써서|써봐|쓰고|정리해|정리하|준비|초안|뽑아|짜\s?줘|구성해/i;
132
+ const SLIDE_COUNT = /\d+\s?장|\b\d+[\s-]?slides?\b/i;
133
+ const NOT_A_DECK = /readme|changelog|contributing|api\s?(?:문서|docs?\b|reference)|docstrings?|jsdoc|주석|\bdocs\/|\b(?:bug|test|coverage|error|crash|lint|issue|incident)\s+reports?\b|사진\s?\d+\s?장\s?보정|보정해|보정하/i;
134
+ function deckIntent(task) {
135
+ const t = String(task ?? '');
136
+ if (NOT_A_DECK.test(t)) return false;
137
+ // A presentation asked for as a video is a film, not a deck.
138
+ if (motionIntent(t)) return false;
139
+ return SLIDE_NOUN.test(t) && (MAKE_DECK.test(t) || SLIDE_COUNT.test(t));
140
+ }
141
+
142
+ // An English request to build, redesign or review a page or site routes to the frontend skill: a making
143
+ // verb beside an interface noun, a UI target named outright, or an artefacts review beside a page noun.
144
+ const UI_NOUN = /\b(?:pages?|sites?|websites?|landing(?:\s?pages?)?|homepages?|portfolios?|dashboards?|forms?|checkouts?|onboardings?|wizards?|modals?|tooltips?|popovers?|navbars?|footers?|heros?|carousels?|galleries?|grids?|tables?|cards?|buttons?|inputs?|dropdowns?|tabs?|accordions?|themes?|layouts?|wireframes?|mockups?|prototypes?|redesigns?|uis?|ux|frontends?|front[-\s]?ends?|css|stylesheets?|responsive|accessibility|a11y|viewports?|breakpoints?|components?|design[-\s]?systems?|styleguides?|tokens?|interfaces?)\b/i;
145
+ const UI_VERB = /\b(?:build|building|create|creating|make|makes|making|design|designing|redesign|redesigning|restyle|restyling|revamp|revamping|polish|polishing|harden|hardening|audit|auditing|review|reviewing|fix|fixing|improve|improving|modernize|modernizing|responsive)\b/i;
146
+ const UI_TARGET = /^[^]*\b(?:page|site|website|landing|ui|ux|frontend|front-end|stylesheet|redesign|mockup|prototype|wireframe|theme|layout|component|design-system|styleguide)\b[^]*/i;
147
+ function uiBuildIntent(task) {
148
+ const text = String(task ?? '');
149
+ if (!text.trim()) return false;
150
+ if (UI_VERB.test(text) && UI_NOUN.test(text)) return true;
151
+ return UI_TARGET.test(text);
152
+ }
153
+
105
154
  // `lit new …` and `lit restart …` start over; `lit continue` and `lit resume` pick the work up where it stands.
106
155
  const RESTART_WORDS = new Set(['new', 'restart']);
107
156
  const CONTINUE_WORDS = new Set(['continue', 'resume']);
@@ -136,6 +185,12 @@ function detectLeading(prompt) {
136
185
  const marker = slash ? null : /^LitMuse route request:/.exec(text);
137
186
  if (slash) text = `lit ${text.slice(slash[0].length).trimStart()}`;
138
187
  else if (marker) text = `lit ${text.slice(marker[0].length).split('\n')[0].trim()}`;
188
+ // A direct skill slash for the brief (`/lit-adhd`, `/litmuse:lit-rewind`) names the
189
+ // route the same way a bare `lit-adhd` prompt does; without this the hook records no
190
+ // route and the LIT card keeps its normal headline while the brief runs.
191
+ if (/^\/(?:litmuse:)?lit-(?:adhd|rewind)(?=$|\s)/.test(text)) {
192
+ return { route: 'lit-adhd', keyword: null, bare: false, resume: false, restart: false, trailing: false };
193
+ }
139
194
  const m = /^([A-Za-z][A-Za-z-]*)(?=$|[\s:,.!?])/.exec(text);
140
195
  if (!m) return null;
141
196
  const token = m[1];
@@ -152,9 +207,13 @@ function detectLeading(prompt) {
152
207
  const resume = !restart && CONTINUE_WORDS.has(word);
153
208
  const bare = !restart && (word === '' || (resume && rest.slice(word.length).trim() === ''));
154
209
  const route = KEYWORD_ROUTES[word];
210
+ if (!route && !bare && !resume && schematicIntent(rest)) return { route: 'lit-schematic', keyword: null, bare, resume, restart };
211
+ if (!route && !bare && !resume && deckIntent(rest)) return { route: 'lit-pptx', keyword: null, bare, resume, restart };
155
212
  if (!route && !bare && !resume && motionIntent(rest)) return { route: 'lit-typographic-motion', keyword: null, bare, resume, restart };
213
+ if (!route && !bare && !resume && uiBuildIntent(rest)) return { route: 'frontend-ui-ux', keyword: null, bare, resume, restart };
156
214
  return { route: route ?? 'lit-loop', keyword: route ? word : null, bare, resume, restart };
157
215
  }
216
+ if (lower === 'lit-rewind') return { route: 'lit-adhd', keyword: null, bare: false, resume: false, restart: false, trailing: false };
158
217
  if (lower.startsWith('lit') && SKILL_IDS.has(lower)) return { route: lower, keyword: null, bare: false, resume: false, restart: false };
159
218
  return null;
160
219
  }