@litfamily/litopencode 1.0.29 → 1.0.30

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 (151) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README-Ko-KR.md +7 -7
  3. package/README.md +7 -7
  4. package/dist/activation-primary-prompts.js +15 -6
  5. package/dist/activation-prompt-utils.d.ts +2 -2
  6. package/dist/activation-prompt-utils.js +9 -3
  7. package/dist/activation-reentry.d.ts +29 -0
  8. package/dist/activation-reentry.js +71 -0
  9. package/dist/activation-routing.d.ts +1 -1
  10. package/dist/activation-routing.js +40 -7
  11. package/dist/activation-workflow-prompts.d.ts +2 -0
  12. package/dist/activation-workflow-prompts.js +32 -7
  13. package/dist/activation.d.ts +6 -1
  14. package/dist/activation.js +34 -7
  15. package/dist/cli/args.js +14 -0
  16. package/dist/cli/auto-update.js +2 -4
  17. package/dist/cli/command-plan.d.ts +40 -0
  18. package/dist/cli/command-plan.js +62 -0
  19. package/dist/cli/doctor.d.ts +4 -1
  20. package/dist/cli/doctor.js +65 -4
  21. package/dist/cli/host-detect.d.ts +58 -0
  22. package/dist/cli/host-detect.js +199 -0
  23. package/dist/cli/install-report.js +3 -1
  24. package/dist/cli/install.d.ts +9 -1
  25. package/dist/cli/install.js +134 -31
  26. package/dist/cli/managed-skill-assets.d.ts +1 -1
  27. package/dist/cli/managed-skill-assets.js +5 -3
  28. package/dist/cli/office-dependencies.d.ts +2 -0
  29. package/dist/cli/office-dependencies.js +7 -3
  30. package/dist/cli/scientific-visualization-dependencies.js +1 -1
  31. package/dist/cli/tui-config.d.ts +27 -2
  32. package/dist/cli/tui-config.js +77 -2
  33. package/dist/cli/types.d.ts +17 -0
  34. package/dist/cli.js +9 -6
  35. package/dist/commands.d.ts +10 -1
  36. package/dist/commands.js +17 -3
  37. package/dist/features.d.ts +27 -1
  38. package/dist/features.js +40 -8
  39. package/dist/host/server.d.ts +7 -0
  40. package/dist/host/server.js +12 -0
  41. package/dist/host/tui.d.ts +7 -0
  42. package/dist/host/tui.js +12 -0
  43. package/dist/server.js +9 -0
  44. package/dist/skill-renames.d.ts +1 -1
  45. package/dist/skills.d.ts +9 -2
  46. package/dist/skills.js +22 -8
  47. package/dist/tui/mission-source-v2.d.ts +51 -0
  48. package/dist/tui/mission-source-v2.js +52 -0
  49. package/dist/v2/agents.d.ts +105 -0
  50. package/dist/v2/agents.js +185 -0
  51. package/dist/v2/commands.d.ts +44 -0
  52. package/dist/v2/commands.js +65 -0
  53. package/dist/v2/context.d.ts +67 -0
  54. package/dist/v2/context.js +17 -0
  55. package/dist/v2/events.d.ts +55 -0
  56. package/dist/v2/events.js +193 -0
  57. package/dist/v2/hooks.d.ts +39 -0
  58. package/dist/v2/hooks.js +61 -0
  59. package/dist/v2/server.d.ts +2 -0
  60. package/dist/v2/server.js +68 -0
  61. package/dist/v2/session-hooks.d.ts +64 -0
  62. package/dist/v2/session-hooks.js +230 -0
  63. package/dist/v2/tools.d.ts +26 -0
  64. package/dist/v2/tools.js +49 -0
  65. package/dist/v2/tui.d.ts +63 -0
  66. package/dist/v2/tui.js +259 -0
  67. package/docs/assets/cover-motion-still.webp +0 -0
  68. package/docs/assets/cover-motion.webp +0 -0
  69. package/docs/assets/cover.webp +0 -0
  70. package/docs/assets/readme/badge-version.svg +1 -1
  71. package/docs/assets/readme/litfamily-machines.png +0 -0
  72. package/docs/migration.md +4 -3
  73. package/docs/reference-Ko-KR.md +109 -7
  74. package/docs/reference.md +122 -9
  75. package/package.json +10 -5
  76. package/skills/deep-interview/SKILL.md +1 -1
  77. package/skills/lit-brainstorm/SKILL.md +299 -0
  78. package/skills/lit-brainstorm/references/handoff-briefs.md +72 -0
  79. package/skills/lit-brainstorm/references/lenses.md +106 -0
  80. package/skills/lit-brainstorm/references/scoring-and-convergence.md +93 -0
  81. package/skills/lit-brainstorm/references/session-notes-and-lanes.md +122 -0
  82. package/skills/lit-docx/SKILL.md +1 -1
  83. package/skills/lit-docx/run.py +42 -4
  84. package/skills/lit-docx/scripts/convert_md_to_pdf.py +38 -0
  85. package/skills/lit-docx/templates/latex/acs/template.tex +2 -0
  86. package/skills/lit-docx/templates/latex/elsevier/template.tex +2 -0
  87. package/skills/lit-docx/templates/latex/ieee/template.tex +2 -0
  88. package/skills/lit-docx/templates/latex/korean-generic/template.tex +2 -0
  89. package/skills/lit-docx/templates/latex/nature/template.tex +2 -0
  90. package/skills/lit-humanizer/scripts/detect.mjs +1 -1
  91. package/skills/lit-pptx/SKILL.md +1 -1
  92. package/skills/lit-pptx/command.mjs +94 -0
  93. package/skills/lit-pptx/run.mjs +16 -6
  94. package/skills/lit-pptx/scripts/compile-deck.js +2 -2
  95. package/skills/lit-typographic-motion/SKILL.md +5 -2
  96. package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +3 -0
  97. package/skills/lit-typographic-motion/engine/browser.mjs +3 -9
  98. package/skills/lit-typographic-motion/engine/command.mjs +94 -0
  99. package/skills/lit-typographic-motion/engine/constants.mjs +29 -1
  100. package/skills/lit-typographic-motion/engine/cover/assets.mjs +91 -0
  101. package/skills/lit-typographic-motion/engine/cover/capture.mjs +173 -0
  102. package/skills/lit-typographic-motion/engine/cover/decode.mjs +173 -0
  103. package/skills/lit-typographic-motion/engine/cover/frame-worker.mjs +19 -0
  104. package/skills/lit-typographic-motion/engine/cover/gate.mjs +261 -0
  105. package/skills/lit-typographic-motion/engine/cover/image.mjs +138 -0
  106. package/skills/lit-typographic-motion/engine/cover/render.mjs +323 -0
  107. package/skills/lit-typographic-motion/engine/cover/sheet.mjs +65 -0
  108. package/skills/lit-typographic-motion/engine/cover/treatment.mjs +110 -0
  109. package/skills/lit-typographic-motion/engine/encode.mjs +1 -1
  110. package/skills/lit-typographic-motion/engine/look.mjs +113 -10
  111. package/skills/lit-typographic-motion/engine/sound.mjs +1 -1
  112. package/skills/lit-typographic-motion/engine/stage/capture.mjs +26 -18
  113. package/skills/lit-typographic-motion/engine/stage/cover-init.js +38 -0
  114. package/skills/lit-typographic-motion/engine/stage/loop-kit.js +102 -0
  115. package/skills/lit-typographic-motion/engine/stage/render.mjs +4 -4
  116. package/skills/lit-typographic-motion/engine/treatment.mjs +1 -0
  117. package/skills/lit-typographic-motion/fonts/JetBrainsMono-OFL.txt +93 -0
  118. package/skills/lit-typographic-motion/fonts/JetBrainsMono-wght-latin.woff2 +0 -0
  119. package/skills/lit-typographic-motion/fonts/pins.json +3 -1
  120. package/skills/lit-typographic-motion/gate.mjs +34 -1
  121. package/skills/lit-typographic-motion/probe.mjs +8 -3
  122. package/skills/lit-typographic-motion/recipes/negatives/card-scenes/expected.json +16 -0
  123. package/skills/lit-typographic-motion/recipes/negatives/card-scenes/index.html +35 -0
  124. package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/expected.json +16 -0
  125. package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/index.html +29 -0
  126. package/skills/lit-typographic-motion/recipes/negatives/still-glow/expected.json +17 -0
  127. package/skills/lit-typographic-motion/recipes/negatives/still-glow/index.html +29 -0
  128. package/skills/lit-typographic-motion/recipes/negatives/title-slam/expected.json +17 -0
  129. package/skills/lit-typographic-motion/recipes/negatives/title-slam/index.html +35 -0
  130. package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +62 -0
  131. package/skills/lit-typographic-motion/recipes/orbit-loop/index.html +100 -0
  132. package/skills/lit-typographic-motion/recipes/painted-light/expected.json +95 -0
  133. package/skills/lit-typographic-motion/recipes/painted-light/index.html +232 -0
  134. package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +62 -0
  135. package/skills/lit-typographic-motion/recipes/weight-tide/index.html +81 -0
  136. package/skills/lit-typographic-motion/references/cover.md +423 -0
  137. package/skills/lit-typographic-motion/references/craft-loop.md +2 -0
  138. package/skills/lit-typographic-motion/references/runtime.md +17 -2
  139. package/skills/lit-typographic-motion/references/stage.md +3 -0
  140. package/skills/lit-typographic-motion/references/treatment.md +4 -0
  141. package/skills/lit-typographic-motion/render.mjs +30 -6
  142. package/skills/lit-typographic-motion/runtime.mjs +12 -13
  143. package/skills/{durable-litgoal → litgoal}/SKILL.md +31 -12
  144. package/skills/managed-skill-manifest.json +173 -29
  145. package/skills/review-work/SKILL.md +27 -18
  146. package/skills/skill-rename-aliases.json +2 -1
  147. package/tools/check-pack-payload.mjs +4 -1
  148. package/tools/payload-substance-allowlist.json +1 -1
  149. package/tools/payload-substance-parity.json +6 -4
  150. package/tools/qa-cover-recipes.mjs +167 -0
  151. package/tools/version-manifests.json +6 -0
