@litfamily/litopencode 1.0.34 → 1.0.36
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 +12 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/activation-primary-prompts.js +7 -3
- package/dist/activation.js +5 -0
- package/dist/adhd-next.d.ts +1 -1
- package/dist/adhd-next.js +11 -0
- package/dist/cli/agent-file-command.d.ts +21 -0
- package/dist/cli/agent-file-command.js +99 -0
- package/dist/cli/agent-file.d.ts +46 -0
- package/dist/cli/agent-file.js +197 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +25 -1
- package/dist/cli/doctor.js +10 -0
- package/dist/cli/install.d.ts +4 -1
- package/dist/cli/install.js +8 -3
- package/dist/cli/managed-block.d.ts +26 -0
- package/dist/cli/managed-block.js +80 -0
- package/dist/cli/model-routing.d.ts +5 -0
- package/dist/cli/model-routing.js +1 -1
- package/dist/cli/types.d.ts +10 -0
- package/dist/cli.js +14 -1
- package/dist/commands.js +17 -4
- package/dist/native-goal-ledger.d.ts +43 -0
- package/dist/native-goal-ledger.js +74 -0
- package/dist/v2/commands.d.ts +1 -0
- package/dist/v2/commands.js +2 -2
- package/dist/v2/context.d.ts +6 -1
- package/dist/v2/native-goal-runtime.d.ts +74 -0
- package/dist/v2/native-goal-runtime.js +193 -0
- package/dist/v2/native-goal.d.ts +49 -0
- package/dist/v2/native-goal.js +71 -0
- package/dist/v2/server.js +11 -1
- package/dist/v2/session-hooks.d.ts +4 -0
- package/dist/v2/session-hooks.js +13 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/migration.md +1 -1
- package/docs/reference-Ko-KR.md +27 -0
- package/docs/reference.md +33 -3
- package/package.json +2 -2
- package/skills/lit-adhd/SKILL.md +3 -3
- package/skills/lit-adhd/references/brief-format.md +1 -1
- package/skills/lit-adhd/references/status-taxonomy.md +1 -1
- package/skills/lit-adhd/scripts/rewind.mjs +73 -14
- package/skills/lit-init/SKILL.md +4 -0
- package/skills/litgoal/SKILL.md +2 -2
- package/skills/litwork/SKILL.md +4 -0
- package/skills/managed-skill-manifest.json +3 -3
- package/skills/native-goal-verdict/SKILL.md +34 -60
- package/skills/start-work/SKILL.md +4 -0
- package/skills/workflow-loop/SKILL.md +4 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Drives OpenCode 2's native `/goal` for lit-loop, start-work and litwork. The host has no goal setter:
|
|
2
|
+
// the goal is read through the `opencode.goal` RPC and changed by sending the `/goal` command to the
|
|
3
|
+
// session (`session.command`). Policy lives in native-goal.ts; this file reads the state, performs the one
|
|
4
|
+
// action it returns, prints one line per change as a notice row, and records it in the ledger. Nothing here
|
|
5
|
+
// throws: a failed step is recorded and the work carries on with the ledger alone.
|
|
6
|
+
import { ledgerGoalDone, nativeGoalOwnership, recordNativeGoal, recordNativeGoalUnavailable } from "../native-goal-ledger.js";
|
|
7
|
+
import { litOpenCodeCommands } from "../commands.js";
|
|
8
|
+
import { decideNativeGoal, nativeGoalLine, nativeGoalObjective, nativeGoalRoute } from "./native-goal.js";
|
|
9
|
+
/** The `opencode.goal` RPC contract as JSON Schema, so the package needs no runtime import of the host's schema library. */
|
|
10
|
+
const goalDefinition = Object.freeze({
|
|
11
|
+
id: "opencode.goal",
|
|
12
|
+
methods: Object.freeze({
|
|
13
|
+
get: Object.freeze({
|
|
14
|
+
input: Object.freeze({ type: "object", properties: Object.freeze({ sessionID: Object.freeze({ type: "string" }) }), required: Object.freeze(["sessionID"]) }),
|
|
15
|
+
output: Object.freeze({})
|
|
16
|
+
})
|
|
17
|
+
}),
|
|
18
|
+
events: Object.freeze({
|
|
19
|
+
changed: Object.freeze({ schema: Object.freeze({ type: "object", properties: Object.freeze({ sessionID: Object.freeze({ type: "string" }) }), required: Object.freeze(["sessionID"]) }) })
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function parseGoal(value) {
|
|
26
|
+
if (value === null || value === undefined)
|
|
27
|
+
return null;
|
|
28
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.objective !== "string")
|
|
29
|
+
return "unreadable";
|
|
30
|
+
if (value.status !== "active" && value.status !== "paused" && value.status !== "complete")
|
|
31
|
+
return "unreadable";
|
|
32
|
+
return { id: value.id, objective: value.objective, status: value.status, ...(typeof value.reason === "string" ? { reason: value.reason } : {}) };
|
|
33
|
+
}
|
|
34
|
+
/** OpenCode 2.2.5 answers an RPC it does not register with type `rpc.unavailable`; any other failure is a failed read. */
|
|
35
|
+
function hostLacksGoalRpc(error) {
|
|
36
|
+
return isRecord(error) && error.type === "rpc.unavailable";
|
|
37
|
+
}
|
|
38
|
+
function failureDetail(error) {
|
|
39
|
+
const text = error instanceof Error ? error.message : isRecord(error) ? String(error.message ?? error.type ?? "failed") : String(error);
|
|
40
|
+
return text.replace(/\s+/gu, " ").slice(0, 160);
|
|
41
|
+
}
|
|
42
|
+
/** A prompt the host expanded from an installed command alias file arrives as the command body, unmarked. */
|
|
43
|
+
export function aliasGoalRoute(input) {
|
|
44
|
+
const text = input.trimStart();
|
|
45
|
+
for (const command of litOpenCodeCommands) {
|
|
46
|
+
const head = command.activationText.trim();
|
|
47
|
+
if (head === "" || !text.startsWith(head))
|
|
48
|
+
continue;
|
|
49
|
+
const route = nativeGoalRoute(command.id, "");
|
|
50
|
+
return route === undefined ? undefined : { route, work: text.slice(head.length) };
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
export function createNativeGoal(options) {
|
|
55
|
+
const { projectRoot, host } = options;
|
|
56
|
+
const client = host.rpc === undefined ? undefined : host.rpc(goalDefinition);
|
|
57
|
+
const said = new Set();
|
|
58
|
+
const running = new Set();
|
|
59
|
+
async function read(sessionID) {
|
|
60
|
+
if (client === undefined)
|
|
61
|
+
return { supported: false };
|
|
62
|
+
try {
|
|
63
|
+
return { supported: true, goal: parseGoal(await client.get({ sessionID })) };
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return hostLacksGoalRpc(error) ? { supported: false } : { supported: true, goal: "unreadable" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function notice(sessionID, line, key) {
|
|
70
|
+
if (line === "" || said.has(key))
|
|
71
|
+
return;
|
|
72
|
+
said.add(key);
|
|
73
|
+
try {
|
|
74
|
+
await host.session.synthetic({ sessionID, text: line, description: line, metadata: { source: "litopencode-goal" }, resume: false });
|
|
75
|
+
}
|
|
76
|
+
catch { // no-excuse-ok: catch -- a missing notice row must not stop the work; the ledger holds the record.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function fail(sessionID, route, step, error) {
|
|
81
|
+
await recordNativeGoal(projectRoot, sessionID, route, { type: "native-goal.failed", step, detail: failureDetail(error) });
|
|
82
|
+
}
|
|
83
|
+
async function perform(sessionID, route, action, goal, work) {
|
|
84
|
+
const record = goal === null || goal === "unreadable" ? undefined : goal;
|
|
85
|
+
const key = (name) => `${sessionID}:${name}:${record?.id ?? ""}:${action.kind === "report-conflict" ? action.reason : ""}`;
|
|
86
|
+
switch (action.kind) {
|
|
87
|
+
case "none":
|
|
88
|
+
case "keep":
|
|
89
|
+
return;
|
|
90
|
+
case "unavailable": {
|
|
91
|
+
if (route !== undefined)
|
|
92
|
+
await recordNativeGoalUnavailable(projectRoot, sessionID, route, "opencode-2-without-goal-rpc");
|
|
93
|
+
await notice(sessionID, nativeGoalLine(action, { route }), key("unavailable"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
case "report-user-goal": {
|
|
97
|
+
if (!said.has(key("user-goal")) && record !== undefined) {
|
|
98
|
+
await recordNativeGoal(projectRoot, sessionID, route, { type: "native-goal.left-alone", goalID: record.id, status: record.status });
|
|
99
|
+
}
|
|
100
|
+
await notice(sessionID, nativeGoalLine(action, { route, ...(record === undefined ? {} : { goal: record }) }), key("user-goal"));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
case "report-conflict": {
|
|
104
|
+
if (!said.has(key("conflict"))) {
|
|
105
|
+
await recordNativeGoal(projectRoot, sessionID, route, { type: "native-goal.conflict", goalID: record?.id ?? "", reason: action.reason });
|
|
106
|
+
}
|
|
107
|
+
await notice(sessionID, nativeGoalLine(action, { route, ...(record === undefined ? {} : { goal: record }) }), key("conflict"));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
case "create": {
|
|
111
|
+
const objective = nativeGoalObjective(work, sessionID);
|
|
112
|
+
try {
|
|
113
|
+
await host.session.command({ sessionID, name: "goal", text: objective, delivery: "queue" });
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
await fail(sessionID, route, "create", error);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const after = await read(sessionID);
|
|
120
|
+
const created = after.supported && after.goal !== null && after.goal !== "unreadable" && after.goal.objective === objective ? after.goal : undefined;
|
|
121
|
+
if (created === undefined) {
|
|
122
|
+
await fail(sessionID, route, "create", "the goal was not readable after /goal; ownership not claimed");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await recordNativeGoal(projectRoot, sessionID, route, { type: "native-goal.created", goalID: created.id });
|
|
126
|
+
await notice(sessionID, nativeGoalLine(action, { route, goal: created }), key("created"));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
case "resume":
|
|
130
|
+
case "clear": {
|
|
131
|
+
if (record === undefined)
|
|
132
|
+
return;
|
|
133
|
+
const control = action.kind;
|
|
134
|
+
try {
|
|
135
|
+
await host.session.command({ sessionID, name: "goal", text: control, delivery: "queue" });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
await fail(sessionID, route, control, error);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const event = control === "resume" ? { type: "native-goal.resumed", goalID: record.id } : { type: "native-goal.cleared", goalID: record.id };
|
|
142
|
+
await recordNativeGoal(projectRoot, sessionID, route, event);
|
|
143
|
+
await notice(sessionID, nativeGoalLine(action, { route, goal: record }), key(control));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** One decision for one session: `route` set on an activation, absent on an idle check. */
|
|
149
|
+
async function step(sessionID, route, work) {
|
|
150
|
+
const owned = await nativeGoalOwnership(projectRoot, sessionID);
|
|
151
|
+
if (route === undefined && owned.goalID === undefined)
|
|
152
|
+
return;
|
|
153
|
+
const state = await read(sessionID);
|
|
154
|
+
const goal = state.supported ? state.goal : null;
|
|
155
|
+
const action = decideNativeGoal({
|
|
156
|
+
route,
|
|
157
|
+
supported: state.supported,
|
|
158
|
+
goal,
|
|
159
|
+
ownedGoalID: owned.goalID,
|
|
160
|
+
ledgerDone: owned.goalID === undefined ? false : await ledgerGoalDone(projectRoot, sessionID, owned.createdAt),
|
|
161
|
+
resumes: owned.resumes
|
|
162
|
+
});
|
|
163
|
+
await perform(sessionID, route, action, goal, work);
|
|
164
|
+
}
|
|
165
|
+
/** One step at a time per session, so a repeated activation sees the goal the first one made. */
|
|
166
|
+
const chains = new Map();
|
|
167
|
+
function track(sessionID, run) {
|
|
168
|
+
const previous = chains.get(sessionID) ?? Promise.resolve();
|
|
169
|
+
const safe = previous.then(run).catch(() => undefined).finally(() => {
|
|
170
|
+
running.delete(safe);
|
|
171
|
+
if (chains.get(sessionID) === safe)
|
|
172
|
+
chains.delete(sessionID);
|
|
173
|
+
});
|
|
174
|
+
chains.set(sessionID, safe);
|
|
175
|
+
running.add(safe);
|
|
176
|
+
return safe;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
/** Starts the goal step for a qualifying activation without holding up the user's prompt. */
|
|
180
|
+
activate(sessionID, route, work) {
|
|
181
|
+
return track(sessionID, () => step(sessionID, route, work));
|
|
182
|
+
},
|
|
183
|
+
/** The session finished its model work: clear a finished owned goal or report a conflict. */
|
|
184
|
+
idle(sessionID) {
|
|
185
|
+
return track(sessionID, () => step(sessionID, undefined, ""));
|
|
186
|
+
},
|
|
187
|
+
/** Resolves when every started goal step has finished. */
|
|
188
|
+
async settled() {
|
|
189
|
+
while (running.size > 0)
|
|
190
|
+
await Promise.all([...running]);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type NativeGoalRoute = "lit-loop" | "start-work" | "litwork";
|
|
2
|
+
export type NativeGoalRecord = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly objective: string;
|
|
5
|
+
readonly status: "active" | "paused" | "complete";
|
|
6
|
+
readonly reason?: string;
|
|
7
|
+
};
|
|
8
|
+
export type NativeGoalInput = {
|
|
9
|
+
/** The route that is activating now; absent on an idle check. */
|
|
10
|
+
readonly route: string | undefined;
|
|
11
|
+
/** Whether this host answers the `opencode.goal` RPC. */
|
|
12
|
+
readonly supported: boolean;
|
|
13
|
+
/** The host's goal for the session: null when none, "unreadable" when the read failed. */
|
|
14
|
+
readonly goal: NativeGoalRecord | null | "unreadable";
|
|
15
|
+
/** The goal id LitOpenCode created for this session, from its ledger. */
|
|
16
|
+
readonly ownedGoalID: string | undefined;
|
|
17
|
+
/** Whether LitOpenCode's evidence ledger marks the session's goal complete. */
|
|
18
|
+
readonly ledgerDone: boolean;
|
|
19
|
+
/** How many times LitOpenCode already resumed the owned goal. */
|
|
20
|
+
readonly resumes: number;
|
|
21
|
+
};
|
|
22
|
+
export type NativeGoalAction = {
|
|
23
|
+
readonly kind: "none";
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "create";
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "keep";
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: "resume";
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: "clear";
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "report-user-goal";
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "report-conflict";
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "unavailable";
|
|
39
|
+
};
|
|
40
|
+
/** The qualifying route for a chat mode or command id, or undefined for every other route. */
|
|
41
|
+
export declare function nativeGoalRoute(mode: string, text: string): NativeGoalRoute | undefined;
|
|
42
|
+
export declare function decideNativeGoal(input: NativeGoalInput): NativeGoalAction;
|
|
43
|
+
/** The text sent to `/goal`: the work, then the conditions OpenCode's reviewer judges it against. */
|
|
44
|
+
export declare function nativeGoalObjective(work: string, sessionID?: string): string;
|
|
45
|
+
/** The single line announced for an action; an empty string when nothing changed. */
|
|
46
|
+
export declare function nativeGoalLine(action: NativeGoalAction, detail: {
|
|
47
|
+
readonly route?: string;
|
|
48
|
+
readonly goal?: Pick<NativeGoalRecord, "id" | "status" | "objective">;
|
|
49
|
+
}): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Policy for OpenCode 2's native `/goal`. It is pure: the route, the goal record the host reports and the
|
|
2
|
+
// state of LitOpenCode's own ledger go in; one action comes out. The host has no setter for a goal, only
|
|
3
|
+
// the `/goal` command, and that command replaces any goal that exists, so every create is decided here
|
|
4
|
+
// first. LitOpenCode manages only a goal it created (the ledger remembers the id); a goal the user made
|
|
5
|
+
// is reported and never touched. Done needs two things: OpenCode's verdict and the ledger.
|
|
6
|
+
import { containsSecret } from "../secret-shapes.js";
|
|
7
|
+
const goalRoutes = new Set(["lit-loop", "start-work", "litwork"]);
|
|
8
|
+
/** The host pauses a goal on a restart; it is the only pause that is safe to resume without asking. */
|
|
9
|
+
const restartPause = "The server restarted while the goal was active";
|
|
10
|
+
const maxResumes = 2;
|
|
11
|
+
const maxObjective = 4000;
|
|
12
|
+
const completionConditions = "Done only when all of these hold: the change works and the project's tests pass; a real-surface check of the path a user touches has run; temporary files are cleaned up with a cleanup receipt; and LitOpenCode's evidence ledger (.litopencode/litgoal) marks the goal complete.";
|
|
13
|
+
/** The qualifying route for a chat mode or command id, or undefined for every other route. */
|
|
14
|
+
export function nativeGoalRoute(mode, text) {
|
|
15
|
+
if (mode === "lit-loop" || mode === "start-work")
|
|
16
|
+
return mode;
|
|
17
|
+
if (mode === "litwork" || mode === "lit-work")
|
|
18
|
+
return "litwork";
|
|
19
|
+
if (mode !== "lit-task")
|
|
20
|
+
return undefined;
|
|
21
|
+
const first = text.trim().replace(/^\//u, "").split(/\s+/u)[0]?.toLowerCase();
|
|
22
|
+
return first === "litwork" || first === "lit-work" ? "litwork" : undefined;
|
|
23
|
+
}
|
|
24
|
+
export function decideNativeGoal(input) {
|
|
25
|
+
const routed = input.route !== undefined;
|
|
26
|
+
if (routed && !goalRoutes.has(input.route))
|
|
27
|
+
return { kind: "none" };
|
|
28
|
+
if (!input.supported)
|
|
29
|
+
return routed ? { kind: "unavailable" } : { kind: "none" };
|
|
30
|
+
const goal = input.goal;
|
|
31
|
+
if (goal === "unreadable")
|
|
32
|
+
return routed ? { kind: "report-conflict", reason: "the goal state could not be read, so none was set" } : { kind: "none" };
|
|
33
|
+
if (goal === null)
|
|
34
|
+
return routed ? { kind: "create" } : { kind: "none" };
|
|
35
|
+
if (input.ownedGoalID === undefined || goal.id !== input.ownedGoalID)
|
|
36
|
+
return routed ? { kind: "report-user-goal" } : { kind: "none" };
|
|
37
|
+
if (goal.status === "complete") {
|
|
38
|
+
return input.ledgerDone ? { kind: "clear" } : { kind: "report-conflict", reason: "OpenCode says the goal is met but the ledger is still open" };
|
|
39
|
+
}
|
|
40
|
+
if (goal.status === "paused") {
|
|
41
|
+
if (goal.reason === restartPause && input.resumes < maxResumes)
|
|
42
|
+
return routed ? { kind: "resume" } : { kind: "none" };
|
|
43
|
+
return routed ? { kind: "report-conflict", reason: `the goal is paused${goal.reason === undefined ? "" : ` (${oneLine(goal.reason, 60)})`} and needs a person to resume it` } : { kind: "none" };
|
|
44
|
+
}
|
|
45
|
+
return { kind: "keep" };
|
|
46
|
+
}
|
|
47
|
+
function oneLine(value, limit) {
|
|
48
|
+
const flat = value.replace(/\s+/gu, " ").trim();
|
|
49
|
+
return flat.length <= limit ? flat : `${flat.slice(0, limit - 1)}…`;
|
|
50
|
+
}
|
|
51
|
+
/** The text sent to `/goal`: the work, then the conditions OpenCode's reviewer judges it against. */
|
|
52
|
+
export function nativeGoalObjective(work, sessionID) {
|
|
53
|
+
const flat = work.replace(/\s+/gu, " ").trim();
|
|
54
|
+
const subject = flat === "" || containsSecret(flat) ? "the work requested in this session" : flat;
|
|
55
|
+
const conditions = sessionID === undefined || !/^[A-Za-z0-9_-]{1,80}$/u.test(sessionID) ? completionConditions : `${completionConditions} Open that ledger goal with --session-id ${sessionID}.`;
|
|
56
|
+
const room = maxObjective - conditions.length - 2;
|
|
57
|
+
return `${oneLine(subject, room)}\n\n${conditions}`;
|
|
58
|
+
}
|
|
59
|
+
/** The single line announced for an action; an empty string when nothing changed. */
|
|
60
|
+
export function nativeGoalLine(action, detail) {
|
|
61
|
+
const id = detail.goal === undefined ? "" : ` ${detail.goal.id.slice(0, 8)}`;
|
|
62
|
+
switch (action.kind) {
|
|
63
|
+
case "create": return `LitOpenCode goal: created for ${detail.route ?? "this work"}; OpenCode judges it, the ledger must agree`;
|
|
64
|
+
case "resume": return `LitOpenCode goal: resumed${id} after a server restart`;
|
|
65
|
+
case "clear": return `LitOpenCode goal: cleared${id}; OpenCode's verdict and the ledger both say done`;
|
|
66
|
+
case "report-user-goal": return `LitOpenCode goal: left yours alone${detail.goal === undefined ? "" : ` (${detail.goal.status}: ${oneLine(detail.goal.objective, 60)})`}`;
|
|
67
|
+
case "report-conflict": return `LitOpenCode goal: conflict, ${oneLine(action.reason, 120)}; not reporting done`;
|
|
68
|
+
case "unavailable": return "LitOpenCode goal: native-goal-unavailable (this OpenCode has no /goal); the ledger carries the work";
|
|
69
|
+
default: return "";
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/v2/server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { applyLitAgents, createSessionModelRoute, enforcePlanningOnly, planLitAg
|
|
|
11
11
|
import { createSessionLookupV2, projectRootFromLocation } from "./context.js";
|
|
12
12
|
import { createV2Commands } from "./commands.js";
|
|
13
13
|
import { createV2Events } from "./events.js";
|
|
14
|
+
import { createNativeGoal } from "./native-goal-runtime.js";
|
|
14
15
|
import { createV2ToolHooks } from "./hooks.js";
|
|
15
16
|
import { createV2SessionHooks } from "./session-hooks.js";
|
|
16
17
|
import { addLitTools, litToolsForProject } from "./tools.js";
|
|
@@ -46,6 +47,7 @@ export async function setupLitOpenCodeServer(context) {
|
|
|
46
47
|
model: context.model,
|
|
47
48
|
onSessionDeleted: (sessionID) => sessionHooks.clear(sessionID)
|
|
48
49
|
});
|
|
50
|
+
const nativeGoal = createNativeGoal({ projectRoot: root, host: { ...(context.rpc === undefined ? {} : { rpc: context.rpc }), session: context.session } });
|
|
49
51
|
const sessionHooks = createV2SessionHooks({
|
|
50
52
|
projectRoot: root,
|
|
51
53
|
config: loaded.config,
|
|
@@ -54,14 +56,22 @@ export async function setupLitOpenCodeServer(context) {
|
|
|
54
56
|
requestOptions: agentPlan.requestOptions,
|
|
55
57
|
autoHandoffRoute: events.route,
|
|
56
58
|
extraSystem: events.systemText,
|
|
57
|
-
acceptCommandMark: commands.acceptMark
|
|
59
|
+
acceptCommandMark: commands.acceptMark,
|
|
60
|
+
nativeGoal
|
|
58
61
|
});
|
|
59
62
|
await context.session.hook("prompt", sessionHooks.prompt);
|
|
60
63
|
await context.session.hook("context", sessionHooks.context);
|
|
61
64
|
await context.session.hook("compaction", sessionHooks.compaction);
|
|
65
|
+
try {
|
|
66
|
+
await context.session.hook("experimental.idle", ({ sessionID }) => nativeGoal.idle(sessionID));
|
|
67
|
+
}
|
|
68
|
+
catch { // no-excuse-ok: catch -- an OpenCode 2 build without the idle hook has no native /goal either; the plugin still starts.
|
|
69
|
+
// Activation-time goal handling still runs; only the finished-goal check is missing.
|
|
70
|
+
}
|
|
62
71
|
const stopEvents = events.start(context.event);
|
|
63
72
|
return async () => {
|
|
64
73
|
await stopEvents();
|
|
74
|
+
await nativeGoal.settled();
|
|
65
75
|
deliverableHedgeGuard.dispose();
|
|
66
76
|
await logger.dispose();
|
|
67
77
|
};
|
|
@@ -2,6 +2,7 @@ import { type ChatActivationMode } from "../activation-routing.ts";
|
|
|
2
2
|
import { type AutoHandoffRouteMode } from "../auto-handoff.ts";
|
|
3
3
|
import type { LitOpenCodeConfig } from "../config.ts";
|
|
4
4
|
import type { AgentRequestOptions } from "./agents.ts";
|
|
5
|
+
import { type NativeGoal } from "./native-goal-runtime.ts";
|
|
5
6
|
export type SystemPartLike = {
|
|
6
7
|
readonly type: "text";
|
|
7
8
|
readonly text: string;
|
|
@@ -45,7 +46,10 @@ export type V2SessionHookOptions = {
|
|
|
45
46
|
readonly acceptCommandMark?: (sessionID: string, marker: unknown) => {
|
|
46
47
|
readonly command: string;
|
|
47
48
|
readonly banner: string;
|
|
49
|
+
readonly work?: string;
|
|
48
50
|
} | undefined;
|
|
51
|
+
/** Sets and manages OpenCode 2's native goal for lit-loop, start-work and litwork. */
|
|
52
|
+
readonly nativeGoal?: NativeGoal;
|
|
49
53
|
};
|
|
50
54
|
/**
|
|
51
55
|
* `opencode2 run "lit fix it"` sends the message wrapped in double quotes (each multi-word argument is
|
package/dist/v2/session-hooks.js
CHANGED
|
@@ -17,6 +17,8 @@ import { startTypedWork, typedStartArgument } from "../start-work-typed.js";
|
|
|
17
17
|
import { createLitGoalOperations } from "../ledger.js";
|
|
18
18
|
import { applyCompactionRuleReset, applyStaticRuleInjection } from "../rules/hooks.js";
|
|
19
19
|
import { isReadOnlyWorkflowFamilyPlan } from "../workflow-families.js";
|
|
20
|
+
import { aliasGoalRoute } from "./native-goal-runtime.js";
|
|
21
|
+
import { nativeGoalRoute } from "./native-goal.js";
|
|
20
22
|
const bundledRulesDir = fileURLToPath(new URL("../../rules/bundled-rules", import.meta.url));
|
|
21
23
|
/**
|
|
22
24
|
* `opencode2 run "lit fix it"` sends the message wrapped in double quotes (each multi-word argument is
|
|
@@ -63,6 +65,12 @@ export function createV2SessionHooks(options) {
|
|
|
63
65
|
}
|
|
64
66
|
return state;
|
|
65
67
|
}
|
|
68
|
+
/** Starts the native-goal step for a qualifying route; it never holds up or fails the prompt. */
|
|
69
|
+
function startNativeGoal(sessionID, route, work) {
|
|
70
|
+
if (route === undefined || options.nativeGoal === undefined)
|
|
71
|
+
return;
|
|
72
|
+
void options.nativeGoal.activate(sessionID, route, work);
|
|
73
|
+
}
|
|
66
74
|
async function lookup(sessionID) {
|
|
67
75
|
try {
|
|
68
76
|
const session = await options.getSession(sessionID);
|
|
@@ -88,6 +96,7 @@ export function createV2SessionHooks(options) {
|
|
|
88
96
|
}
|
|
89
97
|
event.metadata = { ...rest, litopencode: { command: accepted.command, banner: accepted.banner, source: "command" } };
|
|
90
98
|
options.onActivation?.(event.sessionID, accepted.command);
|
|
99
|
+
startNativeGoal(event.sessionID, nativeGoalRoute(accepted.command, ""), accepted.work ?? "");
|
|
91
100
|
return true;
|
|
92
101
|
}
|
|
93
102
|
async function prompt(event) {
|
|
@@ -143,9 +152,13 @@ export function createV2SessionHooks(options) {
|
|
|
143
152
|
options.onActivation?.(event.sessionID, "lit-adhd");
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
155
|
+
const alias = aliasGoalRoute(input);
|
|
156
|
+
if (alias !== undefined)
|
|
157
|
+
startNativeGoal(event.sessionID, alias.route, alias.work);
|
|
146
158
|
const mode = detectChatActivationMode(input, session.agent);
|
|
147
159
|
if (mode === undefined)
|
|
148
160
|
return;
|
|
161
|
+
startNativeGoal(event.sessionID, nativeGoalRoute(mode, input), input);
|
|
149
162
|
const trigger = mode === "lit-handoff" ? "handoff" : mode === "lit-scientific-visualization" ? mode : "lit";
|
|
150
163
|
let activation = promptForChatActivationMode(mode, input);
|
|
151
164
|
if (mode === "lit-adhd")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.36"><title>release: 1.0.36</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.36</text></g></svg>
|
package/docs/migration.md
CHANGED
|
@@ -29,7 +29,7 @@ This guide describes the supported migration shape for moving an OpenCode workfl
|
|
|
29
29
|
- Planning routes now produce proportionate objective-achievable checklists rather than relying on a later review pass to discover missing execution detail. Each retained item carries action/output/verification, while risky or multi-stage work receives SDD-like gates only when justified. `/review-work` accepts both draft plans (read-only `PASS`/`ITERATE`/`NEEDS-CONTEXT`) and completed work (the existing five-lane review); it never implements a draft plan.
|
|
30
30
|
- Keep `/start-work` as the native executable handoff to `lit-implement`. Chat-only start-work activation must begin with `start-work` or `lit start work`; diagnostic, copied, quoted, and blockquote mentions must not select start-work or append a false start-work event. An independent final `lit` remains an ordinary `lit-loop` activation.
|
|
31
31
|
- Schema-3 bounded-authority state is package-owned under `.litopencode/litgoal/lit-loop`. Existing activity ledgers remain readable; strict `/start-work init|resume|cancel|complete|status` directives create and advance the separate work snapshot and bounded journal. Resume is trusted-user-only and the generic `start-work` tool does not gain a resume action.
|
|
32
|
-
-
|
|
32
|
+
- Native goal binding exists only on OpenCode 2.2 and later (the `opencode.goal` RPC and the `/goal` command) and only for lit-loop, start-work and litwork; on OpenCode 1 and older OpenCode 2 hosts do not claim it, and keep LitOpenCode-owned `.litopencode/litgoal` state as the evidence record everywhere.
|
|
33
33
|
|
|
34
34
|
## Durable State
|
|
35
35
|
|
package/docs/reference-Ko-KR.md
CHANGED
|
@@ -53,6 +53,32 @@ npm exec --package @litfamily/litopencode@latest -- litopencode doctor
|
|
|
53
53
|
그대로 둡니다. 기본 OpenCode 설정 파일은 `~/.config/opencode/opencode.jsonc`입니다.
|
|
54
54
|
`XDG_CONFIG_HOME`이 설정되어 있으면 `$XDG_CONFIG_HOME/opencode/opencode.jsonc`를 사용합니다.
|
|
55
55
|
|
|
56
|
+
### 전역 AGENTS.md 블록
|
|
57
|
+
|
|
58
|
+
OpenCode의 Rules 문서는 `~/.config/opencode/AGENTS.md`를 전역 규칙 파일로 적고, OpenCode는 그 폴더를
|
|
59
|
+
`$XDG_CONFIG_HOME`에서 만듭니다(`OPENCODE_CONFIG_DIR`은 위치를 바꾸지 않으며, `opencode debug paths`로 확인할 수
|
|
60
|
+
있습니다). 설치 도구는 다른 모든 곳에서 쓰는 것과 같은 root의 `<root>/AGENTS.md`에 쓰므로 `--root`와
|
|
61
|
+
`XDG_CONFIG_HOME`이 그대로 따라갑니다. `<!-- litfamily:litopencode:start v=<version> -->`와
|
|
62
|
+
`<!-- litfamily:litopencode:end -->` 사이의 줄만 씁니다. 시작 표시에는 블록 앞에 덧붙인 내용도 기록해 두므로,
|
|
63
|
+
블록을 빼면 LF든 CRLF든 원래 바이트로 돌아갑니다. 쓰기는 임시 파일과 rename으로 하고, 두 번째 설치는 아무것도
|
|
64
|
+
바꾸지 않으며 새 백업도 만들지 않습니다.
|
|
65
|
+
|
|
66
|
+
| 명령 | 효과 |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `install` (대화형) | 한 번 묻고 기본값은 예입니다. 아니오를 고르면 `litopencode.json`에 `"agentFile": "off"`를 저장합니다. |
|
|
69
|
+
| `install --yes` | `agentFile`이 `off`가 아니면 블록을 넣거나 갱신합니다. |
|
|
70
|
+
| `install --no-agent-file` | 이번 실행에서만 파일을 그대로 두고 아무것도 저장하지 않습니다. |
|
|
71
|
+
| `install` (터미널 없음, `--yes` 없음) | 블록을 새로 넣지 않습니다. 이미 있는 이전 블록은 제자리에서 갱신합니다. |
|
|
72
|
+
| `agent-file on` / `off` | 설정을 저장하고 블록을 바로 넣거나 뺍니다. |
|
|
73
|
+
| `agent-file remove` | 블록을 빼고, 설치가 만든 파일이면 파일도 지웁니다. 저장된 설정은 그대로입니다. |
|
|
74
|
+
| `agent-file project [--dir <path>]` | 해당 폴더의 `AGENTS.md`에 프로젝트 블록을 쓰며 백업 복사본은 만들지 않습니다. |
|
|
75
|
+
| `doctor` | `agentFile.state`는 `present v=<x>`, `stale v=<x>`, `absent`, `off`, `malformed`, `symlink`, `not-file`, `not-utf8` 중 하나입니다. |
|
|
76
|
+
|
|
77
|
+
`LITOPENCODE_AGENT_FILE=on|off`는 저장된 설정보다 우선합니다. 표시 쌍이 깨졌거나, 심볼릭 링크이거나, 폴더이거나,
|
|
78
|
+
UTF-8이 아닌 파일은 `refused-*`로 알리고 그대로 둡니다. `<root>/AGENTS.md`가 없고 `~/.claude/CLAUDE.md`가 있으면
|
|
79
|
+
OpenCode는 자기 전역 파일이 없을 때만 그 Claude 파일을 읽으므로 아무것도 만들지 않습니다
|
|
80
|
+
(`skipped-claude-fallback`). `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1`이면 이 보호는 풀립니다.
|
|
81
|
+
|
|
56
82
|
*OpenCode의 이벤트가 플러그인으로 들어오면 TypeScript runtime이 route와 권한 경계를 적용하고, tool guard와 durable ledger로 결과를 남깁니다. 아래 흐름이 설치 후 실제로 연결되는 host surface입니다.*
|
|
57
83
|
|
|
58
84
|
```mermaid
|
|
@@ -263,6 +289,7 @@ flowchart LR
|
|
|
263
289
|
npm uninstall -g @litfamily/litopencode
|
|
264
290
|
```
|
|
265
291
|
|
|
292
|
+
설치가 전역 `AGENTS.md`에 LitFamily 블록을 넣었다면 그 전에 `litopencode agent-file remove`를 실행하세요.
|
|
266
293
|
그 다음 OpenCode가 관리하는 `opencode.jsonc`(또는 custom root의 `opencode.json`)의
|
|
267
294
|
`plugin` 배열에서 `@litfamily/litopencode` 또는 `@litfamily/litopencode@<version>` 항목을 제거합니다.
|
|
268
295
|
더 이상 route가 필요하지 않을 때만 `~/.config/opencode/litopencode.json`을 제거하세요.
|
package/docs/reference.md
CHANGED
|
@@ -54,6 +54,32 @@ routes remain user-owned. The default OpenCode config is
|
|
|
54
54
|
`~/.config/opencode/opencode.jsonc`, or `$XDG_CONFIG_HOME/opencode/opencode.jsonc`
|
|
55
55
|
when `XDG_CONFIG_HOME` is set.
|
|
56
56
|
|
|
57
|
+
### Global AGENTS.md block
|
|
58
|
+
|
|
59
|
+
OpenCode's Rules page names `~/.config/opencode/AGENTS.md` as the global rules file, and OpenCode builds that
|
|
60
|
+
folder from `$XDG_CONFIG_HOME` (`OPENCODE_CONFIG_DIR` does not move it; `opencode debug paths` prints it). The
|
|
61
|
+
installer writes to `<root>/AGENTS.md` for the same root it uses everywhere else, so `--root` and
|
|
62
|
+
`XDG_CONFIG_HOME` carry over. Only the lines between `<!-- litfamily:litopencode:start v=<version> -->` and
|
|
63
|
+
`<!-- litfamily:litopencode:end -->` are written. The start marker also records what was added in front of the
|
|
64
|
+
block, so removing it restores the original bytes under LF and CRLF alike. Writes use a temporary file and a
|
|
65
|
+
rename; a second install changes nothing and makes no new backup.
|
|
66
|
+
|
|
67
|
+
| Command | Effect |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `install` (interactive) | Asks once, default yes; No stores `"agentFile": "off"` in `litopencode.json`. |
|
|
70
|
+
| `install --yes` | Adds or refreshes the block, unless `agentFile` is `off`. |
|
|
71
|
+
| `install --no-agent-file` | Leaves the file alone for that run and stores nothing. |
|
|
72
|
+
| `install` (no terminal, no `--yes`) | Never adds a block; an older block is refreshed in place. |
|
|
73
|
+
| `agent-file on` / `off` | Stores the setting and adds or removes the block at once. |
|
|
74
|
+
| `agent-file remove` | Removes the block and, when install created the file, the file; the stored setting is untouched. |
|
|
75
|
+
| `agent-file project [--dir <path>]` | Writes the project block into that folder's `AGENTS.md`, with no backup copy. |
|
|
76
|
+
| `doctor` | `agentFile.state` is `present v=<x>`, `stale v=<x>`, `absent`, `off`, `malformed`, `symlink`, `not-file` or `not-utf8`. |
|
|
77
|
+
|
|
78
|
+
`LITOPENCODE_AGENT_FILE=on|off` overrides the stored setting. A malformed marker pair, a symlink, a folder or a
|
|
79
|
+
file that is not UTF-8 is reported as `refused-*` and left untouched. When `<root>/AGENTS.md` does not exist and
|
|
80
|
+
`~/.claude/CLAUDE.md` does, nothing is created (`skipped-claude-fallback`), because OpenCode reads that Claude file
|
|
81
|
+
only while its own global file is missing; `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1` lifts the guard.
|
|
82
|
+
|
|
57
83
|
*OpenCode provides the event surfaces; LitOpenCode turns them into bounded routes and guards, then records the durable ledger. This is the host connection behind the commands above.*
|
|
58
84
|
|
|
59
85
|
```mermaid
|
|
@@ -229,8 +255,11 @@ folder would cover it. Paths in code fences and commands quoted in an inline cod
|
|
|
229
255
|
When typed start-work begins, each valid `Scope roots:` line becomes a grant of the run, so it does
|
|
230
256
|
not pause for a folder the approved plan named; the folder must exist then.
|
|
231
257
|
|
|
232
|
-
Native goal verdict:
|
|
233
|
-
|
|
258
|
+
Native goal verdict: OpenCode 2.2 and later has a native `/goal`, and LitOpenCode sets, resumes and
|
|
259
|
+
clears one only for lit-loop, start-work and litwork, only for goals it created; a goal you made is
|
|
260
|
+
reported and left alone. OpenCode 1 and older OpenCode 2 hosts record `native-goal-unavailable`.
|
|
261
|
+
The ledger under `.litopencode/litgoal` is the evidence record on every host, and done needs both
|
|
262
|
+
OpenCode's verdict and the ledger. Static skills are installed under
|
|
234
263
|
`skills/*/SKILL.md` and remain documentation unless a documented OpenCode route selects them.
|
|
235
264
|
|
|
236
265
|
## Skill learning state
|
|
@@ -370,7 +399,8 @@ There is no `uninstall` subcommand. For a global install, remove the npm binary
|
|
|
370
399
|
npm uninstall -g @litfamily/litopencode
|
|
371
400
|
```
|
|
372
401
|
|
|
373
|
-
|
|
402
|
+
Run `litopencode agent-file remove` before that if install added the LitFamily block to the global
|
|
403
|
+
`AGENTS.md`. Then remove the `@litfamily/litopencode` or `@litfamily/litopencode@<version>` entry from the `plugin` array in
|
|
374
404
|
OpenCode's managed `opencode.jsonc` (or the custom root's `opencode.json`). Remove
|
|
375
405
|
`~/.config/opencode/litopencode.json` only when you no longer need its user-owned routes.
|
|
376
406
|
Project `.litopencode/` state is separate; preserve it for later resume or remove it only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@litfamily/litopencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "Workflow plugin for OpenCode: add lit to a prompt and the work is planned, built, checked and written down so the next session can pick it up.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@opencode-ai/plugin": "^1.17.6",
|
|
77
|
-
"@opencode/plugin": "2.
|
|
77
|
+
"@opencode/plugin": "2.2.5",
|
|
78
78
|
"@types/node": "^24.12.2",
|
|
79
79
|
"picomatch": "^4.0.5",
|
|
80
80
|
"typescript": "^5.9.3"
|
package/skills/lit-adhd/SKILL.md
CHANGED
|
@@ -57,7 +57,7 @@ Use the OpenCode vocabulary for this contract: `chat.message`, `command.execute.
|
|
|
57
57
|
| `session_id` | The id of the session that typed the request. The route prompt states it; pass it as `--session`. Without it the brief still prints but no `go` step is armed. |
|
|
58
58
|
| `scope` | This worktree and the other worktrees of the same git repository. `--root <dir>` points elsewhere; `--all-dirs` is not used unless the user asks. |
|
|
59
59
|
| `store` | The OpenCode session database (`opencode.db`), opened read-only, else the older JSON session store, else nothing. A missing store is said in words. |
|
|
60
|
-
| `local_state` | `plans/*.md` and `.litopencode/plans/*.md` checklists, the active goal, the bounded start-work run, `.litopencode/workflow-stack/<session>.json`, `.litopencode/liveness/<session>.json`, `HANDOFF*.md` headings and the first item under their `Next` heading, and `git log`. These small records are read before the session store, so a large store cannot use up the read budget first. |
|
|
60
|
+
| `local_state` | `plans/*.md` and `.litopencode/plans/*.md` checklists, the active goal, the bounded start-work run, `.litopencode/workflow-stack/<session>.json`, `.litopencode/liveness/<session>.json`, `HANDOFF*.md` headings and the first open item under their `Next` heading (newest file first; an item marked ✅, ✓, `[x]`, `~~struck~~`, `DONE` or `SHIPPED` is skipped), and `git log`. Under `HERDR_ENV=1` it also runs `herdr agent list` once (3 s timeout; a failure reads `unknown` and counts nothing). These small records are read before the session store, so a large store cannot use up the read budget first. |
|
|
61
61
|
|
|
62
62
|
## #contract.mode_matrix
|
|
63
63
|
|
|
@@ -169,14 +169,14 @@ The collector draws `[▉▉▉░░░░░░░] 3/10` only when both numbe
|
|
|
169
169
|
|
|
170
170
|
### The recommended step
|
|
171
171
|
|
|
172
|
-
One step, chosen in this order: something waiting on the user, a blocked session,
|
|
172
|
+
One step, chosen in this order: something waiting on the user, a blocked session, another session's paused discipline, lanes at work (other running sessions of this worktree family, and under `HERDR_ENV=1` herdr agents working or blocked in this herdr workspace or inside the repo, never this pane; a lane seen both ways counts once), this session's own paused discipline, this session's running start-work run, another session's running start-work run (followed from that session), the single open plan, the first item under this worktree's `HANDOFF.md` `Next` heading (it never runs on `go`), the newest unfinished session, an open plan in another worktree, the most recent session that stopped. The box always names a real step from the records; only an empty window asks what to start. If the step belongs to this session and is one of continue, keep working, or start the plan, the collector writes the next-step record and `go` follows it. If it belongs to another session or worktree, the box says where to act and `go` here only repeats that pointer; it never starts the step. Lanes are counted before anything that resumes work here: while they work, this session's own paused discipline and its own run are held back, and an approval stays the one step offered. The box says to wait for them (or to answer the one waiting on the user), nothing is armed, and the closing line is ``Do not type `go` until they finish.``
|
|
173
173
|
|
|
174
174
|
## Following `go`
|
|
175
175
|
|
|
176
176
|
`go` is live only while the next-step record for this session is younger than 30 minutes. It is read once and removed.
|
|
177
177
|
|
|
178
178
|
- A paused discipline: the same return as `lit continue`, at the same stage and count.
|
|
179
|
-
- The running start-work run: a note to keep going from the checked plan; nothing restarts.
|
|
179
|
+
- The running start-work run: a note to keep going from the checked plan; nothing restarts. `go` first re-checks that the run is still active and still this session's; if not, the reply says the step no longer holds and starts nothing.
|
|
180
180
|
- A single open plan: the typed start-work path, which opens a bounded run with read, write and execute under this workspace and nothing more.
|
|
181
181
|
|
|
182
182
|
A pending boundary is never answered by `go`. The user types `lit approve` to grant exactly that boundary, or `lit deny`. In LitOpenCode `lit deny` ends the run, because the run lifecycle has no refused-and-still-paused state; the reply says so and says how to start it again (`start-work`, then answer the boundary differently). Forbidden actions (commit, push, publish, release, version-bump, host-config, destructive) can never become a pending boundary, so `lit approve` cannot grant them.
|
|
@@ -17,7 +17,7 @@ The brief is Markdown, so OpenCode's chat draws it with the theme's colours.
|
|
|
17
17
|
3. Ended sessions (done, idle) under `### ⚪ Ended · <n>` as one table, newest first, at most eight rows and an italic count of the rest. Columns: id, where (cut to 20 cells), goal (cut to 44 cells), ago (the largest unit only: `5d`, `14h`, `20m`), done (`finished`, a file count, or `·`).
|
|
18
18
|
4. `### 🗺️ Roadmap`: per worktree with something to show, up to three recent commits with their month-day (`✓`), the plan step in hand (`▶ **now**`), up to two steps after it (`○ **later**`); another worktree's lines carry its name in inline code.
|
|
19
19
|
5. The box, `Recommended next step`, in a code block with no language tag, so its frame keeps its columns and the chat draws it as plain text: the step wrapped at word breaks to at most three lines, then `type: go` when a step is armed.
|
|
20
|
-
6. The closing line: ``Type **`go`** to do it.`` when a step is armed; otherwise `Nothing runs on its own here: open that session, or say what to do.`
|
|
20
|
+
6. The closing line: ``Type **`go`** to do it.`` when a step is armed; ``Do not type `go` until they finish.`` while lanes work; otherwise `Nothing runs on its own here: open that session, or say what to do.`
|
|
21
21
|
7. When the read budget ran out before every plan was read, an italic line: `<n> plans were not read (read budget used up).` Plans and the other small records are read before the session store, so this happens only with very large plan folders.
|
|
22
22
|
|
|
23
23
|
## Plans the roadmap leaves out
|
|
@@ -31,4 +31,4 @@ A `done` card never names a next step: what it left open is history. An `alive`
|
|
|
31
31
|
|
|
32
32
|
## Recommended-step precedence
|
|
33
33
|
|
|
34
|
-
Waiting, blocked, paused,
|
|
34
|
+
Waiting, blocked, another session's paused discipline, lanes at work (other working sessions plus herdr agents under `HERDR_ENV=1`, counted once each; no record), this session's own paused discipline, the running start-work run (this session only), another session's running run (a pointer there), the single open plan, this worktree's newest `HANDOFF*.md` first open `Next` item, the newest idle session with a known next step. The next-step record is written only for `continue`, `work` and `plan`, and only when the step belongs to the asking session; a `HANDOFF.md` item writes no record at all.
|