@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.
- package/CHANGELOG.md +17 -0
- package/README.md +115 -26
- package/README_ko-KR.md +112 -23
- package/bin/litmuse.mjs +4 -0
- package/docs/assets/card/after.webp +0 -0
- package/docs/assets/card/idle.webp +0 -0
- package/docs/assets/card/working.webp +0 -0
- package/docs/assets/handoff/latest.webp +0 -0
- package/docs/assets/handoff/saved.webp +0 -0
- package/docs/assets/readme/demo.webp +0 -0
- package/lib/stage-card.mjs +180 -5
- package/package.json +7 -1
- package/plugin/.muse-plugin/plugin.json +26 -11
- package/plugin/commands/lit-status.md +2 -1
- package/plugin/commands/lit.md +1 -1
- package/plugin/hooks/lib/digest.mjs +1 -1
- package/plugin/hooks/lib/next-step.mjs +68 -0
- package/plugin/hooks/lib/routes.mjs +59 -0
- package/plugin/hooks/lib/workflow-stack.mjs +318 -0
- package/plugin/hooks/session-start.mjs +29 -2
- package/plugin/hooks/user-prompt-submit.mjs +138 -7
- package/plugin/lib/capped-fetch.mjs +25 -0
- package/plugin/lib/pinned-fetch.mjs +79 -0
- package/plugin/lib/plan-task-rows.mjs +32 -0
- package/plugin/lib/render_pages.py +29 -1
- package/plugin/lib/rewind/collect.mjs +679 -0
- package/plugin/lib/rewind/gauge.mjs +72 -0
- package/plugin/lib/rewind/next-step.mjs +91 -0
- package/plugin/lib/rewind/redact.mjs +83 -0
- package/plugin/lib/rewind/transcripts.mjs +227 -0
- package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
- package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
- package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
- package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
- package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
- package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
- package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
- package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
- package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
- package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
- package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
- package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
- package/plugin/skills/lit-adhd/SKILL.md +188 -0
- package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
- package/plugin/skills/lit-adhd/references/status-taxonomy.md +43 -0
- package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
- package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
- package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
- package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
- package/plugin/skills/lit-docx/SKILL.md +1 -1
- package/plugin/skills/lit-hijack/SKILL.md +589 -0
- package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
- package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
- package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
- package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
- package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
- package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
- package/plugin/skills/lit-pptx/SKILL.md +20 -9
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
- package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
- package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
- package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
- package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
- package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
- package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
- package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
- package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
- package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
- package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
- package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
- package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
- package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
- package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
- package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
- package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
- package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
- package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
- package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
- package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
- package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
- package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
- package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
- package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
- package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
- package/plugin/skills/lit-recap/SKILL.md +3 -0
- package/plugin/skills/lit-schematic/SKILL.md +241 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
- package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
- package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
- package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
- package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
- package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
- package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
- package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
- package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
- package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
- package/plugin/skills/lit-schematic/references/verification.md +24 -0
- package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
- package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
- package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
- package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
- package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
- package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
- package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
- package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
- package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
- package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
- package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
- package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
- package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
- package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
- package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
- package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
- package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
- package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
- package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
- package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
- package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
- package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
- package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
- package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
- package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
- package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
- package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
- package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
- package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
- package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
- package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
- package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
- package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
- package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
- package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
- package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
- package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
- package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
- package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
- package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
- package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
- package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
- package/plugin/skills/readme-studio/SKILL.md +1 -1
- package/plugin/skills/readme-studio/templates/hyperframes-cover/package-lock.json +3 -3
- package/plugin/skills/readme-studio/templates/remotion-cover/package-lock.json +3 -3
- package/plugin/skills/start-work/SKILL.md +6 -0
package/lib/stage-card.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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,
|
|
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
|
|
package/plugin/commands/lit.md
CHANGED
|
@@ -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
|
}
|