@skillstate/core 2.0.7 → 2.1.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.
- package/dist/adapter-shared.d.ts +61 -0
- package/dist/adapter-shared.d.ts.map +1 -0
- package/dist/adapter-shared.js +96 -0
- package/dist/adapter-shared.js.map +1 -0
- package/dist/hook-runtime.d.ts +103 -0
- package/dist/hook-runtime.d.ts.map +1 -0
- package/dist/hook-runtime.js +224 -0
- package/dist/hook-runtime.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/prompt-contract.d.ts +73 -0
- package/dist/prompt-contract.d.ts.map +1 -0
- package/dist/prompt-contract.js +115 -0
- package/dist/prompt-contract.js.map +1 -0
- package/dist/prompt-transformer.d.ts +0 -4
- package/dist/prompt-transformer.d.ts.map +1 -1
- package/dist/prompt-transformer.js +8 -43
- package/dist/prompt-transformer.js.map +1 -1
- package/dist/state-manager.d.ts.map +1 -1
- package/dist/state-manager.js +6 -30
- package/dist/state-manager.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @non-paper Wave-5 shared adapter plumbing — the SINGLE SOURCE OF TRUTH
|
|
3
|
+
* for the state-path resolution, atomic persistence, and hooks-merge
|
|
4
|
+
* mechanics that were duplicated across the platform adapters (claude,
|
|
5
|
+
* codex). Adapters stay EVENT TABLES + brand constants + script/config
|
|
6
|
+
* generators; everything below is host-agnostic:
|
|
7
|
+
*
|
|
8
|
+
* - {@link resolveTarget}: the `string | StatePathRef` resolution every
|
|
9
|
+
* adapter save/generate methods shared (throws on `..` traversal);
|
|
10
|
+
* - {@link saveGenerated}: resolve + `atomicWriteFile` + return the dest —
|
|
11
|
+
* the body of every adapter `save` method;
|
|
12
|
+
* - {@link mergeHookGroups}: the hooks.json / settings.json merge —
|
|
13
|
+
* idempotent by skillstate commands, appends fresh groups, preserves
|
|
14
|
+
* every foreign group and top-level key. Format specifics (which groups
|
|
15
|
+
* to generate, which commands are "ours") are passed in by the adapter.
|
|
16
|
+
*/
|
|
17
|
+
import type { StatePathRef } from './atomic-write.js';
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a `string | StatePathRef` target via `resolveStatePath` — the
|
|
20
|
+
* exact logic every adapter's private `resolve` helper shared. Raw strings
|
|
21
|
+
* pass through; `{ root, name }` refs are confined to `root` (traversal
|
|
22
|
+
* throws).
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveTarget(target: string | StatePathRef): string;
|
|
25
|
+
/**
|
|
26
|
+
* Persist adapter-generated content to `target` via `atomicWriteFile`
|
|
27
|
+
* (temp sibling + fsync + rename) and return the absolute destination
|
|
28
|
+
* path. The shared body of the adapter `save*` methods.
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveGenerated(target: string | StatePathRef, content: string): Promise<string>;
|
|
31
|
+
/** Parameters for {@link mergeHookGroups} (the adapter-provided specifics). */
|
|
32
|
+
export interface MergeHookGroupsParams {
|
|
33
|
+
/** The existing hooks document text (hooks.json / settings.json). */
|
|
34
|
+
existingJson: string;
|
|
35
|
+
/** The adapter-generated `{ Event: [group, …] }` groups to append. */
|
|
36
|
+
generatedGroups: Record<string, unknown[]>;
|
|
37
|
+
/**
|
|
38
|
+
* JSON-stringified skillstate commands (one per generated event script)
|
|
39
|
+
* — a handler whose stringified `command` is in this set marks the
|
|
40
|
+
* document as already wired.
|
|
41
|
+
*/
|
|
42
|
+
commandsOf: ReadonlySet<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Merge the skillstate hook groups into an existing hooks document — the
|
|
46
|
+
* shared mechanics of the claude and codex `mergeHooksConfig` methods:
|
|
47
|
+
*
|
|
48
|
+
* - malformed/empty/non-object input starts from a fresh document (the
|
|
49
|
+
* CLI guards a live config before calling);
|
|
50
|
+
* - idempotent: if ANY skillstate command is already wired, the ORIGINAL
|
|
51
|
+
* text is returned byte-unchanged (no re-serialization, no duplicate
|
|
52
|
+
* groups, no surprise reformatting of a hand-written file);
|
|
53
|
+
* - otherwise every generated group is APPENDED per event: existing
|
|
54
|
+
* (non-skillstate) groups and every other top-level key survive, and a
|
|
55
|
+
* non-array event value is replaced by the fresh group.
|
|
56
|
+
*
|
|
57
|
+
* Returns the merged document re-serialized (2-space indent, newline-
|
|
58
|
+
* terminated) — or the untouched input on the already-wired path.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mergeHookGroups({ existingJson, generatedGroups, commandsOf, }: MergeHookGroupsParams): string;
|
|
61
|
+
//# sourceMappingURL=adapter-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-shared.d.ts","sourceRoot":"","sources":["../src/adapter-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,CAInE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C;;;;OAIG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,EAC9B,YAAY,EACZ,eAAe,EACf,UAAU,GACX,EAAE,qBAAqB,GAAG,MAAM,CAoChC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @non-paper Wave-5 shared adapter plumbing — the SINGLE SOURCE OF TRUTH
|
|
3
|
+
* for the state-path resolution, atomic persistence, and hooks-merge
|
|
4
|
+
* mechanics that were duplicated across the platform adapters (claude,
|
|
5
|
+
* codex). Adapters stay EVENT TABLES + brand constants + script/config
|
|
6
|
+
* generators; everything below is host-agnostic:
|
|
7
|
+
*
|
|
8
|
+
* - {@link resolveTarget}: the `string | StatePathRef` resolution every
|
|
9
|
+
* adapter save/generate methods shared (throws on `..` traversal);
|
|
10
|
+
* - {@link saveGenerated}: resolve + `atomicWriteFile` + return the dest —
|
|
11
|
+
* the body of every adapter `save` method;
|
|
12
|
+
* - {@link mergeHookGroups}: the hooks.json / settings.json merge —
|
|
13
|
+
* idempotent by skillstate commands, appends fresh groups, preserves
|
|
14
|
+
* every foreign group and top-level key. Format specifics (which groups
|
|
15
|
+
* to generate, which commands are "ours") are passed in by the adapter.
|
|
16
|
+
*/
|
|
17
|
+
import { atomicWriteFile, resolveStatePath } from './atomic-write.js';
|
|
18
|
+
import { isPlainObject } from './hook-runtime.js';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a `string | StatePathRef` target via `resolveStatePath` — the
|
|
21
|
+
* exact logic every adapter's private `resolve` helper shared. Raw strings
|
|
22
|
+
* pass through; `{ root, name }` refs are confined to `root` (traversal
|
|
23
|
+
* throws).
|
|
24
|
+
*/
|
|
25
|
+
export function resolveTarget(target) {
|
|
26
|
+
return typeof target === 'string'
|
|
27
|
+
? target
|
|
28
|
+
: resolveStatePath(target.root, target.name);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Persist adapter-generated content to `target` via `atomicWriteFile`
|
|
32
|
+
* (temp sibling + fsync + rename) and return the absolute destination
|
|
33
|
+
* path. The shared body of the adapter `save*` methods.
|
|
34
|
+
*/
|
|
35
|
+
export async function saveGenerated(target, content) {
|
|
36
|
+
const dest = resolveTarget(target);
|
|
37
|
+
await atomicWriteFile(dest, content);
|
|
38
|
+
return dest;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Merge the skillstate hook groups into an existing hooks document — the
|
|
42
|
+
* shared mechanics of the claude and codex `mergeHooksConfig` methods:
|
|
43
|
+
*
|
|
44
|
+
* - malformed/empty/non-object input starts from a fresh document (the
|
|
45
|
+
* CLI guards a live config before calling);
|
|
46
|
+
* - idempotent: if ANY skillstate command is already wired, the ORIGINAL
|
|
47
|
+
* text is returned byte-unchanged (no re-serialization, no duplicate
|
|
48
|
+
* groups, no surprise reformatting of a hand-written file);
|
|
49
|
+
* - otherwise every generated group is APPENDED per event: existing
|
|
50
|
+
* (non-skillstate) groups and every other top-level key survive, and a
|
|
51
|
+
* non-array event value is replaced by the fresh group.
|
|
52
|
+
*
|
|
53
|
+
* Returns the merged document re-serialized (2-space indent, newline-
|
|
54
|
+
* terminated) — or the untouched input on the already-wired path.
|
|
55
|
+
*/
|
|
56
|
+
export function mergeHookGroups({ existingJson, generatedGroups, commandsOf, }) {
|
|
57
|
+
let doc = {};
|
|
58
|
+
try {
|
|
59
|
+
doc = JSON.parse(existingJson);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Missing or malformed input: start from a fresh document.
|
|
63
|
+
}
|
|
64
|
+
if (!isPlainObject(doc)) {
|
|
65
|
+
doc = {};
|
|
66
|
+
}
|
|
67
|
+
if (!isPlainObject(doc['hooks'])) {
|
|
68
|
+
doc['hooks'] = {};
|
|
69
|
+
}
|
|
70
|
+
const hooks = doc['hooks'];
|
|
71
|
+
let alreadyWired = false;
|
|
72
|
+
for (const groups of Object.values(hooks)) {
|
|
73
|
+
if (!Array.isArray(groups))
|
|
74
|
+
continue;
|
|
75
|
+
for (const group of groups) {
|
|
76
|
+
if (!isPlainObject(group) || !Array.isArray(group['hooks']))
|
|
77
|
+
continue;
|
|
78
|
+
for (const handler of group['hooks']) {
|
|
79
|
+
if (isPlainObject(handler) && commandsOf.has(JSON.stringify(handler['command']))) {
|
|
80
|
+
alreadyWired = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (alreadyWired) {
|
|
86
|
+
return existingJson;
|
|
87
|
+
}
|
|
88
|
+
for (const [event, groups] of Object.entries(generatedGroups)) {
|
|
89
|
+
const existing = Array.isArray(hooks[event])
|
|
90
|
+
? hooks[event]
|
|
91
|
+
: [];
|
|
92
|
+
hooks[event] = [...existing, ...groups];
|
|
93
|
+
}
|
|
94
|
+
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=adapter-shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-shared.js","sourceRoot":"","sources":["../src/adapter-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAA6B;IACzD,OAAO,OAAO,MAAM,KAAK,QAAQ;QAC/B,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAA6B,EAC7B,OAAe;IAEf,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,YAAY,EACZ,eAAe,EACf,UAAU,GACY;IACtB,IAAI,GAAG,GAA4B,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAe,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;IAC7D,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,GAAG,GAAG,EAAE,CAAC;IACX,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAA4B,CAAC;IACtD,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,SAAS;QACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAAE,SAAS;YACtE,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjF,YAAY,GAAG,IAAI,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC,CAAE,KAAK,CAAC,KAAK,CAAe;YAC7B,CAAC,CAAC,EAAE,CAAC;QACP,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
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 declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the per-project state file for a working directory — the pure,
|
|
31
|
+
* dependency-free mirror of `resolveHostStateForCwd`
|
|
32
|
+
* (`<cwd>/.skillstate/skillstate.json`; the global bucket
|
|
33
|
+
* `<home>/.skillstate/global/skillstate.json` when cwd equals home).
|
|
34
|
+
*
|
|
35
|
+
* String arithmetic only (POSIX): absolute paths are normalized like
|
|
36
|
+
* `path.resolve` (empty/`.` segments dropped, `..` popped, trailing
|
|
37
|
+
* slashes trimmed); relative inputs stay relative because there is no
|
|
38
|
+
* `process` access. Callers that may see relative paths resolve them
|
|
39
|
+
* first (`path.resolve(cwd)`) — the generated hook scripts do exactly
|
|
40
|
+
* that. `home` must be provided to detect the global bucket; when it is
|
|
41
|
+
* omitted the project path is returned.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveStatePathForCwd(cwd: string, home?: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Read the state file through the injected `readFile` (real `fs` in
|
|
46
|
+
* generated scripts, mocks in tests). The on-disk envelope is
|
|
47
|
+
* `{ version: 1, state }`; a bare object is tolerated and treated as the
|
|
48
|
+
* state itself; anything else (missing file, corrupt JSON, arrays,
|
|
49
|
+
* scalars) yields `{}` — best-effort, never throws.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readStateEnvelope(statePath: string, readFile: (p: string) => string): unknown;
|
|
52
|
+
/**
|
|
53
|
+
* Persist the state through the injected `writeFile` as the
|
|
54
|
+
* `{ version: 1, state }` envelope (pretty-printed, newline-terminated).
|
|
55
|
+
* Throws on failure — the caller decides whether to swallow it (OpenCode
|
|
56
|
+
* plugin) or surface a `systemMessage` (PostToolUse hooks).
|
|
57
|
+
*/
|
|
58
|
+
export declare function saveStateEnvelope(statePath: string, state: object, writeFile: (p: string, data: string) => void): void;
|
|
59
|
+
/**
|
|
60
|
+
* Paper ⊕ merge: `null` deletes a key, nested plain objects merge
|
|
61
|
+
* recursively, everything else replaces. Pure — neither `state` nor
|
|
62
|
+
* `patch` is mutated.
|
|
63
|
+
*/
|
|
64
|
+
export declare function mergePatch(state: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* Coerce a tool response into text: strings pass through, plain objects
|
|
67
|
+
* expose their `content` then `text` string field, other objects are
|
|
68
|
+
* JSON-stringified, null/undefined becomes `""`, everything else is
|
|
69
|
+
* `String()`-ed.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readResponseText(response: unknown): string;
|
|
72
|
+
/** Outcome of the patch extractors: a patch, an invalid attempt, or nothing. */
|
|
73
|
+
export type PatchLookup = {
|
|
74
|
+
patch: Record<string, unknown>;
|
|
75
|
+
} | {
|
|
76
|
+
invalid: true;
|
|
77
|
+
} | {
|
|
78
|
+
absent: true;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Look for a fenced ```json block: `{ patch }` when it parses and carries
|
|
82
|
+
* an object-shaped `state_patch`, `{ invalid: true }` when a block exists
|
|
83
|
+
* but is malformed (an open fence that never closes — truncated output —
|
|
84
|
+
* is still a patch attempt and classifies as invalid), `{ absent: true }`
|
|
85
|
+
* when there is no block at all.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findFencedPatch(text: string): PatchLookup;
|
|
88
|
+
/**
|
|
89
|
+
* Fallback: a raw JSON object with `state_patch` anywhere in the text
|
|
90
|
+
* (first `{` … last `}`). Ordinary JSON output without a `state_patch`
|
|
91
|
+
* key is simply not a patch (`{ absent: true }`); a `state_patch` key
|
|
92
|
+
* holding a non-object is an invalid attempt.
|
|
93
|
+
*/
|
|
94
|
+
export declare function findRawPatch(text: string): PatchLookup;
|
|
95
|
+
/**
|
|
96
|
+
* Assemble the CJS snippet embedded into every generated hook script:
|
|
97
|
+
* all sibling functions of this module, source-verbatim via
|
|
98
|
+
* `fn.toString()`. The adapters splice this block after their `require`
|
|
99
|
+
* preamble, which keeps the embedded logic byte-identical across hosts
|
|
100
|
+
* and impossible to drift from this module.
|
|
101
|
+
*/
|
|
102
|
+
export declare function hookRuntimeSnippet(): string;
|
|
103
|
+
//# sourceMappingURL=hook-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-runtime.d.ts","sourceRoot":"","sources":["../src/hook-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBzE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC9B,OAAO,CAaT;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAC3C,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAQ1D;AAED,gFAAgF;AAChF,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAClC;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAczD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA4BtD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C"}
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
* Resolve the per-project state file for a working directory — the pure,
|
|
33
|
+
* dependency-free mirror of `resolveHostStateForCwd`
|
|
34
|
+
* (`<cwd>/.skillstate/skillstate.json`; the global bucket
|
|
35
|
+
* `<home>/.skillstate/global/skillstate.json` when cwd equals home).
|
|
36
|
+
*
|
|
37
|
+
* String arithmetic only (POSIX): absolute paths are normalized like
|
|
38
|
+
* `path.resolve` (empty/`.` segments dropped, `..` popped, trailing
|
|
39
|
+
* slashes trimmed); relative inputs stay relative because there is no
|
|
40
|
+
* `process` access. Callers that may see relative paths resolve them
|
|
41
|
+
* first (`path.resolve(cwd)`) — the generated hook scripts do exactly
|
|
42
|
+
* that. `home` must be provided to detect the global bucket; when it is
|
|
43
|
+
* omitted the project path is returned.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveStatePathForCwd(cwd, home) {
|
|
46
|
+
const normalize = (p) => {
|
|
47
|
+
const isAbsolute = p.startsWith('/');
|
|
48
|
+
const segments = [];
|
|
49
|
+
for (const segment of p.split('/')) {
|
|
50
|
+
if (segment === '' || segment === '.')
|
|
51
|
+
continue;
|
|
52
|
+
if (segment === '..') {
|
|
53
|
+
if (segments.length > 0 && segments[segments.length - 1] !== '..') {
|
|
54
|
+
segments.pop();
|
|
55
|
+
}
|
|
56
|
+
else if (!isAbsolute) {
|
|
57
|
+
segments.push('..');
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
segments.push(segment);
|
|
62
|
+
}
|
|
63
|
+
return (isAbsolute ? '/' : '') + segments.join('/');
|
|
64
|
+
};
|
|
65
|
+
const rootless = (p) => (p === '/' ? '' : p);
|
|
66
|
+
const resolvedCwd = normalize(cwd);
|
|
67
|
+
const resolvedHome = home === undefined || home === null ? '' : normalize(home);
|
|
68
|
+
if (resolvedCwd === resolvedHome) {
|
|
69
|
+
return `${rootless(resolvedHome)}/.skillstate/global/skillstate.json`;
|
|
70
|
+
}
|
|
71
|
+
return `${rootless(resolvedCwd)}/.skillstate/skillstate.json`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Read the state file through the injected `readFile` (real `fs` in
|
|
75
|
+
* generated scripts, mocks in tests). The on-disk envelope is
|
|
76
|
+
* `{ version: 1, state }`; a bare object is tolerated and treated as the
|
|
77
|
+
* state itself; anything else (missing file, corrupt JSON, arrays,
|
|
78
|
+
* scalars) yields `{}` — best-effort, never throws.
|
|
79
|
+
*/
|
|
80
|
+
export function readStateEnvelope(statePath, readFile) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(readFile(statePath));
|
|
83
|
+
if (isPlainObject(parsed)) {
|
|
84
|
+
if (isPlainObject(parsed.state)) {
|
|
85
|
+
return parsed.state;
|
|
86
|
+
}
|
|
87
|
+
return parsed;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Missing or corrupt state file — fall back to the empty state.
|
|
92
|
+
}
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Persist the state through the injected `writeFile` as the
|
|
97
|
+
* `{ version: 1, state }` envelope (pretty-printed, newline-terminated).
|
|
98
|
+
* Throws on failure — the caller decides whether to swallow it (OpenCode
|
|
99
|
+
* plugin) or surface a `systemMessage` (PostToolUse hooks).
|
|
100
|
+
*/
|
|
101
|
+
export function saveStateEnvelope(statePath, state, writeFile) {
|
|
102
|
+
writeFile(statePath, `${JSON.stringify({ version: 1, state }, null, 2)}\n`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Paper ⊕ merge: `null` deletes a key, nested plain objects merge
|
|
106
|
+
* recursively, everything else replaces. Pure — neither `state` nor
|
|
107
|
+
* `patch` is mutated.
|
|
108
|
+
*/
|
|
109
|
+
export function mergePatch(state, patch) {
|
|
110
|
+
const result = { ...state };
|
|
111
|
+
for (const key of Object.keys(patch)) {
|
|
112
|
+
const value = patch[key];
|
|
113
|
+
if (value === null) {
|
|
114
|
+
delete result[key];
|
|
115
|
+
}
|
|
116
|
+
else if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
117
|
+
result[key] = mergePatch(result[key], value);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
result[key] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Coerce a tool response into text: strings pass through, plain objects
|
|
127
|
+
* expose their `content` then `text` string field, other objects are
|
|
128
|
+
* JSON-stringified, null/undefined becomes `""`, everything else is
|
|
129
|
+
* `String()`-ed.
|
|
130
|
+
*/
|
|
131
|
+
export function readResponseText(response) {
|
|
132
|
+
if (typeof response === 'string')
|
|
133
|
+
return response;
|
|
134
|
+
if (isPlainObject(response)) {
|
|
135
|
+
if (typeof response['content'] === 'string')
|
|
136
|
+
return response['content'];
|
|
137
|
+
if (typeof response['text'] === 'string')
|
|
138
|
+
return response['text'];
|
|
139
|
+
return JSON.stringify(response);
|
|
140
|
+
}
|
|
141
|
+
return response === null || response === undefined ? '' : String(response);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Look for a fenced ```json block: `{ patch }` when it parses and carries
|
|
145
|
+
* an object-shaped `state_patch`, `{ invalid: true }` when a block exists
|
|
146
|
+
* but is malformed (an open fence that never closes — truncated output —
|
|
147
|
+
* is still a patch attempt and classifies as invalid), `{ absent: true }`
|
|
148
|
+
* when there is no block at all.
|
|
149
|
+
*/
|
|
150
|
+
export function findFencedPatch(text) {
|
|
151
|
+
const match = text.match(/```json\s*\n?([\s\S]*?)\n?\s*```/) ||
|
|
152
|
+
text.match(/```json\s*\n?([\s\S]+)$/);
|
|
153
|
+
if (!match)
|
|
154
|
+
return { absent: true };
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(match[1]);
|
|
157
|
+
if (isPlainObject(parsed) && isPlainObject(parsed['state_patch'])) {
|
|
158
|
+
return { patch: parsed['state_patch'] };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Malformed fenced JSON — report an invalid patch attempt.
|
|
163
|
+
}
|
|
164
|
+
return { invalid: true };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Fallback: a raw JSON object with `state_patch` anywhere in the text
|
|
168
|
+
* (first `{` … last `}`). Ordinary JSON output without a `state_patch`
|
|
169
|
+
* key is simply not a patch (`{ absent: true }`); a `state_patch` key
|
|
170
|
+
* holding a non-object is an invalid attempt.
|
|
171
|
+
*/
|
|
172
|
+
export function findRawPatch(text) {
|
|
173
|
+
const trimmed = text.trim();
|
|
174
|
+
const candidates = [];
|
|
175
|
+
try {
|
|
176
|
+
candidates.push(JSON.parse(trimmed));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Not a whole-text JSON object — try the braced slice below.
|
|
180
|
+
}
|
|
181
|
+
const first = trimmed.indexOf('{');
|
|
182
|
+
const last = trimmed.lastIndexOf('}');
|
|
183
|
+
if (first !== -1 && last > first) {
|
|
184
|
+
try {
|
|
185
|
+
candidates.push(JSON.parse(trimmed.slice(first, last + 1)));
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// The braced slice is not JSON either — no candidates left.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const candidate of candidates) {
|
|
192
|
+
if (isPlainObject(candidate)) {
|
|
193
|
+
if (isPlainObject(candidate['state_patch'])) {
|
|
194
|
+
return { patch: candidate['state_patch'] };
|
|
195
|
+
}
|
|
196
|
+
if (Object.prototype.hasOwnProperty.call(candidate, 'state_patch')) {
|
|
197
|
+
return { invalid: true };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { absent: true };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Assemble the CJS snippet embedded into every generated hook script:
|
|
205
|
+
* all sibling functions of this module, source-verbatim via
|
|
206
|
+
* `fn.toString()`. The adapters splice this block after their `require`
|
|
207
|
+
* preamble, which keeps the embedded logic byte-identical across hosts
|
|
208
|
+
* and impossible to drift from this module.
|
|
209
|
+
*/
|
|
210
|
+
export function hookRuntimeSnippet() {
|
|
211
|
+
return [
|
|
212
|
+
isPlainObject,
|
|
213
|
+
resolveStatePathForCwd,
|
|
214
|
+
readStateEnvelope,
|
|
215
|
+
saveStateEnvelope,
|
|
216
|
+
mergePatch,
|
|
217
|
+
readResponseText,
|
|
218
|
+
findFencedPatch,
|
|
219
|
+
findRawPatch,
|
|
220
|
+
]
|
|
221
|
+
.map((fn) => fn.toString())
|
|
222
|
+
.join('\n\n');
|
|
223
|
+
}
|
|
224
|
+
//# 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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ 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';
|
|
20
23
|
export * from './schemas/index.js';
|
|
21
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAC9B,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,14 @@ 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';
|
|
28
36
|
export * from './schemas/index.js';
|
|
29
37
|
//# 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,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
* Render a state schema as the shared `## Schema` markdown block used by
|
|
45
|
+
* every prompt formatter and adapter `injectState`. Fields without a
|
|
46
|
+
* description fall back to "no description".
|
|
47
|
+
*/
|
|
48
|
+
export declare function describeSchema(schema: StateSchema): string;
|
|
49
|
+
/** Options for {@link skillMdBody}. */
|
|
50
|
+
export interface SkillMdBodyOptions {
|
|
51
|
+
/** Brand label woven into the hooks intro ("Claude Code", "Codex"). */
|
|
52
|
+
hostLabel: string;
|
|
53
|
+
/**
|
|
54
|
+
* How the injected state reaches the model, as a predicate:
|
|
55
|
+
* "injected into your context via hooks" (claude) /
|
|
56
|
+
* "provided as developer context" (codex).
|
|
57
|
+
*/
|
|
58
|
+
injectionPhrase: string;
|
|
59
|
+
/** The skill spec — name/instructions/version fill the frontmatter. */
|
|
60
|
+
spec: ProceduralSpec;
|
|
61
|
+
/** State path written into the frontmatter and the body (default `./.skillstate/skillstate.json`). */
|
|
62
|
+
statePath?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate the whole SKILL.md document shared by the hook-wiring adapters
|
|
66
|
+
* (claude, codex): identical frontmatter, Execution Context, and Process
|
|
67
|
+
* sections; the ONLY brand-specific parts are `hostLabel` (hooks intro)
|
|
68
|
+
* and `injectionPhrase` (how the state reaches the model). OpenCode keeps
|
|
69
|
+
* its own body — its history is trimmed by the plugin, not by hooks, so
|
|
70
|
+
* its Execution Context and Process genuinely differ.
|
|
71
|
+
*/
|
|
72
|
+
export declare function skillMdBody(options: SkillMdBodyOptions): string;
|
|
73
|
+
//# 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;;;;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,CAmD/D"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two-key response directive (without the fenced example). Adapters
|
|
3
|
+
* prepend their own lead-in ("Based on your current state, provide your
|
|
4
|
+
* response with:" etc.) and append the numbered items via
|
|
5
|
+
* {@link STATE_PATCH_CONTRACT}.
|
|
6
|
+
*/
|
|
7
|
+
export 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:';
|
|
8
|
+
/** Canonical fenced ```json example block for the two-key contract. */
|
|
9
|
+
export const STATE_PATCH_EXAMPLE_JSON = [
|
|
10
|
+
'```json',
|
|
11
|
+
'{',
|
|
12
|
+
' "state_patch": { "key": "new_value", "obsolete_key": null },',
|
|
13
|
+
' "action": "your_action_here"',
|
|
14
|
+
'}',
|
|
15
|
+
'```',
|
|
16
|
+
].join('\n');
|
|
17
|
+
/** Canonical sparse-patch rules: null deletes, omissions leave state unchanged. */
|
|
18
|
+
export 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.';
|
|
19
|
+
/**
|
|
20
|
+
* The reasoning-is-discarded persistence bullet (paper §3.2, §4): any fact
|
|
21
|
+
* that must survive belongs in `state_patch`, never in the conversation.
|
|
22
|
+
*/
|
|
23
|
+
export const REASONING_DISCARDED_NOTE = 'Reasoning is discarded after execution — put anything you need to persist into `state_patch`.';
|
|
24
|
+
/**
|
|
25
|
+
* The full numbered response contract: reasoning is discarded (paper §3.2),
|
|
26
|
+
* the JSON block carries exactly the two keys, and the patch follows the
|
|
27
|
+
* sparse ⊕ semantics. Includes the example block and the rules — no lead-in.
|
|
28
|
+
*/
|
|
29
|
+
export const STATE_PATCH_CONTRACT = [
|
|
30
|
+
'1. Step-by-step reasoning (will be discarded after execution)',
|
|
31
|
+
`2. ${STATE_PATCH_CONTRACT_HEADER}`,
|
|
32
|
+
'',
|
|
33
|
+
STATE_PATCH_EXAMPLE_JSON,
|
|
34
|
+
'',
|
|
35
|
+
STATE_PATCH_RULES,
|
|
36
|
+
].join('\n');
|
|
37
|
+
/**
|
|
38
|
+
* The single history-unreliability hint appended to the additionalContext
|
|
39
|
+
* of every inject-style hook script (claude + codex alike). Names the MCP
|
|
40
|
+
* tools AND the fenced ```json state_patch channel — one text for all
|
|
41
|
+
* hosts, so the Bash-carried patch option can no longer drift away.
|
|
42
|
+
*/
|
|
43
|
+
export 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.';
|
|
44
|
+
/**
|
|
45
|
+
* Render a state schema as the shared `## Schema` markdown block used by
|
|
46
|
+
* every prompt formatter and adapter `injectState`. Fields without a
|
|
47
|
+
* description fall back to "no description".
|
|
48
|
+
*/
|
|
49
|
+
export function describeSchema(schema) {
|
|
50
|
+
const fields = Object.entries(schema)
|
|
51
|
+
.map(([name, field]) => `- ${name} (${field.type}): ${field.description ?? 'no description'}`)
|
|
52
|
+
.join('\n');
|
|
53
|
+
return `## Schema\n${fields}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate the whole SKILL.md document shared by the hook-wiring adapters
|
|
57
|
+
* (claude, codex): identical frontmatter, Execution Context, and Process
|
|
58
|
+
* sections; the ONLY brand-specific parts are `hostLabel` (hooks intro)
|
|
59
|
+
* and `injectionPhrase` (how the state reaches the model). OpenCode keeps
|
|
60
|
+
* its own body — its history is trimmed by the plugin, not by hooks, so
|
|
61
|
+
* its Execution Context and Process genuinely differ.
|
|
62
|
+
*/
|
|
63
|
+
export function skillMdBody(options) {
|
|
64
|
+
const resolvedStatePath = options.statePath ?? './.skillstate/skillstate.json';
|
|
65
|
+
return [
|
|
66
|
+
'---',
|
|
67
|
+
`name: ${JSON.stringify(options.spec.name)}`,
|
|
68
|
+
`description: ${JSON.stringify(options.spec.instructions)}`,
|
|
69
|
+
`version: ${options.spec.version}`,
|
|
70
|
+
'execution_context:',
|
|
71
|
+
` state_path: ${resolvedStatePath}`,
|
|
72
|
+
' format: json',
|
|
73
|
+
'---',
|
|
74
|
+
'',
|
|
75
|
+
`# ${options.spec.name}`,
|
|
76
|
+
'',
|
|
77
|
+
options.spec.instructions,
|
|
78
|
+
'',
|
|
79
|
+
'## Execution Context',
|
|
80
|
+
'',
|
|
81
|
+
`Your execution state lives at \`${resolvedStatePath}\` (per project; a`,
|
|
82
|
+
'session started in $HOME uses `~/.skillstate/global/skillstate.json`).',
|
|
83
|
+
`The skillstate ${options.hostLabel} hooks:`,
|
|
84
|
+
'',
|
|
85
|
+
`- the CURRENT state is ${options.injectionPhrase} on every prompt submit`,
|
|
86
|
+
' (`UserPromptSubmit`) and is re-injected after compaction',
|
|
87
|
+
' (`SessionStart` matcher `^compact$`);',
|
|
88
|
+
'- watch every Bash tool result and merge a fenced ```json block carrying',
|
|
89
|
+
' a `state_patch` into the state file (`PostToolUse`).',
|
|
90
|
+
'',
|
|
91
|
+
'The injected state is authoritative — history is not reliable. Never',
|
|
92
|
+
'reconstruct execution context from the conversation.',
|
|
93
|
+
'',
|
|
94
|
+
'## Process',
|
|
95
|
+
'',
|
|
96
|
+
'1. Orient yourself: read the injected state, or call the skillstate MCP',
|
|
97
|
+
' tools `state.summary` (compact) / `state.get` (full dump).',
|
|
98
|
+
'2. Observe the result of your last action.',
|
|
99
|
+
'3. Reason about what to do next, given the state and the observation.',
|
|
100
|
+
'4. Persist progress with the skillstate MCP tool `state.patch` (sparse',
|
|
101
|
+
' patch; set a key to `null` to delete it; dry-run first with',
|
|
102
|
+
' `state.validate` when unsure; `state.diff` shows what changed since',
|
|
103
|
+
' your last look), and/or emit a fenced JSON block with exactly two',
|
|
104
|
+
' keys inside a Bash tool call so the `PostToolUse` hook merges it:',
|
|
105
|
+
'',
|
|
106
|
+
STATE_PATCH_EXAMPLE_JSON,
|
|
107
|
+
'',
|
|
108
|
+
`- ${STATE_PATCH_RULES}`,
|
|
109
|
+
'- Put anything you need to survive into `state_patch`; never rely on',
|
|
110
|
+
' the conversation remembering it.',
|
|
111
|
+
'- `action` names what you will do next (e.g. "continue", "done").',
|
|
112
|
+
'',
|
|
113
|
+
].join('\n');
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=prompt-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-contract.js","sourceRoot":"","sources":["../src/prompt-contract.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GACtC,iHAAiH,CAAC;AAEpH,uEAAuE;AACvE,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS;IACT,GAAG;IACH,gEAAgE;IAChE,gCAAgC;IAChC,GAAG;IACH,KAAK;CACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,mFAAmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAC5B,iIAAiI,CAAC;AAEpI;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,+FAA+F,CAAC;AAElG;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,+DAA+D;IAC/D,MAAM,2BAA2B,EAAE;IACnC,EAAE;IACF,wBAAwB;IACxB,EAAE;IACF,iBAAiB;CAClB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,wJAAwJ,CAAC;AAE3J;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SAClC,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;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,cAAc,MAAM,EAAE,CAAC;AAChC,CAAC;AAkBD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,iBAAiB,GAAG,OAAO,CAAC,SAAS,IAAI,+BAA+B,CAAC;IAC/E,OAAO;QACL,KAAK;QACL,SAAS,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5C,gBAAgB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE;QAC3D,YAAY,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE;QAClC,oBAAoB;QACpB,iBAAiB,iBAAiB,EAAE;QACpC,gBAAgB;QAChB,KAAK;QACL,EAAE;QACF,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;QACxB,EAAE;QACF,OAAO,CAAC,IAAI,CAAC,YAAY;QACzB,EAAE;QACF,sBAAsB;QACtB,EAAE;QACF,mCAAmC,iBAAiB,oBAAoB;QACxE,wEAAwE;QACxE,kBAAkB,OAAO,CAAC,SAAS,SAAS;QAC5C,EAAE;QACF,0BAA0B,OAAO,CAAC,eAAe,yBAAyB;QAC1E,4DAA4D;QAC5D,yCAAyC;QACzC,0EAA0E;QAC1E,wDAAwD;QACxD,EAAE;QACF,sEAAsE;QACtE,sDAAsD;QACtD,EAAE;QACF,YAAY;QACZ,EAAE;QACF,yEAAyE;QACzE,+DAA+D;QAC/D,4CAA4C;QAC5C,uEAAuE;QACvE,wEAAwE;QACxE,gEAAgE;QAChE,wEAAwE;QACxE,sEAAsE;QACtE,sEAAsE;QACtE,EAAE;QACF,wBAAwB;QACxB,EAAE;QACF,KAAK,iBAAiB,EAAE;QACxB,sEAAsE;QACtE,oCAAoC;QACpC,mEAAmE;QACnE,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -98,9 +98,5 @@ export declare class PromptTransformer {
|
|
|
98
98
|
serializeState(state: SkillState, options?: {
|
|
99
99
|
pretty?: boolean;
|
|
100
100
|
}, schema?: StateSchema): string;
|
|
101
|
-
/**
|
|
102
|
-
* Describe the schema fields for inclusion in prompts.
|
|
103
|
-
*/
|
|
104
|
-
private describeSchema;
|
|
105
101
|
}
|
|
106
102
|
//# sourceMappingURL=prompt-transformer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-transformer.d.ts","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"prompt-transformer.d.ts","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,gBAAgB,GAChB,qBAAqB,GACrB,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAMN;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAoC;IAEpD,YAAY,OAAO,CAAC,EAAE,wBAAwB,EAE7C;IAED;;;;;OAKG;IACH,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAC3C,MAAM,CASR;IAED;;;;;OAKG;IACH,eAAe,CACb,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAyBR;IAED;;;;OAIG;IACH,iBAAiB,CACf,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAkBR;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAmBR;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IA2BrB;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGrD;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG7C;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CA2CnD;IAMD;;;;;OAKG;IACH,cAAc,CACZ,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,MAAM,CAcR;CACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STATE_PATCH_CONTRACT, describeSchema } from './prompt-contract.js';
|
|
1
2
|
function isPlainObject(value) {
|
|
2
3
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
4
|
}
|
|
@@ -38,7 +39,7 @@ export class PromptTransformer {
|
|
|
38
39
|
*/
|
|
39
40
|
formatForClaude(spec, state, observation) {
|
|
40
41
|
const stateJson = this.serializeState(state, undefined, spec.schema);
|
|
41
|
-
const schemaDesc =
|
|
42
|
+
const schemaDesc = describeSchema(spec.schema);
|
|
42
43
|
return `# System
|
|
43
44
|
|
|
44
45
|
You are ${spec.name}. ${spec.instructions}
|
|
@@ -59,17 +60,7 @@ ${observation.content}
|
|
|
59
60
|
|
|
60
61
|
Based on the observation and your current state, provide your response with:
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
2. A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
|
|
64
|
-
|
|
65
|
-
\`\`\`json
|
|
66
|
-
{
|
|
67
|
-
"state_patch": { "key": "new_value", "obsolete_key": null },
|
|
68
|
-
"action": "your_action_here"
|
|
69
|
-
}
|
|
70
|
-
\`\`\`
|
|
71
|
-
|
|
72
|
-
In \`state_patch\`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.`;
|
|
63
|
+
${STATE_PATCH_CONTRACT}`;
|
|
73
64
|
}
|
|
74
65
|
/**
|
|
75
66
|
* OpenCode-specific prompt format adapted for the opencode skill system.
|
|
@@ -78,7 +69,7 @@ In \`state_patch\`, set keys to null to delete them. Only include fields you wan
|
|
|
78
69
|
*/
|
|
79
70
|
formatForOpenCode(spec, state, observation) {
|
|
80
71
|
const stateJson = this.serializeState(state, undefined, spec.schema);
|
|
81
|
-
const schemaDesc =
|
|
72
|
+
const schemaDesc = describeSchema(spec.schema);
|
|
82
73
|
return `<skill name="${spec.id}">
|
|
83
74
|
<instructions>${spec.instructions}</instructions>
|
|
84
75
|
${schemaDesc}
|
|
@@ -90,16 +81,9 @@ ${observation.content}
|
|
|
90
81
|
</observation>
|
|
91
82
|
</skill>
|
|
92
83
|
|
|
93
|
-
Respond with
|
|
94
|
-
|
|
95
|
-
\`\`\`json
|
|
96
|
-
{
|
|
97
|
-
"state_patch": { "key": "new_value", "obsolete_key": null },
|
|
98
|
-
"action": "action_name"
|
|
99
|
-
}
|
|
100
|
-
\`\`\`
|
|
84
|
+
Respond with:
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
${STATE_PATCH_CONTRACT}`;
|
|
103
87
|
}
|
|
104
88
|
/**
|
|
105
89
|
* Paper-exact prompt format — byte-verbatim Appendix A.4.
|
|
@@ -138,7 +122,7 @@ Provide your response with:
|
|
|
138
122
|
*/
|
|
139
123
|
formatGeneric(spec, state, observation) {
|
|
140
124
|
const stateJson = this.serializeState(state, undefined, spec.schema);
|
|
141
|
-
const schemaDesc =
|
|
125
|
+
const schemaDesc = describeSchema(spec.schema);
|
|
142
126
|
return `${spec.instructions}
|
|
143
127
|
|
|
144
128
|
${schemaDesc}
|
|
@@ -155,17 +139,7 @@ ${observation.content}
|
|
|
155
139
|
|
|
156
140
|
Provide your response with:
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
2. A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
|
|
160
|
-
|
|
161
|
-
\`\`\`json
|
|
162
|
-
{
|
|
163
|
-
"state_patch": { "key": "value", "obsolete_key": null },
|
|
164
|
-
"action": "action_name"
|
|
165
|
-
}
|
|
166
|
-
\`\`\`
|
|
167
|
-
|
|
168
|
-
In \`state_patch\`, set keys to null to delete them.`;
|
|
142
|
+
${STATE_PATCH_CONTRACT}`;
|
|
169
143
|
}
|
|
170
144
|
/**
|
|
171
145
|
* Extract the state_patch from an LLM response containing a fenced JSON block.
|
|
@@ -252,14 +226,5 @@ In \`state_patch\`, set keys to null to delete them.`;
|
|
|
252
226
|
}
|
|
253
227
|
return JSON.stringify(toSerialize);
|
|
254
228
|
}
|
|
255
|
-
/**
|
|
256
|
-
* Describe the schema fields for inclusion in prompts.
|
|
257
|
-
*/
|
|
258
|
-
describeSchema(schema) {
|
|
259
|
-
const fields = Object.entries(schema)
|
|
260
|
-
.map(([name, field]) => `- ${name} (${field.type}): ${field.description ?? 'no description'}`)
|
|
261
|
-
.join('\n');
|
|
262
|
-
return `## Schema\n${fields}`;
|
|
263
|
-
}
|
|
264
229
|
}
|
|
265
230
|
//# sourceMappingURL=prompt-transformer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-transformer.js","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompt-transformer.js","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA+B5E,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;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IACpB,QAAQ,CAAoC;IAEpD,YAAY,OAAkC;QAC5C,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,SAAS,CAAC;IACjD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CACV,IAAoB,EACpB,KAAiB,EACjB,WAAwB,EACxB,QAA4C;QAE5C,MAAM,CAAC,GAAG,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC;QACpC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACH,eAAe,CACb,IAAoB,EACpB,KAAiB,EACjB,WAAwB;QAExB,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE/C,OAAO;;UAED,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY;;EAEvC,UAAU;;;;;EAKV,SAAS;;;;;EAKT,WAAW,CAAC,OAAO;;;;;;EAMnB,oBAAoB,EAAE,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CACf,IAAoB,EACpB,KAAiB,EACjB,WAAwB;QAExB,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE/C,OAAO,gBAAgB,IAAI,CAAC,EAAE;gBAClB,IAAI,CAAC,YAAY;EAC/B,UAAU;;EAEV,SAAS;;;EAGT,WAAW,CAAC,OAAO;;;;;;EAMnB,oBAAoB,EAAE,CAAC;IACvB,CAAC;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,IAAoB,EACpB,KAAiB,EACjB,WAAwB;QAExB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAExC,OAAO;;EAET,IAAI,CAAC,YAAY;;;;;EAKjB,SAAS;;sBAEW,WAAW,CAAC,OAAO;;;;;;oRAM2O,CAAC;IACnR,CAAC;IAED;;;;;OAKG;IACK,aAAa,CACnB,IAAoB,EACpB,KAAiB,EACjB,WAAwB;QAExB,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE/C,OAAO,GAAG,IAAI,CAAC,YAAY;;EAE7B,UAAU;;;;EAIV,SAAS;;;;EAIT,WAAW,CAAC,OAAO;;;;;;EAMnB,oBAAoB,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,QAAgB;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAgB;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,QAAgB;QAC5B,iCAAiC;QACjC,EAAE;QACF,mEAAmE;QACnE,sEAAsE;QACtE,kEAAkE;QAClE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QAC3C,CAAC;QAED,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gEAAgE;YAChE,uCAAuC;YACvC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,gBAAgB;gBACxB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC;aACtB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,4DAA4D;YAC5D,4BAA4B;YAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QACtD,CAAC;QAED,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAEnD,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QACtD,CAAC;QAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QACjD,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAwB,EAAE,MAAM,EAAE,CAAC;IAC/D,CAAC;IAED,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IAExE;;;;;OAKG;IACH,cAAc,CACZ,KAAiB,EACjB,OAA8B,EAC9B,MAAoB;QAEpB,IAAI,WAAW,GAAe,KAAK,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,WAAW,GAAG,EAAE,CAAC;YACjB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrC,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;oBAClB,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../src/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../src/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAC9B,UAAU,CAWZ;AAkBD,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAE3E;AAyBD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,GAChB,gBAAgB,CAkBlB;AAMD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,MAAM,CAKR;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAEzD;AAMD,qBAAa,YAAY;IACvB,MAAM,CAAC,kBAAkB,4BAAsB;IAC/C,MAAM,CAAC,UAAU,oBAAc;IAC/B,MAAM,CAAC,aAAa,uBAAiB;IACrC,MAAM,CAAC,cAAc,wBAAkB;IACvC,MAAM,CAAC,gBAAgB,0BAAoB;CAC5C;AAMD,wBAAgB,kBAAkB;;;;;;EAQjC"}
|
package/dist/state-manager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergePatch } from './hook-runtime.js';
|
|
1
2
|
// ---------------------------------------------------------------------------
|
|
2
3
|
// 1. createInitialState — Σ₀ from schema defaults + optional overrides
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
@@ -19,41 +20,16 @@ export function createInitialState(schema, overrides) {
|
|
|
19
20
|
// - null values DELETE the key entirely
|
|
20
21
|
// - Nested dicts are merged recursively
|
|
21
22
|
// - Original state is NOT mutated
|
|
23
|
+
//
|
|
24
|
+
// Delegates to the hook-runtime {@link mergePatch} — the single ⊕
|
|
25
|
+
// implementation shared with the generated hook scripts and the
|
|
26
|
+
// OpenCode plugin (one merge semantics everywhere).
|
|
22
27
|
// ---------------------------------------------------------------------------
|
|
23
28
|
function isPlainObject(value) {
|
|
24
29
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
30
|
}
|
|
26
31
|
export function mergeState(state, patch) {
|
|
27
|
-
|
|
28
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
29
|
-
if (value === null) {
|
|
30
|
-
delete result[key];
|
|
31
|
-
}
|
|
32
|
-
else if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
33
|
-
// Recursive merge for nested objects — apply null-deletion inside too
|
|
34
|
-
result[key] = mergeNestedObjects(result[key], value);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
result[key] = value;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
/** Deep merge two plain objects with null-deletion semantics. */
|
|
43
|
-
function mergeNestedObjects(base, patch) {
|
|
44
|
-
const result = { ...base };
|
|
45
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
46
|
-
if (value === null) {
|
|
47
|
-
delete result[key];
|
|
48
|
-
}
|
|
49
|
-
else if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
50
|
-
result[key] = mergeNestedObjects(result[key], value);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
result[key] = value;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return result;
|
|
32
|
+
return mergePatch(state, patch);
|
|
57
33
|
}
|
|
58
34
|
// ---------------------------------------------------------------------------
|
|
59
35
|
// 3. validatePatch — reject unknown keys, wrong types; accept null for deletion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../src/state-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../src/state-manager.ts"],"names":[],"mappings":"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"}
|