package/dist/v2/tui.js ADDED
@@ -0,0 +1,259 @@
1
+ // LitOpenCode's OpenCode 2 TUI plugin: the mission panel in the session sidebar, the ignition line under
2
+ // the prompt (`🔥 LIT IGNITED · <discipline>`, the OpenCode 2 home of the OpenCode 1 completion mark),
3
+ // and toasts. OpenCode 2 loads it on its own for an active server plugin. The server cannot raise toasts
4
+ // on OpenCode 2, so the ignition toast is inferred here from the lit mark the server writes into the user
5
+ // message metadata. It only reads state. OpenTUI and Solid come from the host at run time; tests pass a
6
+ // stand-in runtime.
7
+ import { activationDiscipline } from "../activation-probe.js";
8
+ import { micro, supportsMarkGlyphs } from "../lit-mark.js";
9
+ import { buildMissionView, leadingNotice, missionNotices } from "../tui/mission-model.js";
10
+ import { idleMotion, motionTiming, planMotion, sampleMotion } from "../tui/mission-motion.js";
11
+ import { latestLitMarkV2, missionStateFromV2 } from "../tui/mission-source-v2.js";
12
+ import { createLedgerReader, createWorkReader, missionProjectRoot, readLatestResearchRun, readMissionInput } from "../tui/mission-source.js";
13
+ import { missionPanel, paletteFromTheme, panelWidth } from "../tui/mission-text.js";
14
+ const refreshDelayMs = 80;
15
+ const ledgerPollMs = 2000;
16
+ const toastMs = 4000;
17
+ const ignitionToastMs = 6000;
18
+ const trackedSessionLimit = 4;
19
+ // A lit mark older than the moment a session is first drawn is history being loaded, not a new turn.
20
+ const historySlackMs = 2000;
21
+ async function hostRuntime() {
22
+ const [jsxRuntime, solid] = await Promise.all([import("@opentui/solid/jsx-runtime"), import("solid-js")]);
23
+ return { jsx: jsxRuntime.jsx, createSignal: solid.createSignal, createEffect: solid.createEffect, createRoot: solid.createRoot };
24
+ }
25
+ /** OpenCode 2 theme tokens under the names the mission palette reads. */
26
+ export function paletteFromV2Theme(theme, mode) {
27
+ const tokens = (typeof theme === "object" && theme !== null ? theme : {});
28
+ return paletteFromTheme({
29
+ text: tokens.text?.base,
30
+ textMuted: tokens.text?.muted,
31
+ borderActive: tokens.border?.base,
32
+ borderSubtle: tokens.border?.base,
33
+ background: tokens.background?.base,
34
+ backgroundPanel: tokens.background?.raised?.base,
35
+ backgroundElement: tokens.background?.raised?.high
36
+ }, mode);
37
+ }
38
+ export function ignitionLine(name) {
39
+ return `🔥 LIT IGNITED · ${activationDiscipline(name)}`;
40
+ }
41
+ export async function setupLitOpenCodeTui(context, runtime) {
42
+ const ui = runtime ?? await hostRuntime();
43
+ const [settings] = context.storage.store("settings", { initial: { motion: true } });
44
+ const motionEnabled = settings.motion !== false;
45
+ const directory = context.location?.directory ?? context.data.location.default().directory;
46
+ const root = missionProjectRoot({ directory });
47
+ const readLedger = createLedgerReader();
48
+ const readWork = createWorkReader();
49
+ const sessions = new Map();
50
+ const ignitions = new Map();
51
+ const trackedAt = new Map();
52
+ const tracked = [];
53
+ // Each session's store watcher lives in its own root, so it outlasts the slot render that started it
54
+ // and ends only on eviction or cleanup. After cleanup nothing may arm a timer again.
55
+ const watchers = new Map();
56
+ let disposed = false;
57
+ const [revision, setRevision] = ui.createSignal(0);
58
+ const [clock, setClock] = ui.createSignal(Date.now());
59
+ let frameTimer;
60
+ let refreshTimer;
61
+ let pollTimer;
62
+ const palette = () => paletteFromV2Theme(context.theme, context.themeMode);
63
+ function span(segment) {
64
+ return ui.jsx("span", {
65
+ style: { fg: segment.fg, ...(segment.bg === undefined ? {} : { bg: segment.bg }), ...(segment.bold === true ? { bold: true } : {}) },
66
+ children: segment.text
67
+ });
68
+ }
69
+ function line(segments) {
70
+ return ui.jsx("text", { wrapMode: "none", children: segments.length === 0 ? " " : segments.map(span) });
71
+ }
72
+ function viewFor(sessionID, now) {
73
+ const source = missionStateFromV2(context.data, directory);
74
+ const records = { work: readWork(root, sessionID), research: readLatestResearchRun(root) };
75
+ return buildMissionView(readMissionInput(source, sessionID, readLedger(root), now, {
76
+ ...(records.work === undefined ? {} : { work: records.work }),
77
+ ...(records.research === undefined ? {} : { research: records.research })
78
+ }));
79
+ }
80
+ function anyMoving(now) {
81
+ for (const entry of sessions.values())
82
+ if (sampleMotion(entry.motion, now).moving)
83
+ return true;
84
+ return false;
85
+ }
86
+ function ensureFrames() {
87
+ if (disposed || frameTimer !== undefined || !anyMoving(Date.now()))
88
+ return;
89
+ frameTimer = setInterval(() => {
90
+ const now = Date.now();
91
+ setClock(now);
92
+ if (!anyMoving(now)) {
93
+ clearInterval(frameTimer);
94
+ frameTimer = undefined;
95
+ }
96
+ }, motionTiming.frameMs);
97
+ }
98
+ function ignitionToast(sessionID, name) {
99
+ const label = `${ignitionLine(name)} 🔥`;
100
+ const message = supportsMarkGlyphs() ? [...micro.map((row) => row.replace(/ +$/u, "")), label].join("\n") : label;
101
+ context.ui.toast.show({ title: "🔥 LIT IGNITED", message, variant: "warning", duration: ignitionToastMs, sessionID });
102
+ }
103
+ function refresh(sessionID) {
104
+ if (disposed)
105
+ return;
106
+ const now = Date.now();
107
+ const mark = latestLitMarkV2(context.data, sessionID);
108
+ if (ignitions.get(sessionID) !== mark?.messageID) {
109
+ ignitions.set(sessionID, mark?.messageID);
110
+ if (mark !== undefined && mark.at >= (trackedAt.get(sessionID) ?? 0) - historySlackMs)
111
+ ignitionToast(sessionID, mark.name);
112
+ }
113
+ const next = viewFor(sessionID, now);
114
+ const entry = sessions.get(sessionID);
115
+ if (entry !== undefined && JSON.stringify(entry.view) === JSON.stringify(next))
116
+ return;
117
+ if (entry !== undefined) {
118
+ const notice = leadingNotice(missionNotices(entry.view, next));
119
+ if (notice !== undefined)
120
+ context.ui.toast.show({ variant: notice.variant, title: notice.title, message: notice.message, duration: toastMs, sessionID });
121
+ }
122
+ sessions.set(sessionID, { view: next, motion: planMotion(entry?.view, next, now, entry?.motion ?? idleMotion, motionEnabled) });
123
+ setClock(now);
124
+ setRevision(revision() + 1);
125
+ ensureFrames();
126
+ }
127
+ function syncPoll() {
128
+ if (disposed)
129
+ return;
130
+ const active = [...sessions.values()].some((entry) => entry.view.active);
131
+ if (active && pollTimer === undefined)
132
+ pollTimer = setInterval(refreshTracked, ledgerPollMs);
133
+ if (!active && pollTimer !== undefined) {
134
+ clearInterval(pollTimer);
135
+ pollTimer = undefined;
136
+ }
137
+ }
138
+ function refreshTracked() {
139
+ refreshTimer = undefined;
140
+ if (disposed)
141
+ return;
142
+ for (const sessionID of tracked)
143
+ refresh(sessionID);
144
+ syncPoll();
145
+ }
146
+ function scheduleRefresh() {
147
+ if (!disposed && refreshTimer === undefined)
148
+ refreshTimer = setTimeout(refreshTracked, refreshDelayMs);
149
+ }
150
+ // A session is tracked from its first render. Its existing history raises no toast; only a lit mark
151
+ // that appears after that does.
152
+ function track(sessionID) {
153
+ if (disposed || tracked.includes(sessionID))
154
+ return;
155
+ tracked.push(sessionID);
156
+ if (tracked.length > trackedSessionLimit) {
157
+ const dropped = tracked.shift() ?? "";
158
+ watchers.get(dropped)?.();
159
+ watchers.delete(dropped);
160
+ sessions.delete(dropped);
161
+ ignitions.delete(dropped);
162
+ trackedAt.delete(dropped);
163
+ }
164
+ const now = Date.now();
165
+ trackedAt.set(sessionID, now);
166
+ ignitions.set(sessionID, latestLitMarkV2(context.data, sessionID)?.messageID);
167
+ sessions.set(sessionID, { view: viewFor(sessionID, now), motion: idleMotion });
168
+ queueMicrotask(syncPoll);
169
+ // Opening a session loads its messages over HTTP, which raises no event. Reading the host's reactive
170
+ // store here re-runs this effect as they arrive, so a resumed session lights up too.
171
+ const dispose = ui.createRoot((release) => {
172
+ ui.createEffect(() => {
173
+ for (const message of context.data.session.message.list(sessionID) ?? [])
174
+ void message.id;
175
+ void context.data.session.status(sessionID);
176
+ scheduleRefresh();
177
+ });
178
+ return release;
179
+ });
180
+ watchers.set(sessionID, dispose);
181
+ }
182
+ const unlisten = context.data.listen((event) => {
183
+ const type = event.details.type;
184
+ if (type.startsWith("session.") || type.startsWith("permission.") || type.startsWith("form."))
185
+ scheduleRefresh();
186
+ });
187
+ const releases = [
188
+ context.ui.slot({
189
+ append: "prompt.footer.status",
190
+ render(input) {
191
+ const sessionID = input.sessionID;
192
+ if (sessionID === undefined)
193
+ return null;
194
+ track(sessionID);
195
+ return () => {
196
+ revision();
197
+ const mark = latestLitMarkV2(context.data, sessionID);
198
+ if (mark === undefined)
199
+ return null;
200
+ return ui.jsx("box", {
201
+ flexShrink: 0,
202
+ children: line([{ text: ignitionLine(mark.name), fg: palette().primary, bold: true }])
203
+ });
204
+ };
205
+ }
206
+ }),
207
+ context.ui.slot({
208
+ append: "sidebar.content",
209
+ render(input) {
210
+ track(input.sessionID);
211
+ return () => {
212
+ revision();
213
+ const entry = sessions.get(input.sessionID);
214
+ if (entry === undefined || !entry.view.active)
215
+ return null;
216
+ const panel = missionPanel(entry.view, sampleMotion(entry.motion, clock()), palette());
217
+ if (panel === undefined)
218
+ return null;
219
+ return ui.jsx("box", {
220
+ border: true,
221
+ borderStyle: "rounded",
222
+ width: panelWidth,
223
+ flexShrink: 0,
224
+ flexDirection: "column",
225
+ paddingLeft: 1,
226
+ paddingRight: 1,
227
+ titleAlignment: "left",
228
+ bottomTitleAlignment: "right",
229
+ borderColor: panel.border,
230
+ title: panel.title,
231
+ titleColor: panel.titleColor,
232
+ bottomTitle: panel.bottomTitle ?? "",
233
+ backgroundColor: panel.background ?? "transparent",
234
+ children: panel.rows.map(line)
235
+ });
236
+ };
237
+ }
238
+ })
239
+ ];
240
+ return async () => {
241
+ disposed = true;
242
+ for (const dispose of watchers.values())
243
+ dispose();
244
+ watchers.clear();
245
+ tracked.length = 0;
246
+ unlisten();
247
+ for (const release of releases)
248
+ release();
249
+ if (frameTimer !== undefined)
250
+ clearInterval(frameTimer);
251
+ if (refreshTimer !== undefined)
252
+ clearTimeout(refreshTimer);
253
+ if (pollTimer !== undefined)
254
+ clearInterval(pollTimer);
255
+ sessions.clear();
256
+ ignitions.clear();
257
+ trackedAt.clear();
258
+ };
259
+ }
Binary file
Binary file
Binary file
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.29"><title>release: 1.0.29</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.29</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.30"><title>release: 1.0.30</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.30</text></g></svg>
package/docs/migration.md CHANGED
@@ -52,10 +52,10 @@ Use LitOpenCode vocabulary in docs, config, runtime output, and issue reports:
52
52
  - npm package and OpenCode package-loading entry: `@litfamily/litopencode`; native namespace and binary: `litopencode`
