@skillstate/core 2.0.7 → 2.2.0

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.
@@ -0,0 +1,364 @@
1
+ /**
2
+ * @non-paper Wave-5 hook runtime — the SINGLE SOURCE OF TRUTH for every
3
+ * piece of logic embedded into self-contained generated hook scripts
4
+ * (Claude Code `.cjs` hooks, Codex `.cjs` hooks) and reused directly by
5
+ * the OpenCode plugin.
6
+ *
7
+ * CONSTRAINT: the generated scripts cannot import `@skillstate/*`, so the
8
+ * adapters inline these functions into the emitted `.cjs` via
9
+ * `fn.toString()` (see {@link hookRuntimeSnippet}). That means every
10
+ * function body below must be PLAIN JavaScript:
11
+ *
12
+ * - no imports and no `require` — dependencies are passed as parameters
13
+ * (`readFile`, `writeFile`, `home`), so the caller wires the real
14
+ * `node:fs`/`node:os`/`node:path` (or test mocks) at the call site;
15
+ * - type annotations live ONLY in the signatures — both `tsc` (dist) and
16
+ * the vitest/esbuild transform erase them, so `fn.toString()` yields a
17
+ * valid CJS snippet;
18
+ * - no references to module-scope helpers — each function is either fully
19
+ * self-contained or calls only its sibling functions from this module
20
+ * (which the snippet inlines together).
21
+ *
22
+ * The parity suite (`tests/core/hook-runtime-parity.test.ts`) evals the
23
+ * snippets extracted from the generated scripts and asserts byte-identical
24
+ * behavior against these originals, so the "embedded copy" can no longer
25
+ * drift from the source of truth.
26
+ */
27
+ /** True for plain (non-null, non-array) objects. */
28
+ export function isPlainObject(value) {
29
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
30
+ }
31
+ /**
32
+ * Agent-id sanitization: keep `[A-Za-z0-9_-]` runs, collapse everything
33
+ * else into single `-`, trim edge dashes, cap at 64 chars. Garbage input
34
+ * sanitizes to `''` — callers treat that as "no agent" (the main state).
35
+ */
36
+ export function sanitizeAgentId(agentId) {
37
+ return agentId
38
+ .replace(/[^A-Za-z0-9_-]+/g, '-')
39
+ .replace(/^-+|-+$/g, '')
40
+ .slice(0, 64);
41
+ }
42
+ /**
43
+ * Agent id from a host session id (Claude Code / Codex hook stdin carry
44
+ * `session_id`; opencode hooks carry `sessionID`): the short prefix — the
45
+ * first 8 characters — keeps agent directories bounded while remaining
46
+ * unique enough per session. Non-string/empty input yields `''`.
47
+ */
48
+ export function resolveAgentIdFromSession(sessionId) {
49
+ if (typeof sessionId !== 'string' || sessionId.length === 0)
50
+ return '';
51
+ return sessionId.slice(0, 8);
52
+ }
53
+ /**
54
+ * Resolve the per-project state file for a working directory — the pure,
55
+ * dependency-free mirror of `resolveHostStateForCwd`
56
+ * (`<cwd>/.skillstate/skillstate.json`; the global bucket
57
+ * `<home>/.skillstate/global/skillstate.json` when cwd equals home).
58
+ *
59
+ * AGENT-SCOPED STATE: a non-empty `agentId` (sanitized via
60
+ * {@link sanitizeAgentId}) scopes the file under an isolated
61
+ * `agents/<agentId>/` copy — parallel sub-agents (hook sessions) never
62
+ * share the main state file.
63
+ *
64
+ * String arithmetic only (POSIX): absolute paths are normalized like
65
+ * `path.resolve` (empty/`.` segments dropped, `..` popped, trailing
66
+ * slashes trimmed); relative inputs stay relative because there is no
67
+ * `process` access. Callers that may see relative paths resolve them
68
+ * first (`path.resolve(cwd)`) — the generated hook scripts do exactly
69
+ * that. `home` must be provided to detect the global bucket; when it is
70
+ * omitted the project path is returned.
71
+ */
72
+ export function resolveStatePathForCwd(cwd, home, agentId) {
73
+ const normalize = (p) => {
74
+ const isAbsolute = p.startsWith('/');
75
+ const segments = [];
76
+ for (const segment of p.split('/')) {
77
+ if (segment === '' || segment === '.')
78
+ continue;
79
+ if (segment === '..') {
80
+ if (segments.length > 0 && segments[segments.length - 1] !== '..') {
81
+ segments.pop();
82
+ }
83
+ else if (!isAbsolute) {
84
+ segments.push('..');
85
+ }
86
+ continue;
87
+ }
88
+ segments.push(segment);
89
+ }
90
+ return (isAbsolute ? '/' : '') + segments.join('/');
91
+ };
92
+ const rootless = (p) => (p === '/' ? '' : p);
93
+ const resolvedCwd = normalize(cwd);
94
+ const resolvedHome = home === undefined || home === null ? '' : normalize(home);
95
+ const agent = typeof agentId === 'string' && agentId.length > 0 ? sanitizeAgentId(agentId) : '';
96
+ const base = resolvedCwd === resolvedHome
97
+ ? `${rootless(resolvedHome)}/.skillstate/global`
98
+ : `${rootless(resolvedCwd)}/.skillstate`;
99
+ if (agent.length > 0) {
100
+ return `${base}/agents/${agent}/skillstate.json`;
101
+ }
102
+ return `${base}/skillstate.json`;
103
+ }
104
+ /**
105
+ * Read the state file through the injected `readFile` (real `fs` in
106
+ * generated scripts, mocks in tests). The on-disk envelope is
107
+ * `{ version: 1, state }`; a bare object is tolerated and treated as the
108
+ * state itself; anything else (missing file, corrupt JSON, arrays,
109
+ * scalars) yields `{}` — best-effort, never throws.
110
+ */
111
+ export function readStateEnvelope(statePath, readFile) {
112
+ try {
113
+ const parsed = JSON.parse(readFile(statePath));
114
+ if (isPlainObject(parsed)) {
115
+ if (isPlainObject(parsed.state)) {
116
+ return parsed.state;
117
+ }
118
+ return parsed;
119
+ }
120
+ }
121
+ catch {
122
+ // Missing or corrupt state file — fall back to the empty state.
123
+ }
124
+ return {};
125
+ }
126
+ /**
127
+ * Persist the state through the injected `writeFile` as the
128
+ * `{ version: 1, state }` envelope (pretty-printed, newline-terminated).
129
+ * Throws on failure — the caller decides whether to swallow it (OpenCode
130
+ * plugin) or surface a `systemMessage` (PostToolUse hooks).
131
+ */
132
+ export function saveStateEnvelope(statePath, state, writeFile) {
133
+ writeFile(statePath, `${JSON.stringify({ version: 1, state }, null, 2)}\n`);
134
+ }
135
+ /**
136
+ * Block the current thread for `ms` milliseconds — the sync sleep for the
137
+ * {@link lockStateWrite} retry loop. Prefers `Atomics.wait` on a shared
138
+ * integer (a real sleep); falls back to a busy spin when the primitive is
139
+ * unavailable. Plain JS — survives `fn.toString()` inlining.
140
+ */
141
+ export function sleepSync(ms) {
142
+ try {
143
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
144
+ return;
145
+ }
146
+ catch (error) {
147
+ // Atomics.wait unavailable in this host — spin below.
148
+ }
149
+ const end = Date.now() + ms;
150
+ while (Date.now() < end) {
151
+ // Spin — hook scripts must stay synchronous.
152
+ }
153
+ }
154
+ /**
155
+ * Cross-process state lock for the SELF-CONTAINED hook scripts (sync,
156
+ * no imports): exclusive `O_EXCL` lockfile at `statePath + '.lock'`,
157
+ * stale-TTL takeover (10s — crashed hook holders), and a retry loop
158
+ * (50ms × 40 ≈ 2s) while a live holder runs its critical section. The
159
+ * injected `fs` (real `node:fs` in generated scripts and the plugin,
160
+ * mocks in tests) performs the lockfile I/O and creates the parent
161
+ * directory. The injected `fn` runs inside the lock; the lockfile is
162
+ * ALWAYS removed in the `finally` block. `fn` failures propagate AFTER
163
+ * the release; lock exhaustion throws.
164
+ */
165
+ export function lockStateWrite(statePath, fs, fn) {
166
+ const lockPath = `${statePath}.lock`;
167
+ const ttl = 10_000;
168
+ const retries = 40;
169
+ const dir = lockPath.replace(/\/[^/]+$/, '');
170
+ try {
171
+ fs.mkdirSync(dir);
172
+ }
173
+ catch (error) {
174
+ // Exists already — only the lockfile itself must be exclusive.
175
+ }
176
+ let acquired = false;
177
+ for (let attempt = 0; attempt < retries; attempt++) {
178
+ try {
179
+ fs.closeSync(fs.openSync(lockPath, 'wx'));
180
+ acquired = true;
181
+ break;
182
+ }
183
+ catch (error) {
184
+ let mtimeMs = 0;
185
+ try {
186
+ mtimeMs = fs.statSync(lockPath).mtimeMs;
187
+ }
188
+ catch (error) {
189
+ // Vanished between the failed create and the stat — retry at once.
190
+ }
191
+ if (Date.now() - mtimeMs > ttl) {
192
+ try {
193
+ fs.unlinkSync(lockPath);
194
+ }
195
+ catch (error) {
196
+ // Another waiter removed it first — retry from the top.
197
+ }
198
+ }
199
+ else {
200
+ sleepSync(50);
201
+ }
202
+ }
203
+ }
204
+ if (!acquired) {
205
+ throw new Error(`skillstate: could not acquire the state lock: ${lockPath}`);
206
+ }
207
+ try {
208
+ return fn();
209
+ }
210
+ finally {
211
+ try {
212
+ fs.unlinkSync(lockPath);
213
+ }
214
+ catch (error) {
215
+ // Lock already removed (stale takeover raced us) — nothing to release.
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * Paper ⊕ merge: `null` deletes a key, nested plain objects merge
221
+ * recursively, everything else replaces. Pure — neither `state` nor
222
+ * `patch` is mutated.
223
+ */
224
+ export function mergePatch(state, patch) {
225
+ const result = { ...state };
226
+ for (const key of Object.keys(patch)) {
227
+ const value = patch[key];
228
+ if (value === null) {
229
+ delete result[key];
230
+ }
231
+ else if (isPlainObject(value) && isPlainObject(result[key])) {
232
+ result[key] = mergePatch(result[key], value);
233
+ }
234
+ else {
235
+ result[key] = value;
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+ /**
241
+ * Coerce a tool response into text: strings pass through, plain objects
242
+ * expose their `content` then `text` string field, other objects are
243
+ * JSON-stringified, null/undefined becomes `""`, everything else is
244
+ * `String()`-ed.
245
+ */
246
+ export function readResponseText(response) {
247
+ if (typeof response === 'string')
248
+ return response;
249
+ if (isPlainObject(response)) {
250
+ if (typeof response['content'] === 'string')
251
+ return response['content'];
252
+ if (typeof response['text'] === 'string')
253
+ return response['text'];
254
+ return JSON.stringify(response);
255
+ }
256
+ return response === null || response === undefined ? '' : String(response);
257
+ }
258
+ /**
259
+ * Look for a fenced ```json block: `{ patch }` when it parses and carries
260
+ * an object-shaped `state_patch`, `{ invalid: true }` when a block exists
261
+ * but is malformed (an open fence that never closes — truncated output —
262
+ * is still a patch attempt and classifies as invalid), `{ absent: true }`
263
+ * when there is no block at all.
264
+ */
265
+ export function findFencedPatch(text) {
266
+ const match = text.match(/```json\s*\n?([\s\S]*?)\n?\s*```/) ||
267
+ text.match(/```json\s*\n?([\s\S]+)$/);
268
+ if (!match)
269
+ return { absent: true };
270
+ try {
271
+ const parsed = JSON.parse(match[1]);
272
+ if (isPlainObject(parsed) && isPlainObject(parsed['state_patch'])) {
273
+ return { patch: parsed['state_patch'] };
274
+ }
275
+ }
276
+ catch {
277
+ // Malformed fenced JSON — report an invalid patch attempt.
278
+ }
279
+ return { invalid: true };
280
+ }
281
+ /**
282
+ * Fallback: a raw JSON object with `state_patch` anywhere in the text
283
+ * (first `{` … last `}`). Ordinary JSON output without a `state_patch`
284
+ * key is simply not a patch (`{ absent: true }`); a `state_patch` key
285
+ * holding a non-object is an invalid attempt.
286
+ */
287
+ export function findRawPatch(text) {
288
+ const trimmed = text.trim();
289
+ const candidates = [];
290
+ try {
291
+ candidates.push(JSON.parse(trimmed));
292
+ }
293
+ catch {
294
+ // Not a whole-text JSON object — try the braced slice below.
295
+ }
296
+ const first = trimmed.indexOf('{');
297
+ const last = trimmed.lastIndexOf('}');
298
+ if (first !== -1 && last > first) {
299
+ try {
300
+ candidates.push(JSON.parse(trimmed.slice(first, last + 1)));
301
+ }
302
+ catch {
303
+ // The braced slice is not JSON either — no candidates left.
304
+ }
305
+ }
306
+ for (const candidate of candidates) {
307
+ if (isPlainObject(candidate)) {
308
+ if (isPlainObject(candidate['state_patch'])) {
309
+ return { patch: candidate['state_patch'] };
310
+ }
311
+ if (Object.prototype.hasOwnProperty.call(candidate, 'state_patch')) {
312
+ return { invalid: true };
313
+ }
314
+ }
315
+ }
316
+ return { absent: true };
317
+ }
318
+ /**
319
+ * Read the session-meta sidecar's status (`<dir>/.session-meta.json`,
320
+ * sibling of the state file). Plain JS — inlined into the generated
321
+ * SessionStart hooks via `fn.toString()`. Returns the `status` string for
322
+ * a readable object-shaped sidecar, else `null` (missing/corrupt/other
323
+ * shape — no lifecycle marker). The `readFile` dependency is injected at
324
+ * the call site (real `node:fs` in hooks, mocks in tests).
325
+ */
326
+ export function readSessionMetaStatus(metaPath, readFile) {
327
+ try {
328
+ const parsed = JSON.parse(readFile(metaPath));
329
+ if (isPlainObject(parsed) && typeof parsed.status === 'string') {
330
+ return parsed.status;
331
+ }
332
+ }
333
+ catch (error) {
334
+ // Missing or corrupt sidecar — no lifecycle marker.
335
+ }
336
+ return null;
337
+ }
338
+ /**
339
+ * Assemble the CJS snippet embedded into every generated hook script:
340
+ * all sibling functions of this module, source-verbatim via
341
+ * `fn.toString()`. The adapters splice this block after their `require`
342
+ * preamble, which keeps the embedded logic byte-identical across hosts
343
+ * and impossible to drift from this module.
344
+ */
345
+ export function hookRuntimeSnippet() {
346
+ return [
347
+ isPlainObject,
348
+ resolveAgentIdFromSession,
349
+ sanitizeAgentId,
350
+ resolveStatePathForCwd,
351
+ readStateEnvelope,
352
+ saveStateEnvelope,
353
+ mergePatch,
354
+ readResponseText,
355
+ findFencedPatch,
356
+ findRawPatch,
357
+ sleepSync,
358
+ lockStateWrite,
359
+ readSessionMetaStatus,
360
+ ]
361
+ .map((fn) => fn.toString())
362
+ .join('\n\n');
363
+ }
364
+ //# sourceMappingURL=hook-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-runtime.js","sourceRoot":"","sources":["../src/hook-runtime.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,8 @@
2
2
  * Resolve the per-project state file for a working directory —
3
3
  * `<cwd>/.skillstate/skillstate.json`, or the global bucket
4
4
  * `<home>/.skillstate/global/skillstate.json` when cwd equals home
5
- * (`home` defaults to `os.homedir()`).
5
+ * (`home` defaults to `os.homedir()`). A non-empty `agentId` scopes the
6
+ * file under `<bucket>/agents/<sanitized agentId>/skillstate.json`.
6
7
  */
7
- export declare function resolveHostStateForCwd(cwd: string, home?: string): string;
8
+ export declare function resolveHostStateForCwd(cwd: string, home?: string, agentId?: string): string;
8
9
  //# sourceMappingURL=host-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"host-state.d.ts","sourceRoot":"","sources":["../src/host-state.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzE"}
1
+ {"version":3,"file":"host-state.d.ts","sourceRoot":"","sources":["../src/host-state.ts"],"names":[],"mappings":"AAyBA;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAa3F"}
@@ -1,31 +1,44 @@
1
1
  /**
2
2
  * @non-paper Wave-4 DX helper — the canonical per-project state resolver
3
3
  * shared by every host adapter (OpenCode plugin, Codex hooks/fork-trim,
4
- * Claude Code hooks).
4
+ * Claude Code hooks, MCP).
5
5
  *
6
6
  * Semantics: the state file for a working directory is
7
7
  * `<cwd>/.skillstate/skillstate.json`; a session opened directly in the
8
8
  * user's home resolves to the global bucket
9
- * `<home>/.skillstate/global/skillstate.json`. Pure path arithmetic — no
10
- * filesystem access. Host-embedded hook scripts (self-contained `.cjs`)
11
- * keep a byte-equivalent copy of this logic because they must run without
12
- * importing `@skillstate/*`; this function is the single source of truth
13
- * the copies mirror.
9
+ * `<home>/.skillstate/global/skillstate.json`. AGENT-SCOPED STATE: a
10
+ * non-empty `agentId` (sanitized `[A-Za-z0-9_-]`, ≤64 chars) scopes the
11
+ * file under an isolated copy — `agents/<agentId>/skillstate.json` inside
12
+ * the same bucket — so 2-3 parallel sub-agents (hook sessions) never
13
+ * last-writer-win over each other; the main agent keeps the unscoped
14
+ * path and merges sub-agent copies explicitly (MCP `agent.merge`).
15
+ *
16
+ * Pure path arithmetic — no filesystem access. Host-embedded hook scripts
17
+ * (self-contained `.cjs`) keep a behavior-equivalent copy of this logic in
18
+ * the hook-runtime `resolveStatePathForCwd` because they must run without
19
+ * importing `@skillstate/*`; that function is the parity mirror. The agent
20
+ * id sanitizer is shared verbatim from the hook-runtime module.
14
21
  */
15
22
  import * as os from 'node:os';
16
23
  import * as path from 'node:path';
24
+ import { sanitizeAgentId } from './hook-runtime.js';
17
25
  /**
18
26
  * Resolve the per-project state file for a working directory —
19
27
  * `<cwd>/.skillstate/skillstate.json`, or the global bucket
20
28
  * `<home>/.skillstate/global/skillstate.json` when cwd equals home
21
- * (`home` defaults to `os.homedir()`).
29
+ * (`home` defaults to `os.homedir()`). A non-empty `agentId` scopes the
30
+ * file under `<bucket>/agents/<sanitized agentId>/skillstate.json`.
22
31
  */
23
- export function resolveHostStateForCwd(cwd, home) {
32
+ export function resolveHostStateForCwd(cwd, home, agentId) {
24
33
  const resolvedCwd = path.resolve(cwd);
25
34
  const resolvedHome = path.resolve(home ?? os.homedir());
26
- if (resolvedCwd === resolvedHome) {
27
- return path.join(resolvedHome, '.skillstate', 'global', 'skillstate.json');
35
+ const bucket = resolvedCwd === resolvedHome
36
+ ? path.join(resolvedHome, '.skillstate', 'global')
37
+ : path.join(resolvedCwd, '.skillstate');
38
+ const agent = typeof agentId === 'string' && agentId.length > 0 ? sanitizeAgentId(agentId) : '';
39
+ if (agent.length > 0) {
40
+ return path.join(bucket, 'agents', agent, 'skillstate.json');
28
41
  }
29
- return path.join(resolvedCwd, '.skillstate', 'skillstate.json');
42
+ return path.join(bucket, 'skillstate.json');
30
43
  }
31
44
  //# sourceMappingURL=host-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"host-state.js","sourceRoot":"","sources":["../src/host-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,IAAa;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,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"}
1
+ {"version":3,"file":"host-state.js","sourceRoot":"","sources":["../src/host-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,IAAa,EAAE,OAAgB;IACjF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,MAAM,MAAM,GACV,WAAW,KAAK,YAAY;QAC1B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,CAAC;QAClD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC5C,MAAM,KAAK,GACT,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAC9C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -16,6 +16,10 @@ export * from './logger.js';
16
16
  export * from './provider.js';
17
17
  export * from './config.js';
18
18
  export * from './host-state.js';
19
+ export * from './hook-runtime.js';
20
+ export * from './adapter-shared.js';
21
+ export * from './prompt-contract.js';
19
22
  export * from './shutdown.js';
23
+ export * from './session-meta.js';
20
24
  export * from './schemas/index.js';
21
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AAErC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAElC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAE5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AAErC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAElC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAE5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -24,6 +24,16 @@ export * from './logger.js';
24
24
  export * from './provider.js';
25
25
  export * from './config.js';
26
26
  export * from './host-state.js';
27
+ // @non-paper Wave-5 hook runtime (single source of truth for generated
28
+ // hook scripts and the OpenCode plugin).
29
+ export * from './hook-runtime.js';
30
+ // @non-paper Wave-5 shared adapter plumbing (resolve/save/merge helpers
31
+ // reused by the claude/codex adapters).
32
+ export * from './adapter-shared.js';
33
+ // @non-paper Wave-5 deduplicated prompt texts (shared adapter vocabulary).
34
+ export * from './prompt-contract.js';
27
35
  export * from './shutdown.js';
36
+ // @non-paper release-2.3.0 session lifecycle marker (orchestration meta).
37
+ export * from './session-meta.js';
28
38
  export * from './schemas/index.js';
29
39
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,0EAA0E;AAC1E,6EAA6E;AAC7E,qDAAqD;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,qEAAqE;AACrE,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,0EAA0E;AAC1E,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,mDAAmD;AACnD,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,0EAA0E;AAC1E,6EAA6E;AAC7E,qDAAqD;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,qEAAqE;AACrE,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,0EAA0E;AAC1E,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,mDAAmD;AACnD,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,uEAAuE;AACvE,yCAAyC;AACzC,cAAc,mBAAmB,CAAC;AAClC,wEAAwE;AACxE,wCAAwC;AACxC,cAAc,qBAAqB,CAAC;AACpC,2EAA2E;AAC3E,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,0EAA0E;AAC1E,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @non-paper Canonical prompt texts — the SINGLE SOURCE OF TRUTH for the
3
+ * {state_patch, action} JSON contract, the history-unreliability hint, the
4
+ * schema renderer, and the SKILL.md body shared by every platform adapter.
5
+ *
6
+ * PROMPT-FIDELITY BOUNDARY: `PromptTransformer.formatPaper` (Appendix A.4)
7
+ * is byte-verbatim paper text and deliberately does NOT use these
8
+ * constants — its inline `{ "state_patch": { <dict: ...> }, "action": ... }`
9
+ * directive differs from the fenced example below by design. Everything
10
+ * here is the @non-paper adapter vocabulary, deduplicated across
11
+ * claude/codex/opencode adapters and the prompt transformer.
12
+ */
13
+ import type { ProceduralSpec, StateSchema } from './types.js';
14
+ /**
15
+ * The two-key response directive (without the fenced example). Adapters
16
+ * prepend their own lead-in ("Based on your current state, provide your
17
+ * response with:" etc.) and append the numbered items via
18
+ * {@link STATE_PATCH_CONTRACT}.
19
+ */
20
+ export declare const STATE_PATCH_CONTRACT_HEADER = "A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:";
21
+ /** Canonical fenced ```json example block for the two-key contract. */
22
+ export declare const STATE_PATCH_EXAMPLE_JSON: string;
23
+ /** Canonical sparse-patch rules: null deletes, omissions leave state unchanged. */
24
+ export declare const STATE_PATCH_RULES = "In `state_patch`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.";
25
+ /**
26
+ * The reasoning-is-discarded persistence bullet (paper §3.2, §4): any fact
27
+ * that must survive belongs in `state_patch`, never in the conversation.
28
+ */
29
+ export declare const REASONING_DISCARDED_NOTE = "Reasoning is discarded after execution \u2014 put anything you need to persist into `state_patch`.";
30
+ /**
31
+ * The full numbered response contract: reasoning is discarded (paper §3.2),
32
+ * the JSON block carries exactly the two keys, and the patch follows the
33
+ * sparse ⊕ semantics. Includes the example block and the rules — no lead-in.
34
+ */
35
+ export declare const STATE_PATCH_CONTRACT: string;
36
+ /**
37
+ * The single history-unreliability hint appended to the additionalContext
38
+ * of every inject-style hook script (claude + codex alike). Names the MCP
39
+ * tools AND the fenced ```json state_patch channel — one text for all
40
+ * hosts, so the Bash-carried patch option can no longer drift away.
41
+ */
42
+ export declare const HISTORY_UNRELIABLE_NOTE = "\nHistory is not reliable. Persist anything you need via the skillstate MCP tools (state.summary / state.patch) or a fenced ```json state_patch block.";
43
+ /**
44
+ * The interrupted-session hint injected by the SessionStart hooks
45
+ * (claude + codex alike) when the session-meta sidecar carries
46
+ * `status: "interrupted"` — a previous run of this session was killed
47
+ * (SIGINT/SIGTERM) mid-procedure. The hook appends the preserved state
48
+ * path so the agent can review progress/blockers before continuing.
49
+ * A fresh launch overwrites the status back to `running`.
50
+ */
51
+ export declare const INTERRUPTED_SESSION_NOTE = "\nPrevious session was interrupted; state preserved at <path>; review progress/blockers before continuing.";
52
+ /**
53
+ * Render a state schema as the shared `## Schema` markdown block used by
54
+ * every prompt formatter and adapter `injectState`. Fields without a
55
+ * description fall back to "no description".
56
+ */
57
+ export declare function describeSchema(schema: StateSchema): string;
58
+ /** Options for {@link skillMdBody}. */
59
+ export interface SkillMdBodyOptions {
60
+ /** Brand label woven into the hooks intro ("Claude Code", "Codex"). */
61
+ hostLabel: string;
62
+ /**
63
+ * How the injected state reaches the model, as a predicate:
64
+ * "injected into your context via hooks" (claude) /
65
+ * "provided as developer context" (codex).
66
+ */
67
+ injectionPhrase: string;
68
+ /** The skill spec — name/instructions/version fill the frontmatter. */
69
+ spec: ProceduralSpec;
70
+ /** State path written into the frontmatter and the body (default `./.skillstate/skillstate.json`). */
71
+ statePath?: string;
72
+ }
73
+ /**
74
+ * Generate the whole SKILL.md document shared by the hook-wiring adapters
75
+ * (claude, codex): identical frontmatter, Execution Context, and Process
76
+ * sections; the ONLY brand-specific parts are `hostLabel` (hooks intro)
77
+ * and `injectionPhrase` (how the state reaches the model). OpenCode keeps
78
+ * its own body — its history is trimmed by the plugin, not by hooks, so
79
+ * its Execution Context and Process genuinely differ.
80
+ */
81
+ export declare function skillMdBody(options: SkillMdBodyOptions): string;
82
+ //# sourceMappingURL=prompt-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-contract.d.ts","sourceRoot":"","sources":["../src/prompt-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,oHAC2E,CAAC;AAEpH,uEAAuE;AACvE,eAAO,MAAM,wBAAwB,QAOzB,CAAC;AAEb,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,oIACqG,CAAC;AAEpI;;;GAGG;AACH,eAAO,MAAM,wBAAwB,uGAC4D,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAOrB,CAAC;AAEb;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,2JACsH,CAAC;AAE3J;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,+GACyE,CAAC;AAE/G;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQ1D;AAED,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,IAAI,EAAE,cAAc,CAAC;IACrB,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAwD/D"}