@skillstate/opencode 2.0.4 → 2.0.6

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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@skillstate/opencode)](https://www.npmjs.com/package/@skillstate/opencode)
8
8
  [![node](https://img.shields.io/node/v/@skillstate/opencode)](https://www.npmjs.com/package/@skillstate/opencode)
9
- [![Tests](https://img.shields.io/badge/tests-755%20passing-brightgreen)](https://github.com/vitkuz573/skillstate)
9
+ [![Tests](https://img.shields.io/badge/tests-873%20passing-brightgreen)](https://github.com/vitkuz573/skillstate)
10
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/vitkuz573/skillstate/blob/main/LICENSE)
11
11
 
12
12
  </div>
@@ -43,19 +43,20 @@ const adapter = new OpenCodeAdapter();
43
43
  // execution_context block pointing at the persisted state file:
44
44
  const skillMd = adapter.generateSkillMd(INTERCODE_CTF_SPEC, './.skillstate.json');
45
45
 
46
- // Plugin with real O(1) history trimming via experimental.chat.messages.transform:
47
- const plugin = adapter.generatePluginCode('./.skillstate.json');
46
+ // Plugin with real O(1) history trimming via experimental.chat.messages.transform.
47
+ // The state path is resolved per session from the host cwd inside the plugin —
48
+ // no baked path:
49
+ const plugin = adapter.generatePluginCode();
48
50
 
49
51
  // Default keeps the last 3 non-system messages + state injection.
50
52
  // Configure history depth:
51
- const plugin2 = adapter.generatePluginCode('./.skillstate.json', {
53
+ const plugin2 = adapter.generatePluginCode({
52
54
  maxHistoryMessages: 5, // keep last 5 non-system messages
53
55
  });
54
56
 
55
57
  // Persist the plugin to disk atomically:
56
58
  const saved = await adapter.savePluginCode(
57
59
  './skillstate.plugin.ts',
58
- './.skillstate.json',
59
60
  { maxHistoryMessages: 5 },
60
61
  );
61
62
  ```
@@ -73,16 +74,17 @@ import { OpenCodeAdapter } from '@skillstate/opencode';
73
74
  import { writeFileSync } from 'node:fs';
74
75
 
75
76
  const adapter = new OpenCodeAdapter();
76
- const statePath = '/abs/path/to/.skillstate.json';
77
- const code = adapter.generatePluginCode(statePath, { maxHistoryMessages: 3 });
77
+ const code = adapter.generatePluginCode({ maxHistoryMessages: 3 });
78
78
  writeFileSync('/abs/path/to/skillstate.plugin.ts', code);
79
79
  ```
80
80
 
81
81
  Put the generated `skillstate.plugin.ts` anywhere stable (absolute path is
82
- safest), e.g. `<project>/.opencode-runtime/skillstate.plugin.ts`.
82
+ safest), e.g. `<project>/.opencode-runtime/skillstate.plugin.ts`. The plugin
83
+ must be able to resolve `@skillstate/opencode` at load time (install it in
84
+ the project or globally) — hook logic lives in that package.
83
85
 
84
- **2. Create the initial state file** (`statePath` from step 1), matching your
85
- spec schema, e.g. for `INTERCODE_CTF_SPEC`:
86
+ **2. Create the initial state file** (`<project>/.skillstate/skillstate.json`),
87
+ matching your spec schema, e.g. for `INTERCODE_CTF_SPEC`:
86
88
 
87
89
  ```json
88
90
  {
@@ -126,39 +128,43 @@ Root path `@skillstate/opencode` exports the adapter and the static plugin:
126
128
  - `new OpenCodeAdapter()` — implements `PlatformAdapter` (`name = 'opencode'`).
127
129
  - `generateSkillMd(spec, statePath?): string` — a `SKILL.md` body with
128
130
  frontmatter and a state-based process description.
129
- - `generatePluginCode(statePath, options?): string` — a thin plugin loader by
130
- default (`import { createSkillStatePlugin } from '@skillstate/opencode'` with
131
- the state path baked in; `options.maxHistoryMessages`, default 3).
132
- `options.standalone: true` inlines the full self-contained plugin — escape
133
- hatch for environments without npm resolution of this package. Hooks:
131
+ - `generatePluginCode(options?): string` — a thin plugin loader
132
+ (`import { createSkillStatePlugin } from '@skillstate/opencode'`;
133
+ `options.maxHistoryMessages`, default 3). State resolution is always
134
+ per-project and lives inside the static plugin. Hooks:
134
135
  `experimental.chat.messages.transform` (real history trimming),
135
136
  `experimental.session.compacting` (inject state into compaction context),
136
137
  `tool.execute.after` (persist `state_patch` to disk).
137
- - `createSkillStatePlugin({ statePath, maxHistoryMessages? })` — the static
138
- plugin factory (single source of truth for the hook logic); re-exported from
139
- `@skillstate/opencode/plugin` as well.
138
+ - `createSkillStatePlugin({ maxHistoryMessages? })` — the static plugin
139
+ factory (single source of truth for the hook logic); state is resolved from
140
+ the session cwd on every hook call via
141
+ `resolveStatePathForCwd(process.cwd(), os.homedir())` —
142
+ `<cwd>/.skillstate/skillstate.json`, or the global bucket
143
+ `<home>/.skillstate/global/skillstate.json` when the session runs from
144
+ `$HOME`. Re-exported from `@skillstate/opencode/plugin` as well.
145
+ - `resolveStatePathForCwd(cwd, home): string` — the per-project state path
146
+ resolution (pure path arithmetic, no filesystem access).
140
147
  - `readSkillState` / `saveSkillState` / `mergePatch` / `extractPatch` — the
141
148
  plugin's state helpers, shared by the static plugin.
142
- - `savePluginCode(target, statePath, options?): Promise<string>` — writes the
143
- plugin atomically and returns the destination.
149
+ - `savePluginCode(target, options?): Promise<string>` — writes the plugin
150
+ atomically and returns the destination. `target` accepts a raw path or a
151
+ `{ root, name }` ref confined by `resolveStatePath` (`..` escapes throw).
144
152
  - `injectState(state, spec): string` / `formatPrompt(state, observation, spec): string`.
145
153
  - `extractPatch(response): StatePatch | null` / `extractAction(response): string | null`.
146
154
 
147
- Both `generatePluginCode`/`savePluginCode` accept a raw path (legacy) or a
148
- `{ root, name }` ref confined by `resolveStatePath` (`..` escapes throw).
149
-
150
155
  ## Notes
151
156
 
152
- - **Real O(1).** Unlike Claude Code and Codex, OpenCode exposes
157
+ - **Real O(1).** Unlike Claude Code (append-only hooks) and Codex
158
+ (hooks + experimental app-server fork-trim), OpenCode exposes
153
159
  `experimental.chat.messages.transform`, so the plugin drops old messages
154
160
  instead of just hiding them — only the last N non-system messages plus an
155
161
  injected state message reach the LLM.
156
- - The default generated plugin is a **thin loader**: it imports
157
- `createSkillStatePlugin` from `@skillstate/opencode` (one source of truth)
158
- and bakes in the state path. The `{ standalone: true }` template is a
159
- self-contained ESM/TS escape hatch for environments without npm resolution;
160
- hook logic lives only in `src/plugin.ts` — regenerate rather than editing
161
- generated files.
162
+ - The generated plugin is a **thin loader**: it imports
163
+ `createSkillStatePlugin` from `@skillstate/opencode` (one source of truth).
164
+ Hook logic lives only in `src/plugin.ts` — regenerate rather than editing
165
+ generated files. State resolves per session:
166
+ `<cwd>/.skillstate/skillstate.json` (global bucket from `~` when the
167
+ session cwd is `$HOME`).
162
168
  - Depends on [`@skillstate/core`](../core) for `PromptTransformer`,
163
169
  `atomicWriteFile`, and `resolveStatePath`.
164
170
 
@@ -18,11 +18,6 @@ import type { StatePathRef } from '@skillstate/core';
18
18
  export interface GeneratePluginOptions {
19
19
  /** Non-system messages kept by the plugin (default 3). */
20
20
  maxHistoryMessages?: number;
21
- /**
22
- * Inline the full plugin instead of the thin loader — escape hatch for
23
- * environments without npm resolution of `@skillstate/opencode`.
24
- */
25
- standalone?: boolean;
26
21
  }
27
22
  /**
28
23
  * OpenCode platform adapter (@non-paper; see module doc).
@@ -63,14 +58,12 @@ export declare class OpenCodeAdapter implements PlatformAdapter {
63
58
  /**
64
59
  * Generate an OpenCode plugin file.
65
60
  *
66
- * Default (thin) mode: a loader that imports the STATIC plugin
67
- * (`createSkillStatePlugin` from `@skillstate/opencode`) — the single
68
- * source of truth for hook logic. `statePath`/`maxHistoryMessages` are
69
- * baked into the loader call.
70
- *
71
- * `{ standalone: true }` inlines the full plugin (self-contained template,
72
- * escape hatch for environments without npm resolution of
73
- * `@skillstate/opencode`).
61
+ * The plugin is ALWAYS a thin loader in per-project resolver mode: the
62
+ * state path is computed from the session cwd on every hook call inside
63
+ * the static plugin (`resolveStatePathForCwd(process.cwd(),
64
+ * os.homedir())`), so each project gets its own
65
+ * `<cwd>/.skillstate/skillstate.json` and a session opened anywhere
66
+ * resolves the same state for the same project.
74
67
  *
75
68
  * Hooks (see `src/plugin.ts`): `experimental.chat.messages.transform`
76
69
  * (real O(1) history trimming, `{ info, parts }` entries, in-place
@@ -78,21 +71,14 @@ export declare class OpenCodeAdapter implements PlatformAdapter {
78
71
  * context), `tool.execute.after` (persist `state_patch` from
79
72
  * `output.output`).
80
73
  */
81
- generatePluginCode(statePath: string, options?: GeneratePluginOptions): string;
82
- /**
83
- * @non-paper additive overload: accept a `{ root, name }` ref confined
84
- * via `resolveStatePath` — `..` escapes throw instead of embedding an
85
- * unsafe path into the generated plugin.
86
- */
87
- generatePluginCode(stateRef: StatePathRef, options?: GeneratePluginOptions): string;
74
+ generatePluginCode(options?: GeneratePluginOptions): string;
88
75
  /**
89
76
  * @non-paper additive helper: generate the plugin and persist it via
90
- * `atomicWriteFile` (tmp + fsync + rename). Both the destination and the
91
- * embedded state path accept raw strings (legacy behavior) or
92
- * `{ root, name }` refs confined by `resolveStatePath`. Returns the
93
- * absolute destination path.
77
+ * `atomicWriteFile` (tmp + fsync + rename). The destination accepts a raw
78
+ * string or a `{ root, name }` ref confined by `resolveStatePath`.
79
+ * Returns the absolute destination path.
94
80
  */
95
- savePluginCode(target: string | StatePathRef, statePath: string | StatePathRef, options?: GeneratePluginOptions): Promise<string>;
81
+ savePluginCode(target: string | StatePathRef, options?: GeneratePluginOptions): Promise<string>;
96
82
  /**
97
83
  * Describe the schema fields for inclusion in prompts.
98
84
  */
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-adapter.d.ts","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,eAAe;IACrD,QAAQ,CAAC,IAAI,cAAc;IAE3B,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAsB3D;IAED;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEhD;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7C;IAED;;;;;OAKG;IACH,YAAY,CACV,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,cAAc,GACnB,MAAM,CAER;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CA0ChE;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IAC/E;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IA4BpF;;;;;;OAMG;IACG,cAAc,CAClB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAYjB;IAMD;;OAEG;IACH,OAAO,CAAC,cAAc;CASvB"}
1
+ {"version":3,"file":"opencode-adapter.d.ts","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,eAAe;IACrD,QAAQ,CAAC,IAAI,cAAc;IAE3B,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAsB3D;IAED;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEhD;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7C;IAED;;;;;OAKG;IACH,YAAY,CACV,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,cAAc,GACnB,MAAM,CAER;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CA0ChE;IAED;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAa1D;IAED;;;;;OAKG;IACG,cAAc,CAClB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAOjB;IAMD;;OAEG;IACH,OAAO,CAAC,cAAc;CASvB"}
@@ -104,42 +104,47 @@ plugin; only the current state and the latest observation matter.
104
104
  - Reasoning is discarded after execution — put anything you need to persist
105
105
  into \`state_patch\`.`;
106
106
  }
107
- generatePluginCode(statePathOrRef, options) {
108
- const statePath = typeof statePathOrRef === 'string'
109
- ? statePathOrRef
110
- : resolveStatePath(statePathOrRef.root, statePathOrRef.name);
107
+ /**
108
+ * Generate an OpenCode plugin file.
109
+ *
110
+ * The plugin is ALWAYS a thin loader in per-project resolver mode: the
111
+ * state path is computed from the session cwd on every hook call inside
112
+ * the static plugin (`resolveStatePathForCwd(process.cwd(),
113
+ * os.homedir())`), so each project gets its own
114
+ * `<cwd>/.skillstate/skillstate.json` and a session opened anywhere
115
+ * resolves the same state for the same project.
116
+ *
117
+ * Hooks (see `src/plugin.ts`): `experimental.chat.messages.transform`
118
+ * (real O(1) history trimming, `{ info, parts }` entries, in-place
119
+ * mutation), `experimental.session.compacting` (state into compaction
120
+ * context), `tool.execute.after` (persist `state_patch` from
121
+ * `output.output`).
122
+ */
123
+ generatePluginCode(options) {
111
124
  const maxHistory = options?.maxHistoryMessages ?? 3;
112
- if (options?.standalone) {
113
- return standalonePluginCode(statePath, maxHistory);
114
- }
115
- const sp = JSON.stringify(statePath);
116
125
  return `// OpenCode plugin (thin loader) generated by skillstate.
117
126
  // Hook logic lives in the static plugin — the single source of truth —
118
- // shipped in the @skillstate/opencode package.
127
+ // shipped in the @skillstate/opencode package. PER-PROJECT state is
128
+ // resolved from the session cwd on every hook call —
129
+ // <cwd>/.skillstate/skillstate.json (global bucket when cwd === home).
119
130
  import { createSkillStatePlugin } from '@skillstate/opencode';
120
131
 
121
132
  export default createSkillStatePlugin({
122
- statePath: ${sp},
123
133
  maxHistoryMessages: ${maxHistory},
124
134
  });
125
135
  `;
126
136
  }
127
137
  /**
128
138
  * @non-paper additive helper: generate the plugin and persist it via
129
- * `atomicWriteFile` (tmp + fsync + rename). Both the destination and the
130
- * embedded state path accept raw strings (legacy behavior) or
131
- * `{ root, name }` refs confined by `resolveStatePath`. Returns the
132
- * absolute destination path.
139
+ * `atomicWriteFile` (tmp + fsync + rename). The destination accepts a raw
140
+ * string or a `{ root, name }` ref confined by `resolveStatePath`.
141
+ * Returns the absolute destination path.
133
142
  */
134
- async savePluginCode(target, statePath, options) {
143
+ async savePluginCode(target, options) {
135
144
  const dest = typeof target === 'string'
136
145
  ? target
137
146
  : resolveStatePath(target.root, target.name);
138
- const resolvedState = typeof statePath === 'string'
139
- ? statePath
140
- : resolveStatePath(statePath.root, statePath.name);
141
- const plugin = this.generatePluginCode(resolvedState, options);
142
- await atomicWriteFile(dest, plugin);
147
+ await atomicWriteFile(dest, this.generatePluginCode(options));
143
148
  return dest;
144
149
  }
145
150
  /* ------------------------------------------------------------------ */
@@ -155,147 +160,4 @@ export default createSkillStatePlugin({
155
160
  return `## Schema\n${fields}`;
156
161
  }
157
162
  }
158
- /**
159
- * Standalone (escape-hatch) plugin template — a self-contained inline copy of
160
- * `src/plugin.ts` for environments without npm resolution of
161
- * `@skillstate/opencode`. Escape hatch only: `src/plugin.ts` remains the
162
- * single source of truth; regenerate rather than editing generated files.
163
- */
164
- function standalonePluginCode(statePath, maxHistory) {
165
- const sp = JSON.stringify(statePath);
166
- return `// OpenCode plugin (standalone escape hatch) generated by skillstate.
167
- // Self-contained copy of createSkillStatePlugin from @skillstate/opencode.
168
- // Real O(1) prompt economy via experimental.chat.messages.transform:
169
- // trims history to last ${maxHistory} non-system messages + injected state.
170
-
171
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
172
- import type { Plugin } from "@opencode-ai/plugin";
173
-
174
- const STATE_PATH = ${sp};
175
- const MAX_HISTORY = ${maxHistory};
176
-
177
- function readSkillState(): Record<string, unknown> {
178
- try {
179
- if (existsSync(STATE_PATH)) {
180
- return JSON.parse(readFileSync(STATE_PATH, "utf-8"));
181
- }
182
- } catch {
183
- // Corrupt or unreadable state file — fall back to empty state.
184
- }
185
- return {};
186
- }
187
-
188
- function saveSkillState(state: Record<string, unknown>): void {
189
- try {
190
- writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
191
- } catch {
192
- // Best-effort: read-only environments or permission issues.
193
- }
194
- }
195
-
196
- function isPlainObject(value: unknown): value is Record<string, unknown> {
197
- return typeof value === "object" && value !== null && !Array.isArray(value);
198
- }
199
-
200
- function mergePatch(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
201
- const result = { ...base };
202
- for (const key of Object.keys(patch)) {
203
- const value = patch[key];
204
- if (value === null) {
205
- delete result[key];
206
- } else if (isPlainObject(value) && isPlainObject(result[key])) {
207
- result[key] = mergePatch(result[key] as Record<string, unknown>, value);
208
- } else {
209
- result[key] = value;
210
- }
211
- }
212
- return result;
213
- }
214
-
215
- function extractPatch(response: string): Record<string, unknown> | null {
216
- const match = response.match(/\`\`\`json\\s*\\n?([\\s\\S]*?)\\n?\\s*\`\`\`/);
217
- if (!match) return null;
218
- try {
219
- const parsed: unknown = JSON.parse(match[1]);
220
- if (isPlainObject(parsed) && isPlainObject(parsed["state_patch"])) {
221
- return parsed["state_patch"];
222
- }
223
- } catch {
224
- // Malformed JSON — ignore.
225
- }
226
- return null;
227
- }
228
-
229
- const SkillStatePlugin: Plugin = async () => {
230
- return {
231
- // ── O(1) history trimming ──────────────────────────────────────────
232
- // Filters messages BEFORE each LLM call: keeps all system messages
233
- // plus the last ${maxHistory} non-system messages, then injects a
234
- // synthetic state element. Old messages are DROPPED from the prompt,
235
- // not just hidden.
236
- "experimental.chat.messages.transform": async (_input, output) => {
237
- const state = readSkillState();
238
- const messages = output.messages;
239
- const systemMessages = messages.filter((m) => m.info.role === "system");
240
- const trimmed = messages
241
- .filter((m) => m.info.role !== "system")
242
- .slice(-MAX_HISTORY);
243
-
244
- const stateMessage = {
245
- info: {
246
- id: "skillstate-state-inject",
247
- sessionID: "skillstate",
248
- role: "user",
249
- time: { created: 0 },
250
- agent: "skillstate",
251
- model: { providerID: "skillstate", modelID: "skillstate" },
252
- },
253
- parts: [
254
- {
255
- id: "skillstate-state-inject-text",
256
- sessionID: "skillstate",
257
- messageID: "skillstate-state-inject",
258
- type: "text",
259
- synthetic: true,
260
- text: "Current skill state (JSON): " + JSON.stringify(state),
261
- },
262
- ],
263
- };
264
-
265
- // The pipeline holds the original array reference — mutate in place.
266
- const kept = [...systemMessages, ...trimmed, stateMessage];
267
- messages.length = 0;
268
- messages.push(...kept);
269
- },
270
-
271
- // ── Compaction context injection ───────────────────────────────────
272
- // Before compaction, inject the current state into the context so the
273
- // compaction summary preserves state even after history is compressed.
274
- "experimental.session.compacting": async (_input, output) => {
275
- const state = readSkillState();
276
- const stateContext = "Skillstate: " + JSON.stringify(state);
277
- if (!Array.isArray(output.context)) {
278
- output.context = [];
279
- }
280
- output.context.push(stateContext);
281
- },
282
-
283
- // ── State persistence from LLM responses ───────────────────────────
284
- // After tool execution, extract state_patch from the tool response
285
- // (output.output), merge it, and save to disk.
286
- "tool.execute.after": async (_input, output) => {
287
- const response = output.output ?? "";
288
- if (typeof response === "string") {
289
- const patch = extractPatch(response);
290
- if (patch) {
291
- saveSkillState(STATE_PATH, mergePatch(readSkillState(), patch));
292
- }
293
- }
294
- },
295
- };
296
- };
297
-
298
- export default SkillStatePlugin;
299
- `;
300
- }
301
163
  //# sourceMappingURL=opencode-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-adapter.js","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opencode-adapter.js","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAS1B;;GAEG;AACH,MAAM,OAAO,eAAe;IACjB,IAAI,GAAG,UAAU,CAAC;IAEnB,WAAW,GAAG,IAAI,iBAAiB,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtE;;;;OAIG;IACH,WAAW,CAAC,KAAiB,EAAE,IAAoB;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEpD,OAAO,gBAAgB,IAAI,CAAC,EAAE;gBAClB,IAAI,CAAC,YAAY;EAC/B,UAAU;;EAEV,SAAS;;;;;;;;;;;;;kIAauH,CAAC;IACjI,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,QAAgB;QAC3B,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAgB;QAC5B,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CACV,KAAiB,EACjB,WAAwB,EACxB,IAAoB;QAEpB,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAoB,EAAE,SAAkB;QACtD,MAAM,iBAAiB,GAAG,SAAS,IAAI,oBAAoB,CAAC;QAE5D,OAAO;QACH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;eAClB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC;WACrC,IAAI,CAAC,OAAO;;gBAEP,iBAAiB;;;;IAI7B,IAAI,CAAC,IAAI;;EAEX,IAAI,CAAC,YAAY;;;;yCAIsB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;wBAuBlC,CAAC;IACvB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,OAA+B;QAChD,MAAM,UAAU,GAAG,OAAO,EAAE,kBAAkB,IAAI,CAAC,CAAC;QACpD,OAAO;;;;;;;;wBAQa,UAAU;;CAEjC,CAAC;IACA,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAClB,MAA6B,EAC7B,OAA+B;QAE/B,MAAM,IAAI,GACR,OAAO,MAAM,KAAK,QAAQ;YACxB,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IAExE;;OAEG;IACK,cAAc,CAAC,MAAgC;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aAClC,GAAG,CACF,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAChB,KAAK,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,WAAW,IAAI,gBAAgB,EAAE,CACxE;aACA,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,cAAc,MAAM,EAAE,CAAC;IAChC,CAAC;CACF"}
package/dist/plugin.d.ts CHANGED
@@ -2,11 +2,27 @@ import type { SkillStatePlugin } from './plugin-types.js';
2
2
  export * from './plugin-types.js';
3
3
  /** Options for {@link createSkillStatePlugin}. */
4
4
  export interface SkillStatePluginOptions {
5
- /** Absolute path of the persisted state file. */
6
- statePath: string;
7
5
  /** Non-system messages kept in the prompt (default 3). */
8
6
  maxHistoryMessages?: number;
9
7
  }
8
+ /**
9
+ * Resolve the per-project state file for a session working directory
10
+ * (`cwd` of the current opencode session):
11
+ *
12
+ * - `cwd === home` — a session launched straight from `$HOME` has no single
13
+ * project, so state goes to the global bucket
14
+ * `<home>/.skillstate/global/skillstate.json`;
15
+ * - any other cwd (including subdirectories of `$HOME`) — the state lives in
16
+ * the project: `<cwd>/.skillstate/skillstate.json`.
17
+ *
18
+ * Pure path arithmetic: both arguments are normalized via `path.resolve`
19
+ * before comparison, and there is NO filesystem access. The same project
20
+ * directory therefore always maps to the same state file no matter where
21
+ * the host was launched from, while different projects never share state.
22
+ * Zero-dep by design (node builtins only) so generated plugins and the MCP
23
+ * server can inline the same semantics. Keep any copy in sync.
24
+ */
25
+ export declare function resolveStatePathForCwd(cwd: string, home: string): string;
10
26
  /**
11
27
  * Read the state file. Missing or corrupt files yield `{}` (best-effort).
12
28
  * The on-disk envelope is `{ version: 1, state }` (migrations-compatible);
@@ -15,8 +31,9 @@ export interface SkillStatePluginOptions {
15
31
  export declare function readSkillState(statePath: string): Record<string, unknown>;
16
32
  /**
17
33
  * Persist the state file (best-effort: read-only environments are ignored).
18
- * Writes the `{ version: 1, state }` envelope so `migrate()`/runtime resume
19
- * read the same file.
34
+ * Creates the parent directory when missing (the per-project resolver may
35
+ * target a fresh `<cwd>/.skillstate/`). Writes the `{ version: 1, state }`
36
+ * envelope so `migrate()`/runtime resume read the same file.
20
37
  */
21
38
  export declare function saveSkillState(statePath: string, state: Record<string, unknown>): void;
22
39
  /**
@@ -33,6 +50,12 @@ export declare function extractPatch(response: string): Record<string, unknown>
33
50
  /**
34
51
  * Build the OpenCode plugin function with the same behavior for every host
35
52
  * entry point (thin generated loaders, direct imports).
53
+ *
54
+ * State resolution is ALWAYS per-project: the state file path is computed
55
+ * from the session cwd on EVERY hook call via
56
+ * `resolveStatePathForCwd(process.cwd(), os.homedir())` — each project gets
57
+ * its own `<cwd>/.skillstate/skillstate.json`, and a session launched from
58
+ * `$HOME` uses the global bucket.
36
59
  */
37
- export declare function createSkillStatePlugin(options: SkillStatePluginOptions): SkillStatePlugin;
60
+ export declare function createSkillStatePlugin(options?: SkillStatePluginOptions): SkillStatePlugin;
38
61
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,mBAAmB,CAAC;AAElC,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAMtF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAY7E;AAKD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CA+EzF"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,mBAAmB,CAAC;AAElC,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxE;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAOtF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAY7E;AAKD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAgF9F"}
package/dist/plugin.js CHANGED
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Static OpenCode plugin — the SINGLE SOURCE OF TRUTH for the skillstate
3
- * host integration. `OpenCodeAdapter.generatePluginCode` (thin mode, the
4
- * default) emits a loader that imports `createSkillStatePlugin` from this
5
- * module; the standalone template is only an escape hatch for environments
6
- * without npm resolution of `@skillstate/opencode`.
3
+ * host integration. `OpenCodeAdapter.generatePluginCode` emits a thin loader
4
+ * that imports `createSkillStatePlugin` from this module; the per-project
5
+ * state resolution lives inside the plugin itself.
7
6
  *
8
7
  * Hooks (opencode 1.17 contract, verified on host):
9
8
  * - `experimental.chat.messages.transform` — entries are `{ info, parts }`
@@ -16,7 +15,34 @@
16
15
  * ```json `state_patch` block is merged (paper ⊕: null deletes) and saved.
17
16
  */
18
17
  import * as fs from 'node:fs';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
19
20
  export * from './plugin-types.js';
21
+ /**
22
+ * Resolve the per-project state file for a session working directory
23
+ * (`cwd` of the current opencode session):
24
+ *
25
+ * - `cwd === home` — a session launched straight from `$HOME` has no single
26
+ * project, so state goes to the global bucket
27
+ * `<home>/.skillstate/global/skillstate.json`;
28
+ * - any other cwd (including subdirectories of `$HOME`) — the state lives in
29
+ * the project: `<cwd>/.skillstate/skillstate.json`.
30
+ *
31
+ * Pure path arithmetic: both arguments are normalized via `path.resolve`
32
+ * before comparison, and there is NO filesystem access. The same project
33
+ * directory therefore always maps to the same state file no matter where
34
+ * the host was launched from, while different projects never share state.
35
+ * Zero-dep by design (node builtins only) so generated plugins and the MCP
36
+ * server can inline the same semantics. Keep any copy in sync.
37
+ */
38
+ export function resolveStatePathForCwd(cwd, home) {
39
+ const resolvedCwd = path.resolve(cwd);
40
+ const resolvedHome = path.resolve(home);
41
+ if (resolvedCwd === resolvedHome) {
42
+ return path.join(resolvedHome, '.skillstate', 'global', 'skillstate.json');
43
+ }
44
+ return path.join(resolvedCwd, '.skillstate', 'skillstate.json');
45
+ }
20
46
  /** True for plain (non-null, non-array) objects. */
21
47
  function isPlainObject(value) {
22
48
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -49,11 +75,13 @@ export function readSkillState(statePath) {
49
75
  }
50
76
  /**
51
77
  * Persist the state file (best-effort: read-only environments are ignored).
52
- * Writes the `{ version: 1, state }` envelope so `migrate()`/runtime resume
53
- * read the same file.
78
+ * Creates the parent directory when missing (the per-project resolver may
79
+ * target a fresh `<cwd>/.skillstate/`). Writes the `{ version: 1, state }`
80
+ * envelope so `migrate()`/runtime resume read the same file.
54
81
  */
55
82
  export function saveSkillState(statePath, state) {
56
83
  try {
84
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
57
85
  fs.writeFileSync(statePath, `${JSON.stringify({ version: 1, state }, null, 2)}\n`);
58
86
  }
59
87
  catch {
@@ -105,9 +133,15 @@ const STATE_MESSAGE_ID = 'skillstate-state-inject';
105
133
  /**
106
134
  * Build the OpenCode plugin function with the same behavior for every host
107
135
  * entry point (thin generated loaders, direct imports).
136
+ *
137
+ * State resolution is ALWAYS per-project: the state file path is computed
138
+ * from the session cwd on EVERY hook call via
139
+ * `resolveStatePathForCwd(process.cwd(), os.homedir())` — each project gets
140
+ * its own `<cwd>/.skillstate/skillstate.json`, and a session launched from
141
+ * `$HOME` uses the global bucket.
108
142
  */
109
- export function createSkillStatePlugin(options) {
110
- const statePath = options.statePath;
143
+ export function createSkillStatePlugin(options = {}) {
144
+ const resolvePath = () => resolveStatePathForCwd(process.cwd(), os.homedir());
111
145
  const maxHistory = options.maxHistoryMessages ?? 3;
112
146
  return async () => {
113
147
  return {
@@ -117,7 +151,7 @@ export function createSkillStatePlugin(options) {
117
151
  // synthetic state element. Old messages are DROPPED from the prompt,
118
152
  // not just hidden.
119
153
  'experimental.chat.messages.transform': async (_input, output) => {
120
- const state = readSkillState(statePath);
154
+ const state = readSkillState(resolvePath());
121
155
  const messages = output.messages;
122
156
  const systemMessages = messages.filter((m) => m.info.role === 'system');
123
157
  const trimmed = messages
@@ -155,7 +189,7 @@ export function createSkillStatePlugin(options) {
155
189
  // Before compaction, inject the current state into the context so the
156
190
  // compaction summary preserves state even after history is compressed.
157
191
  'experimental.session.compacting': async (_input, output) => {
158
- const state = readSkillState(statePath);
192
+ const state = readSkillState(resolvePath());
159
193
  if (!Array.isArray(output.context)) {
160
194
  output.context = [];
161
195
  }
@@ -168,6 +202,7 @@ export function createSkillStatePlugin(options) {
168
202
  const response = output.output ?? '';
169
203
  if (typeof response !== 'string')
170
204
  return;
205
+ const statePath = resolvePath();
171
206
  const patch = extractPatch(response);
172
207
  if (patch) {
173
208
  saveSkillState(statePath, mergePatch(readSkillState(statePath), patch));
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,cAAc,mBAAmB,CAAC;AAQlC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,IAAY;IAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC;AAClE,CAAC;AAED,oDAAoD;AACpD,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAY,CAAC;YAC1E,IACE,OAAO,MAAM,KAAK,QAAQ;gBAC1B,MAAM,KAAK,IAAI;gBACf,OAAO,IAAI,MAAM;gBACjB,OAAQ,MAAkC,CAAC,OAAO,CAAC,KAAK,QAAQ;gBAC/D,MAAkC,CAAC,OAAO,CAAC,KAAK,IAAI,EACrD,CAAC;gBACD,OAAQ,MAAkC,CAAC,OAAO,CAA4B,CAAC;YACjF,CAAC;YACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBAClD,OAAO,MAAiC,CAAC;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,KAA8B;IAC9E,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAA6B,EAC7B,KAA8B;IAE9B,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAA4B,EAAE,KAAK,CAAC,CAAC;QAC1E,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;QACvD,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAClE,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,2BAA2B;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4DAA4D;AAC5D,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAO,GAA4B,EAAE;IAC1E,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC,CAAC;IAEnD,OAAO,KAAK,IAAI,EAAE;QAChB,OAAO;YACL,sEAAsE;YACtE,mEAAmE;YACnE,iEAAiE;YACjE,qEAAqE;YACrE,mBAAmB;YACnB,sCAAsC,EAAE,KAAK,EAC3C,MAAM,EACN,MAAM,EACS,EAAE;gBACjB,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC,CAAC;gBAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;gBACjC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;gBACxE,MAAM,OAAO,GAAG,QAAQ;qBACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;qBACvC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;gBAEtB,oEAAoE;gBACpE,uCAAuC;gBACvC,MAAM,YAAY,GAAoB;oBACpC,IAAI,EAAE;wBACJ,EAAE,EAAE,gBAAgB;wBACpB,SAAS,EAAE,YAAY;wBACvB,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;wBACpB,KAAK,EAAE,YAAY;wBACnB,KAAK,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE;qBAC3D;oBACD,KAAK,EAAE;wBACL;4BACE,EAAE,EAAE,GAAG,gBAAgB,OAAO;4BAC9B,SAAS,EAAE,YAAY;4BACvB,SAAS,EAAE,gBAAgB;4BAC3B,IAAI,EAAE,MAAM;4BACZ,SAAS,EAAE,IAAI;4BACf,IAAI,EAAE,+BAA+B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;yBAC7D;qBACF;iBACF,CAAC;gBAEF,oEAAoE;gBACpE,gEAAgE;gBAChE,MAAM,IAAI,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,YAAY,CAAC,CAAC;gBAC3D,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBACpB,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;YACzB,CAAC;YAED,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,iCAAiC,EAAE,KAAK,EACtC,MAAM,EACN,MAAM,EACS,EAAE;gBACjB,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC,CAAC;gBAC5C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;oBACnC,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;gBACtB,CAAC;gBACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9D,CAAC;YAED,sEAAsE;YACtE,mEAAmE;YACnE,+CAA+C;YAC/C,oBAAoB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAiB,EAAE;gBAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;gBACrC,IAAI,OAAO,QAAQ,KAAK,QAAQ;oBAAE,OAAO;gBACzC,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;gBAChC,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;gBACrC,IAAI,KAAK,EAAE,CAAC;oBACV,cAAc,CAAC,SAAS,EAAE,UAAU,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC1E,CAAC;YACH,CAAC;SACwB,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillstate/opencode",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "OpenCode platform adapter for the skillstate runtime.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",