53
53
  - activation commands/tools: `lit`, `litwork`, `start-work`, `review-work`, `lit-recap`, `lit-korean`, `text-neutralization`
54
54
  - durable loop: `lit-loop`
55
- - durable goal manager: `litgoal`
55
+ - durable goal manager: `litgoal` (sets and keeps the finish line; `lit-loop` and `start-work` run toward it)
56
56
  - current planning/research workflow names: `lit-plan`, `lit-crucible`, `litresearch`, `start-work`, `review-work`
57
57
  - `lit-plan` creates a planning-only objective-achievable checklist; `lit-crucible` adversarially tests assumptions before handing surviving insights to `lit-plan`.
58
- - `litresearch` runs the root-owned evidence workflow; `start-work` executes an approved plan through `lit-implement`; `review-work` reviews either a draft plan or completed work without inventing another workflow surface.
58
+ - `litresearch` runs the root-owned evidence workflow; `start-work` executes an approved plan through `lit-implement`; `review-work` runs plan review after `lit-plan` or work review after `lit-loop` or `start-work`, picked from what exists or forced with `lit review plan` / `lit review work`, without inventing another workflow surface.
59
59
 
60
60
  Legacy vocabulary must not appear in tracked files or release payloads. Do not introduce old package names, host claims, or environment prefixes into new product docs, tests, source, fixtures, or release artifacts.
61
61
 
@@ -72,7 +72,8 @@ Legacy vocabulary must not appear in tracked files or release payloads. Do not i
72
72
  ## Skill ID renames
73
73
 
74
74
  Use `lit-crucible`, `lit-init`, `lit-commit`, `lit-burnoff`, `lit-burnoff-file`,
75
- `lit-korean`, `lit-fetch`, and `lit-code`. The Unreleased changelog records the old-to-new
75
+ `lit-korean`, `lit-fetch`, `lit-code`, and `litgoal` (formerly `durable-litgoal`, so the skill,
76
+ the `/litgoal` command and the chat route now share one name). The Unreleased changelog records the old-to-new
76
77
  mapping. Old bare-word invocations select the new skill and inject exactly one line:
77
78
  ``Note: `<old>` was renamed to `<new>`; the old name is removed in the next minor.``
78
79
  Quoted text, code fences, and compound-word
@@ -137,7 +137,9 @@ litopencode install
137
137
  | `/litwork` | 제한된 작업 루프 진입 |
138
138
  | `/lit-plan` | 읽기 전용 구현 계획 작성 |
139
139
  | `/start-work` | 승인된 계획을 `lit-implement`로 실행 |
140
- | `/review-work` | 초안 계획 또는 완료 작업 검토 |
140
+ | `/review-work` | `lit-plan` 뒤에는 계획 검토, `lit-loop`나 `/start-work` 뒤에는 작업 검토. 있는 것을 보고 고르며 `/review-work plan`, `/review-work work`로 직접 정할 수 있음 |
141
+ | `/litgoal` | 끝나는 지점을 정하고 지킴: 확인 가능한 기준이 붙은 목표 하나를 디스크에 남김. `lit-loop`와 `/start-work`가 그 목표를 향해 일함 |
142
+ | `/lit-brainstorm` | 열린 질문을 여러 선택지로 펼친 뒤 짧은 목록으로 좁히고, 고른 것을 `deep-interview`나 `lit-plan`에 넘김. 채팅에서는 `lit brainstorm`, `lit brainstorming`, `lit ideation`, `lit ideate`, `lit idea`, `lit ideas`. 직접 만들지는 않음 |
141
143
  | `/litresearch` 또는 `/lit-research` | 순차 대체 경로를 포함한 근거 기반 연구 |
142
144
  | `/lit-handoff` | 이어서 작업할 수 있는 핸드오프 작성 |
143
145
  | `/lit-recap` | 로컬 ledger와 세션 맥락으로 짧은 리캡 작성 |
@@ -150,6 +152,17 @@ litopencode install
150
152
  `/autoresearch`, `/autoconference`, `/wikify-*` 경로가 있습니다. 이 경로는 안내 또는 제한된
151
153
  로컬 동작을 추가합니다. 호스트 권한은 늘리지 않습니다.
152
154
 
155
+ ### `lit` 반복 입력
156
+
157
+ 메시지마다 `lit`를 붙여도 안전합니다. 세션의 첫 `lit`는 전체 활성화 안내를 보냅니다. 같은 discipline의 `lit`
158
+ (예: 실행 중에 보낸 두 번째 `lit fix ...`)는 전체 안내 대신 "이미 활성, 이어서 진행" 짧은 메모만 보내며, 기존
159
+ 계획, 기준, ledger를 건드리지 않습니다. 다른 discipline(루프 중 `lit plan ...`)은 그 discipline의 전체
160
+ 활성화 안내를 한 번 보냅니다. `lit new ...`와 `lit restart ...`는 새로 시작합니다. 승인된 계획이나 진행 중인
161
+ `/start-work` 기록이 있으면 단독 `lit` 또는 `lit continue`가 그 작업을 이어 가며 새 계획을 만들지 않습니다.
162
+ 작업 내용도 없고 진행 중인 것도 없는 단독 `lit`는 무엇을 할지 되묻습니다. 컴팩션, 새 세션, OpenCode 재시작
163
+ 뒤에는 활성 discipline을 잊으므로 다음 `lit`는 다시 전체 안내를 보냅니다. `lit-loop`, `lit-plan`,
164
+ `lit-humanizer`는 스킬 본문을 붙이지 않고 설치된 스킬을 가리킵니다.
165
+
153
166
  ### Durable loop 상태
154
167
 
155
168
  LitOpenCode는 `.litopencode/litgoal/lit-loop/`에 goal 상태를 저장합니다. append-only
@@ -245,15 +258,104 @@ npm uninstall -g @litfamily/litopencode
245
258
  프로젝트의 `.litopencode/`는 별도 ledger와 receipt입니다. 나중에 재개할 계획이 없다면
246
259
  그 상태를 버려도 되는지 확인한 뒤 직접 정리합니다.
247
260
 
248
- TUI 패널 항목은 `tui.json`이나 `tui.jsonc`에 있습니다. `litopencode tui remove [--root <dir>]`는
249
- 두 파일에서 LitOpenCode 항목만 지우고, 다른 플러그인과 주석은 그대로 둡니다. 바뀌는 파일은 먼저
250
- `<file>.litopencode-backup`으로 복사합니다. `--dry-run`은 같은 결과를 보여 주기만 하고 아무것도 쓰지 않습니다.
261
+ TUI 패널 항목은 `tui.json`이나 `tui.jsonc`에 있고, OpenCode 2에서는 `cli.json`에 고정된 항목이 있을 수도
262
+ 있습니다. `litopencode tui remove [--root <dir>]`는 세 파일에서 LitOpenCode 항목만 지우고, 다른 플러그인과
263
+ 주석은 그대로 둡니다. 바뀌는 파일은 먼저 `<file>.litopencode-backup`으로 복사합니다. 쓰기 전에 모든 파일을
264
+ 읽고 확인하므로, 깨진 파일이나 지워질 주석이 있으면 아무것도 바꾸지 않고 멈춥니다. `--host v1`이면
265
+ `cli.json`은 건드리지 않습니다. `--dry-run`은 같은 결과를 보여 주기만 하고 아무것도 쓰지 않습니다.
266
+
267
+ ## OpenCode 2
268
+
269
+ LitOpenCode는 OpenCode 1(1.18.29 이상. 패키지가 `engines.opencode >=1.18.29`를 선언하고 OpenCode 1이 이를
270
+ 확인합니다)과 OpenCode 2에서 모두 돌아갑니다. 두 호스트를 함께 설치할 수 있습니다. OpenCode 1은 `opencode`
271
+ 명령을 그대로 쓰고, OpenCode 2(npm `@opencode/cli`)는 `opencode2`로 실행합니다. 둘 다 같은
272
+ `~/.config/opencode`를 읽으므로 한 번 설치하면 양쪽에 적용됩니다.
273
+
274
+ **설치.** `litopencode install`은 무엇을 쓰기 전에 호스트부터 찾고, 그때 어느 호스트도 실행하지 않습니다.
275
+ `opencode`와 `opencode2` 뒤에 있는 npm 패키지를 읽습니다(`opencode-ai`는 OpenCode 1, `@opencode/cli`는 OpenCode 2.
276
+ Windows에서는 npm `.cmd` 실행 파일을 글자로 읽습니다). 뒤에 npm 패키지가 없는 `opencode`(curl 설치 스크립트,
277
+ Homebrew, mise)는 일단 OpenCode 1로 봅니다. 어떻게 설치했든 OpenCode 1 설정이 유지되도록 하기 위해서입니다.
278
+ `opencode2`와 같은 파일인 `opencode`는 OpenCode 2로 보고, `opencode2`는 언제나 OpenCode 2로 봅니다. OpenCode 1에는
279
+ `opencode2`가 없기 때문입니다. 실제 설치는 OpenCode 1로 짐작한 `opencode`에 `opencode plugin`을 부르기 전에
280
+ `opencode --version`을 묻고, 2.x라고 답하면 그 호스트에 OpenCode 2 설정을 합니다(dry run은 아무것도 실행하지 않으므로
281
+ 처음 짐작을 보여 줍니다).
282
+ PATH에 두 명령이 모두 없으면 `tui.json`(OpenCode 1)과 `cli.json`(OpenCode 2)을 근거로 삼고, 둘 다 없으면
283
+ OpenCode 1 설정을 유지합니다. `--host v1`이나 `--host both`인데 `opencode`가 OpenCode 2뿐이면 아무것도 실행하기
284
+ 전에 멈춥니다. 그 상태에서 `opencode plugin`을 부르면 OpenCode 2 설정이 바뀌기 때문입니다.
285
+ 기본값인 `--host auto`는 찾은 호스트를 모두 설정하고, `--host v1`, `--host v2`, `--host both`로 직접 정할 수
286
+ 있습니다. dry-run 결과의 `host`에 판단이 나옵니다. OpenCode 1 쪽은 그대로입니다. `opencode plugin`이 패키지를
287
+ 등록하고 TUI 항목은 `tui.json`에 들어갑니다. OpenCode 2 쪽에서 설치 프로그램은 다음을 합니다.
288
+
289
+ - 두 호스트가 함께 읽는 `opencode.json(c)`의 `plugin` 목록에 항목 하나만 둡니다. OpenCode 2 실행 파일로
290
+ `opencode plugin`을 부르지 않습니다. OpenCode 2는 처음 불러올 때 패키지를 직접 설치합니다.
291
+ - OpenCode 2 전용 `plugins` 키에 있는 LitOpenCode 항목을 지웁니다. 같은 id의 플러그인이 둘이면 OpenCode 2가
292
+ 뒤의 것을 거부하기 때문입니다.
293
+ - `cli.json`의 LitOpenCode 항목을 지우고 `cli.json.litopencode-backup`을 남깁니다. OpenCode 2는 활성
294
+ 서버 플러그인의 TUI를 스스로 불러오는데, 거기에 고정된 항목이 있으면 그것이 새 TUI를 가리기 때문입니다.
295
+ - 공유 설정에 OpenCode 2 키 `permissions`나 `policies`를 절대 쓰지 않습니다. OpenCode 1은 그 자리에서
296
+ `permissions`를 보면 시작하지 않습니다.
297
+
298
+ 두 호스트에 설치한 뒤 `opencode2` TUI를 처음 열면 OpenCode 2가 `tui.json`의 플러그인 목록을 새 `cli.json`으로
299
+ 복사하고, 여기에 LitOpenCode 항목도 따라 들어갑니다. 설치 프로그램은 이 복사를 막을 수 없습니다. doctor가 그
300
+ 항목을 알려 주고, `litopencode install`이나 `litopencode tui remove`를 다시 실행하면 지워집니다.
301
+
302
+ **Doctor.** `litopencode doctor`는 `host` 블록(찾은 호스트, 근거 파일, 공유 설정의 `autoupdate`에서 읽은
303
+ OpenCode 2 플러그인 업데이트 방식)을 보여 주고, `plugin`과 `plugins`에 걸친 LitOpenCode 항목 수를 세며,
304
+ `cli.json`에 남은 항목과 OpenCode 1도 읽는 설정에 들어간 `permissions`를 알려 줍니다. doctor는 호스트를
305
+ 실행하지 않습니다. OpenCode 2가 대상이면 `lsp` 블록에 OpenCode 2는 `lsp` 설정을 받아 두기만 하고 언어 서버를
306
+ 돌리지 않는다는 설명이 붙습니다.
307
+
308
+ **똑같이 동작하는 것.** 에이전트(기본은 `lit-loop`, `build`와 `plan`은 숨김), 도구 다섯 개, 단독 `lit` 활성화,
309
+ 정적 규칙과 독자용 계약, 도구 가드, 플래너 잠금, bounded-authority 진행 기록, 자동 인수인계,
310
+ `~/.config/opencode/skills`의 스킬이 모두 OpenCode 2에서 돌아갑니다. OpenCode 2는 도구 두 개의 이름을 바꿨기
311
+ 때문에 권한 규칙은 `bash` 대신 `shell`, `task` 대신 `subagent`를 쓰고, `write`와 `patch`는 `edit`에 들어갑니다.
312
+ 그곳에서는 마지막에 맞는 규칙이 이기므로 `lit-plan`은 어떤 권한 모드에서도 `edit`, `shell`, `subagent`,
313
+ `execute` 거부로 끝나고, 권한 훅이 다른 규칙과 상관없이 한 번 더 거부합니다. `execute`는 OpenCode 2의 Code Mode
314
+ 도구로, 모델이 쓴 JavaScript를 `fetch`와 다른 도구와 함께 실행합니다. OpenCode 1에는 없던 도구이고 계획만 하는
315
+ 에이전트가 가져서는 안 됩니다. 이렇게 거부된 도구는 요청에서 빠집니다. LitOpenCode 도구 다섯 개는 OpenCode 1에서처럼
316
+ Code Mode 밖, 모델의 도구 목록에 바로 있으므로 `lit-plan`도 계속 씁니다. OpenCode 1에서 전역 권한을 넓히는 balanced와 yolo
317
+ 모드는 OpenCode 2에서는 각 LitOpenCode 에이전트 규칙의 앞부분에 들어갑니다. 플러그인이 그곳의 전역 규칙을
318
+ 바꿀 수 없기 때문입니다. 추론 강도 같은 에이전트별 요청 설정은 모델 요청마다 함께 보냅니다. OpenCode 2는
319
+ 에이전트의 요청 설정을 저장만 하고 아직 보내지 않습니다.
320
+
321
+ **OpenCode 2에서 달라지는 것.**
322
+
323
+ - *슬래시 명령.* 설치 프로그램은 두 호스트에 같은 `command/<id>.md` 파일을 쓰고, OpenCode 2에서는 이 파일이
324
+ 같은 이름의 플러그인 명령을 대신합니다. 그래서 모든 lit 명령은 파일에서 오고 목록에 한 번만 나옵니다.
325
+ 플러그인은 파일이 없는 명령만 직접 추가합니다(예를 들어 설치 프로그램 없이 `opencode plugin add`만 한 경우).
326
+ OpenCode 2에서 파일로 실행한 명령 차례에는 LitOpenCode 표시가 붙지 않습니다. 플러그인이 직접 보낸 명령에는
327
+ 한 번만 쓰는 확인값이 든 표시가 붙어, 손으로 입력한 프롬프트가 흉내 낼 수 없습니다. `/lit-crucible`과
328
+ `/deep-interview`(`lit-plan`), `/start-work`(`lit-implement`)처럼 에이전트가 정해진 플러그인 명령은 명령 파일의
329
+ `agent:` 줄과 같이 먼저 세션을 그 에이전트로 바꿉니다.
330
+ - *모델 경로.* OpenCode 2는 자기 모델이 없는 세션을 호스트의 기본 모델로 돌립니다. 에이전트의 모델은 세션을
331
+ 만드는 클라이언트만 읽는데, TUI는 읽고 `opencode2 run`은 읽지 않습니다. 그래서 그런 세션의 첫 프롬프트에서
332
+ 세션을 에이전트의 경로로 바꿉니다(에이전트가 없으면 `lit-loop`). 그 모델이 카탈로그에 있을 때만 바꿉니다.
333
+ 사용자가 고른 모델과, 호스트가 이미 에이전트 모델로 시작하는 하위 세션은 그대로 둡니다. OpenCode 1과 같이
334
+ LitOpenCode가 쓰는 OpenAI 모델이 호스트 카탈로그에 없으면 `openai` 카탈로그에 추가합니다.
335
+ - *사용자 메시지로 가는 프로젝트 데이터.* 로컬 지식 기록, start-work 재개 영수증, 자동 인수인계 설정 응답은
336
+ 요청의 마지막 사용자 메시지에 붙습니다. OpenCode 1에서도 사용자 메시지의 일부였습니다. OpenCode 2의 system
337
+ 부분은 지시를 위한 자리라서, LitOpenCode의 활성화 문구와 규칙, 독자용 계약만 그곳에 들어갑니다.
338
+ - *따옴표 프롬프트.* `opencode2 run "lit fix it"`은 단어를 큰따옴표로 감싸 보내므로, 메시지 전체가 따옴표 한 쌍이면
339
+ 따옴표를 벗겨 읽습니다. 서버는 이것과 TUI에서 통째로 따옴표 안에 입력한 프롬프트를 구별하지 못해서 그런 입력도
340
+ 활성화됩니다. OpenCode 1에서는 활성화되지 않았습니다.
341
+ - *점화 표시.* OpenCode 2에는 끝난 답변을 고치는 훅이 없어서 표시가 TUI로 옮겨 갑니다. 세션의 마지막 프롬프트가
342
+ lit 프롬프트이면 프롬프트 아래 줄(`prompt.footer.status`)에 `🔥 LIT IGNITED · <discipline>`이 나옵니다.
343
+ OpenCode 1에서는 표시가 예전 자리에 그대로 있습니다.
344
+ - *토스트.* OpenCode 2에서는 서버가 토스트를 띄울 수 없습니다. 서버가 사용자 메시지 메타데이터에 남긴 lit 표시를
345
+ TUI가 보고 점화 토스트를 직접 띄웁니다. 서버만 알던 자동 인수인계 알림과 스킬 힌트는 그곳에 나오지 않습니다.
346
+ - *TUI.* 미션 패널은 전처럼 세션 사이드바에 있습니다. OpenCode 2는 서버 플러그인과 함께 오는 TUI에 옵션을
347
+ 넘기지 않고 할 일 목록도 두지 않으므로, 미션 진행은 ledger와 작업 기록과 계획에서 읽습니다. 세션 아래 상태 띠와
348
+ 홈 화면 한 줄은 OpenCode 1에서만 그립니다.
349
+ - *업데이트.* OpenCode 2에서는 플러그인 안의 자동 업데이트가 없습니다. `opencode plugin update`를 실행하거나
350
+ OpenCode 2의 업데이트 방식에 맡기면 됩니다.
251
351
 
252
352
  ## TUI 미션 패널
253
353
 
254
354
  OpenCode 1.18은 `tui.json`이나 `tui.jsonc`의 `plugin` 배열에 적힌 TUI 플러그인을 불러옵니다.
255
- 이 패키지는 TUI 모듈을 서버 플러그인과 따로 `@litfamily/litopencode/tui`(`dist/tui.js`, 플러그인 id
256
- `litopencode`)로 내보냅니다. OpenCode가 두 대상을 한 모듈에 섞는 것을 허용하지 않기 때문입니다.
355
+ 이 패키지는 TUI 모듈을 서버 플러그인과 따로 `@litfamily/litopencode/tui`(`dist/host/tui.js`, 플러그인 id
356
+ `litopencode`)로 내보냅니다. OpenCode가 두 대상을 한 모듈에 섞는 것을 허용하지 않기 때문입니다. OpenCode 2는
357
+ 활성 서버 플러그인을 위해 같은 모듈을 스스로 불러옵니다(위 OpenCode 2 참고). `./server`와 `./tui`의 타입 선언은
358
+ 각 진입점을 모양으로만 적으므로, 어느 호스트의 플러그인 패키지 없이도 해석됩니다.
257
359
 
258
360
  화면에 그리는 것:
259
361
 
@@ -461,7 +563,7 @@ OpenCode는 세션이 바쁜 동안 들어온 `summarize` 호출을 줄 세웠
461
563
  ## 스킬 이름 변경
462
564
 
463
565
  새 명령은 `/lit-crucible`, `/lit-init`, `/lit-commit`, `/lit-burnoff`, `/lit-burnoff-file`,
464
- `/lit-korean`, `/lit-fetch`, `/lit-code`입니다. 이전 이름은 한 릴리스 동안 새 명령으로
566
+ `/lit-korean`, `/lit-fetch`, `/lit-code`, `/litgoal`(이전 `durable-litgoal`)입니다. 이전 이름은 한 릴리스 동안 새 명령으로
465
567
  연결되며, 이름 변경 안내가 한 줄 표시됩니다. 다음 마이너 버전에서 이전 이름을 제거합니다.
466
568
  설치·업데이트는 새 복사본을 검증한 뒤 설치 도구가 관리하는 이전 이름의 스킬 디렉터리를
467
569
  제거합니다. 사용자 소유 파일은 보존합니다. 자세한 내용은 [마이그레이션 안내](migration.md#skill-id-renames)를 참고하세요.
package/docs/reference.md CHANGED
@@ -165,7 +165,9 @@ for an approved plan; `/review-work` checks the `DoneClaim` through a five-lane
165
165
  | `/litwork` | Enter the bounded work loop. |
166
166
  | `/lit-plan` | Create a read-only implementation plan. |
167
167
  | `/start-work` | Execute an approved plan through `lit-implement`. |
168
- | `/review-work` | Review a draft plan or completed work. |
168
+ | `/review-work` | Plan review after `lit-plan` or work review after `lit-loop` or `/start-work`, picked from what exists; `/review-work plan` or `/review-work work` forces one. |
169
+ | `/litgoal` | Set and keep the finish line: one goal with checkable criteria on durable state. `lit-loop` and `/start-work` run toward it. |
170
+ | `/lit-brainstorm` | Open a question into options, narrow them to a short list, and hand the pick to `deep-interview` or `lit-plan`. Chat: `lit brainstorm`, `lit brainstorming`, `lit ideation`, `lit ideate`, `lit idea`, `lit ideas`. It never builds. |
169
171
  | `/litresearch` or `/lit-research` | Run evidence-backed research with a sequential fallback. |
170
172
  | `/lit-handoff` | Create a resumable handoff; exact bare `handoff` also routes here. |
171
173
  | `/lit-recap` | Read a concise recap from local ledger and session context. |
@@ -181,11 +183,23 @@ bounded local behavior; they do not silently grant new host authority. Frontend
181
183
  ships a canonical frontend library for its offline, read-only design references.
182
184
 
183
185
  The renamed routes are `/lit-crucible`, `/lit-init`, `/lit-commit`, `/lit-burnoff`,
184
- `/lit-burnoff-file`, `/lit-korean`, `/lit-fetch`, and `/lit-code`. Previous names remain
186
+ `/lit-burnoff-file`, `/lit-korean`, `/lit-fetch`, `/lit-code`, and `/litgoal` (formerly
187
+ `durable-litgoal`). Previous names remain
185
188
  compatibility redirects for one release and print one deprecation note. Install/update
186
189
  removes installer-owned old skill directories after verifying their replacements; user-owned
187
190
  files are preserved. See [migration notes](migration.md#skill-id-renames).
188
191
 
192
+ ### Repeating `lit`
193
+
194
+ Typing `lit` on every message is safe. The first `lit` of a session sends the full activation. Another `lit`
195
+ for the same discipline, such as a second `lit fix ...` while the first is running, sends a short "already
196
+ active, continuing" note instead and leaves the existing plan, criteria and ledger alone. A different
197
+ discipline (`lit plan ...` during a loop) sends that discipline's full activation once. `lit new ...` and
198
+ `lit restart ...` start fresh. A bare `lit` or `lit continue` over an approved plan or an active `/start-work`
199
+ record resumes it and never writes a new plan; a bare `lit` with no task and nothing in progress asks what to
200
+ work on. A compaction, a new session or a restart of OpenCode forgets the active discipline, so the next `lit`
201
+ is full again. `lit-loop`, `lit-plan` and `lit-humanizer` point to their installed skill instead of pasting it.
202
+
189
203
  ### Durable loop state
190
204
 
191
205
  LitOpenCode owns durable goal state under `.litopencode/litgoal/lit-loop/`. The append-only
@@ -244,11 +258,11 @@ flowchart LR
244
258
  - <code>debugging</code>
245
259
  - <code>deep-interview</code>
246
260
  - <code>doctor-installer</code>
247
- - <code>durable-litgoal</code>
248
261
  - <code>frontend-ui-ux</code>
249
262
  - <code>readme-studio</code>
250
263
  - <code>lit-commit</code>
251
264
  - <code>lit-crucible</code>
265
+ - <code>lit-brainstorm</code>
252
266
  - <code>lit-init</code>
253
267
  - <code>lit-comprehend</code>
254
268
  - <code>lit-handoff</code>
@@ -259,6 +273,7 @@ flowchart LR
259
273
  - <code>lit-pptx</code>
260
274
  - <code>lit-docx</code>
261
275
  - <code>lit-typographic-motion</code>
276
+ - <code>litgoal</code>
262
277
  - <code>litresearch</code>
263
278
  - <code>litwork</code>
264
279
  - <code>lsp</code>
@@ -346,16 +361,114 @@ OpenCode's managed `opencode.jsonc` (or the custom root's `opencode.json`). Remo
346
361
  Project `.litopencode/` state is separate; preserve it for later resume or remove it only
347
362
  when discarding that project's ledger and receipts is intentional.
348
363
 
349
- The TUI panel entry lives in `tui.json` or `tui.jsonc`. `litopencode tui remove [--root <dir>]`
350
- deletes LitOpenCode's own entry from both files, keeps every other plugin and every comment, and
351
- copies each changed file to `<file>.litopencode-backup` first. `--dry-run` prints the same receipt
352
- and writes nothing.
364
+ The TUI panel entry lives in `tui.json` or `tui.jsonc`, and on OpenCode 2 a pinned entry can also sit
365
+ in `cli.json`. `litopencode tui remove [--root <dir>]` deletes LitOpenCode's own entry from all three
366
+ files, keeps every other plugin and every comment, and copies each changed file to
367
+ `<file>.litopencode-backup` first. It reads and checks every file before it writes any, so a malformed
368
+ file or a comment that would be lost stops it with nothing changed. `--host v1` leaves `cli.json` alone.
369
+ `--dry-run` prints the same receipt and writes nothing.
370
+
371
+ ## OpenCode 2
372
+
373
+ LitOpenCode runs on OpenCode 1 (1.18.29 or later; the package declares `engines.opencode >=1.18.29`,
374
+ which OpenCode 1 checks) and on OpenCode 2. The two can be installed side by side: OpenCode 1 keeps the
375
+ `opencode` command, and OpenCode 2 (npm `@opencode/cli`) is run as `opencode2`. Both read the same
376
+ `~/.config/opencode`, so one install serves both.
377
+
378
+ **Install.** `litopencode install` looks for each host before it writes anything, without starting
379
+ either one. It reads the npm package behind `opencode` and `opencode2` (`opencode-ai` is OpenCode 1,
380
+ `@opencode/cli` is OpenCode 2; on Windows it reads the npm `.cmd` shim as text). An `opencode` with no npm
381
+ package behind it (the curl installer, Homebrew, mise) is taken for OpenCode 1, so OpenCode 1 keeps its
382
+ setup however it was installed; an `opencode` that is the same file as `opencode2` counts as OpenCode 2.
383
+ Any `opencode2` counts as OpenCode 2, since OpenCode 1 has no `opencode2`. Before a real install runs
384
+ `opencode plugin` on an `opencode` it only took for OpenCode 1, it asks `opencode --version`; a 2.x answer
385
+ gives that host the OpenCode 2 setup instead (a dry run starts nothing, so it shows the first guess). When neither command is on PATH, it takes `tui.json`
386
+ (OpenCode 1) and `cli.json` (OpenCode 2) as evidence, and with neither it keeps the OpenCode 1 setup.
387
+ `--host v1` or `--host both` stops before anything runs when the only `opencode` is OpenCode 2, because
388
+ `opencode plugin` would then change OpenCode 2's config. With `--host auto`, the default, it sets up
389
+ every host it found; `--host v1`, `--host v2` or `--host both` overrides that. The dry-run receipt shows the
390
+ result under `host`. For OpenCode 1 nothing changes: `opencode plugin` registers the package and the TUI
391
+ entry goes into `tui.json`. For OpenCode 2 the installer:
392
+
393
+ - keeps the single entry in the `plugin` list of `opencode.json(c)`, which both hosts read, and never
394
+ runs `opencode plugin` with the OpenCode 2 binary (OpenCode 2 installs the package itself the first
395
+ time it loads it);
396
+ - removes any LitOpenCode entry from OpenCode 2's own `plugins` key, because OpenCode 2 refuses a second
397
+ plugin with the same id;
398
+ - removes any LitOpenCode entry from `cli.json`, with a `cli.json.litopencode-backup` copy, because
399
+ OpenCode 2 loads the TUI of an active server plugin on its own and a pinned entry there would shadow it;
400
+ - never writes the OpenCode 2 keys `permissions` or `policies` into the shared config: OpenCode 1 stops
401
+ at startup when it finds `permissions` there.
402
+
403
+ After an install for both hosts, the first `opencode2` TUI start copies the `tui.json` plugin list into a
404
+ new `cli.json`, LitOpenCode's entry included. The installer cannot stop that copy; doctor flags the
405
+ entry, and running `litopencode install` or `litopencode tui remove` again takes it out.
406
+
407
+ **Doctor.** `litopencode doctor` adds a `host` block (hosts found, the evidence files, and OpenCode 2's
408
+ plugin update policy, read from the shared `autoupdate` setting), counts LitOpenCode entries across
409
+ `plugin` and `plugins`, flags a stale `cli.json` entry, and warns when `permissions` sits in a config that
410
+ OpenCode 1 also reads. Doctor never starts a host. On OpenCode 2 the `lsp` block adds that OpenCode 2
411
+ accepts the `lsp` settings but does not run language servers.
412
+
413
+ **What works the same.** The agents (with `lit-loop` as the default, and `build` and `plan` hidden), the
414
+ five tools, bare `lit` activation, the static rules and the reader contract, the tool guards, the
415
+ planner lock, bounded-authority progress, the automatic handoff and the skills under
416
+ `~/.config/opencode/skills` all run on OpenCode 2. OpenCode 2 renamed two tools, so the permission rules
417
+ it gets say `shell` for `bash` and `subagent` for `task`, and `write` and `patch` fall under `edit`. The
418
+ last matching rule wins there, so `lit-plan` ends with denials for `edit`, `shell`, `subagent` and
419
+ `execute` in every permission mode, and a permission hook denies them again whatever other rules say.
420
+ `execute` is OpenCode 2's Code Mode tool, which runs model-written JavaScript with `fetch` and other
421
+ tools; OpenCode 1 had nothing like it, and a planning agent must not get it. A tool denied that way is left
422
+ out of the request. The five LitOpenCode tools sit on the model's own tool list, outside Code Mode, as
423
+ they did on OpenCode 1, so `lit-plan` keeps them. Balanced and yolo
424
+ modes, which widen the global permission on OpenCode 1, lead each LitOpenCode agent's own rules on
425
+ OpenCode 2, because a plugin cannot change the global rules there. Per-agent request settings such as the
426
+ reasoning effort are sent with each model request, since OpenCode 2 keeps an agent's own request
427
+ settings but does not send them yet.
428
+
429
+ **What is different on OpenCode 2.**
430
+
431
+ - *Slash commands.* The installer writes the same `command/<id>.md` files on both hosts, and on OpenCode 2
432
+ such a file replaces a plugin command of the same name. So every lit command comes from its file and is
433
+ listed once. The plugin adds a command only when no file covers it (for example after
434
+ `opencode plugin add` without the installer). A command turn from a file carries no LitOpenCode mark on
435
+ OpenCode 2; one the plugin itself sends carries a mark with a one-time check that a typed prompt cannot
436
+ copy. A plugin command with an agent route, such as `/lit-crucible` or `/deep-interview` (`lit-plan`) or
437
+ `/start-work` (`lit-implement`), first switches the session to that agent, as the `agent:` line of
438
+ the command file does.
439
+ - *Model routes.* OpenCode 2 runs a session that has no model of its own on the host's default model;
440
+ only a client that creates the session reads the agent's model, and the TUI does while
441
+ `opencode2 run` does not. So the first prompt of such a session switches it to its agent's route
442
+ (`lit-loop` when it names none), once that model is in the catalog. A model you picked, and a
443
+ delegated child, which the host already starts on its agent's model, stay as they are. As on OpenCode 1,
444
+ the OpenAI rows LitOpenCode routes to are added to the `openai` catalog when the host lacks one.
445
+ - *Project data in the user message.* Local knowledge records, a start-work resume receipt and an
446
+ auto-handoff setting reply are added to the latest user message of the request, where OpenCode 1 had
447
+ them as parts of the user message. OpenCode 2's system parts are for instructions, so only LitOpenCode's
448
+ own activation text, the rules and the reader contract go there.
449
+ - *Quoted prompts.* `opencode2 run "lit fix it"` sends the words inside double quotes, so a message that
450
+ is exactly one quoted string is read without the quotes. The server cannot tell that from a TUI prompt
451
+ typed entirely in quotes, which therefore activates too; on OpenCode 1 it stayed inert.
452
+ - *Ignition mark.* OpenCode 2 has no hook that edits the finished answer, so the mark moves into the
453
+ TUI: the line under the prompt (`prompt.footer.status`) reads `🔥 LIT IGNITED · <discipline>` while the
454
+ latest prompt of the session is a lit prompt. On OpenCode 1 the mark stays where it was.
455
+ - *Toasts.* The server cannot show toasts on OpenCode 2. The TUI raises the ignition toast itself when a
456
+ user message arrives with the lit mark the server writes into its metadata. The automatic-handoff
457
+ notices and the skill hint, which only the server knew about, are not shown there.
458
+ - *TUI.* The mission panel sits in the session sidebar as before. OpenCode 2 passes no options to a TUI
459
+ that comes with a server plugin, and it keeps no todo list, so mission progress comes from the ledger,
460
+ the work record and the plan. The status band under the session and the home-screen line are drawn on
461
+ OpenCode 1 only.
462
+ - *Updates.* There is no automatic self-update inside the plugin on OpenCode 2. Run
463
+ `opencode plugin update`, or let OpenCode 2's own update policy handle it.
353
464
 
354
465
  ## TUI mission panel
355
466
 
356
467
  OpenCode 1.18 loads TUI plugins listed in the `plugin` array of `tui.json` or `tui.jsonc`. The
357
- package exports its TUI module as `@litfamily/litopencode/tui` (`dist/tui.js`, plugin id
358
- `litopencode`), separate from the server plugin, as OpenCode requires.
468
+ package exports its TUI module as `@litfamily/litopencode/tui` (`dist/host/tui.js`, plugin id
469
+ `litopencode`), separate from the server plugin, as OpenCode requires. OpenCode 2 loads the same module
470
+ by itself for the active server plugin (see OpenCode 2 above). The type declarations of `./server` and
471
+ `./tui` describe each entry by its shape, so they resolve without either host's plugin package.
359
472
 
360
473
  What it draws:
361
474