@skillstate/claude 2.0.6 → 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/README.md +88 -56
- package/dist/claude-adapter.d.ts +143 -51
- package/dist/claude-adapter.d.ts.map +1 -1
- package/dist/claude-adapter.js +396 -259
- package/dist/claude-adapter.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# @skillstate/claude
|
|
4
4
|
|
|
5
|
-
**Claude Code platform adapter for the @skillstate/core runtime — state injection and persistence via hook scripts.**
|
|
5
|
+
**Claude Code platform adapter for the @skillstate/core runtime — state injection and patch persistence via hook scripts (2.1.260 hooks contract).**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@skillstate/claude)
|
|
8
8
|
[](https://www.npmjs.com/package/@skillstate/claude)
|
|
9
|
-
[](https://github.com/vitalykuzyaev/skillstate)
|
|
10
9
|
[](https://github.com/vitalykuzyaev/skillstate/blob/main/LICENSE)
|
|
11
10
|
|
|
12
11
|
</div>
|
|
@@ -15,16 +14,40 @@
|
|
|
15
14
|
|
|
16
15
|
`@skillstate/claude` bridges the paper-exact core ([`@skillstate/core`](../core))
|
|
17
16
|
into **Claude Code** sessions. It emits self-contained CommonJS hook scripts
|
|
18
|
-
(run via `node script.cjs`) that inject the persisted skill state into
|
|
19
|
-
|
|
17
|
+
(run via `node script.cjs`) that inject the persisted skill state into every
|
|
18
|
+
turn, re-inject it after compaction, and merge a validated `state_patch` from
|
|
19
|
+
Bash tool responses back into the state file.
|
|
20
20
|
|
|
21
21
|
> **@non-paper** — no adapters exist in arXiv 2608.26263v3. This adapter is an
|
|
22
22
|
> additive integration, not part of the paper.
|
|
23
23
|
|
|
24
|
+
## The honest architecture (Claude Code 2.1.260)
|
|
25
|
+
|
|
26
|
+
**History trimming from hooks is impossible.** The compaction-time hook cannot
|
|
27
|
+
inject context (its only decision is `decision: "block"` — forbid compaction),
|
|
28
|
+
and the post-compaction hook has no decision control at all (its
|
|
29
|
+
`systemMessage` is discarded). So this adapter implements the state-injection
|
|
30
|
+
model, not a trim model:
|
|
31
|
+
|
|
32
|
+
| Event | Matcher | What the script does |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `UserPromptSubmit` | — (no matcher support) | Injects `Current skill state (JSON): {...}` as `additionalContext` — the model gets the authoritative state at every turn. |
|
|
35
|
+
| `SessionStart` | `^compact$` | Re-injects the state right after auto/manual compaction — state SURVIVES compaction even though history was compressed. |
|
|
36
|
+
| `PostToolUse` | `^Bash$` | Extracts a fenced ```json `state_patch` from the tool response (or a raw JSON / object `tool_response.state_patch`), applies the paper's ⊕ null-deletion merge, and writes `{ version: 1, state }`. stdout is `{}` or a `systemMessage` when the patch is invalid. |
|
|
37
|
+
|
|
38
|
+
Reading and writing the full state is available at any time through the
|
|
39
|
+
skillstate MCP server (`state.get` / `state.patch` — schema-validated) that
|
|
40
|
+
`skillstate init` registers in the project's `.mcp.json`.
|
|
41
|
+
|
|
42
|
+
Prompts stay O(T) with a fresh state at every turn. **True O(1) requires
|
|
43
|
+
host-side trimming, which Claude Code does not expose.**
|
|
44
|
+
|
|
24
45
|
## Installation
|
|
25
46
|
|
|
26
47
|
```bash
|
|
27
48
|
npm i @skillstate/core @skillstate/claude
|
|
49
|
+
# or wire everything into a live host at once:
|
|
50
|
+
skillstate init # writes scripts, merges ~/.claude/settings.json, adds .mcp.json + SKILL.md
|
|
28
51
|
```
|
|
29
52
|
|
|
30
53
|
Requires Node.js >= 20. TypeScript types are bundled.
|
|
@@ -37,66 +60,75 @@ import { INTERCODE_CTF_SPEC } from '@skillstate/core/schemas';
|
|
|
37
60
|
|
|
38
61
|
const adapter = new ClaudeAdapter();
|
|
39
62
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
const
|
|
50
|
-
'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
63
|
+
// Self-contained hook scripts (Node builtins only). Each resolves the
|
|
64
|
+
// per-project state from input.cwd at runtime — one global script dir
|
|
65
|
+
// serves every project:
|
|
66
|
+
const inject = adapter.generateHookScript('user-prompt-submit');
|
|
67
|
+
const survive = adapter.generateHookScript('session-start-compact');
|
|
68
|
+
const persist = adapter.generateHookScript('post-tool-use');
|
|
69
|
+
|
|
70
|
+
// The hooks section for ~/.claude/settings.json (2.1.260 schema:
|
|
71
|
+
// { hooks: { Event: [ { matcher?, hooks: [ { type: "command", command, timeout } ] } ] } }):
|
|
72
|
+
const hooksJson = adapter.generateHooksConfig('./.skillstate/skillstate.json', {
|
|
73
|
+
scriptDir: '/home/me/.claude/hooks/skillstate',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Merge into a live settings.json — preserves env/permissions/model and
|
|
77
|
+
// every foreign hook; byte-identical (no-op) when already wired:
|
|
78
|
+
const merged = adapter.mergeHooksConfig(existingSettingsText, {
|
|
79
|
+
scriptDir: '/home/me/.claude/hooks/skillstate',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Uninstall surgery: remove exactly the skillstate groups/handlers from a
|
|
83
|
+
// live settings.json (mixed groups keep their foreign handlers):
|
|
84
|
+
const { text, changed } = removeSkillstateHookGroups(liveSettingsText);
|
|
85
|
+
|
|
86
|
+
// Atomic persistence helpers (tmp + fsync + rename):
|
|
87
|
+
await adapter.saveHookScript('post-tool-use', '/h/skillstate/post-tool-use.cjs');
|
|
88
|
+
await adapter.saveSkillMd('/home/me/.claude/skills/skillstate/SKILL.md', spec);
|
|
67
89
|
```
|
|
68
90
|
|
|
69
91
|
## API / Exports
|
|
70
92
|
|
|
71
|
-
Root path `@skillstate/claude` exports
|
|
72
|
-
|
|
73
|
-
- `new ClaudeAdapter()` — implements `PlatformAdapter` (`name = 'claude'`)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
Root path `@skillstate/claude` exports:
|
|
94
|
+
|
|
95
|
+
- `new ClaudeAdapter()` — implements `PlatformAdapter` (`name = 'claude'`):
|
|
96
|
+
`injectState`, `extractPatch`, `extractAction`, `formatPrompt`.
|
|
97
|
+
- `generateHookScript(event, statePath?): string` — `event` is
|
|
98
|
+
`'user-prompt-submit' | 'session-start-compact' | 'post-tool-use'`.
|
|
99
|
+
Accepts a raw path or a `{ root, name }` ref confined by
|
|
100
|
+
`resolveStatePath` (`..` escapes throw). The path only documents the
|
|
101
|
+
header — the script resolves the state from the session cwd.
|
|
102
|
+
- `generateHooksConfig(statePath?, options?): string` — JSON with ONLY the
|
|
103
|
+
`hooks` section. Options: `scriptDir`, `command`, `timeoutSeconds`
|
|
104
|
+
(default 30).
|
|
105
|
+
- `mergeHooksConfig(existingJson, options?): string` — idempotent merge into
|
|
106
|
+
a live settings.json; preserves every other key.
|
|
107
|
+
- `removeSkillstateHookGroups(existingJson): { text, changed }` — surgical
|
|
108
|
+
uninstall (pure groups dropped, mixed groups trimmed, empty events
|
|
109
|
+
removed).
|
|
110
|
+
- `claudeHookScriptPath(scriptDir, event): string`.
|
|
111
|
+
- `generateSkillMd(spec, statePath?): string` + `saveSkillMd`,
|
|
112
|
+
`saveHookScript(event, target, statePath?)`, `saveHooksConfig(target,
|
|
113
|
+
statePath?, options?)` — atomic writes.
|
|
82
114
|
- `generateAppendPrompt(): string` — mode-switch prompt boilerplate.
|
|
83
|
-
- `
|
|
84
|
-
|
|
85
|
-
- `
|
|
86
|
-
|
|
115
|
+
- `CLAUDE_HOOK_EVENTS`, `CLAUDE_SESSION_START_MATCHER` (`^compact$`),
|
|
116
|
+
`CLAUDE_POST_TOOL_USE_MATCHER` (`^Bash$`), `CLAUDE_HOOK_TIMEOUT_SECONDS`.
|
|
117
|
+
- `resolveStateForCwd(cwd, home?)` — re-export of the core
|
|
118
|
+
`resolveHostStateForCwd` (single source of truth for the per-project
|
|
119
|
+
resolver).
|
|
87
120
|
|
|
88
121
|
## Notes
|
|
89
122
|
|
|
90
|
-
- **Honest limitation.** Claude Code hooks
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
(`mergeState`), `PromptTransformer`, `atomicWriteFile`, and `resolveStatePath`.
|
|
123
|
+
- **Honest limitation.** Claude Code hooks cannot trim history and cannot
|
|
124
|
+
inject context at compaction time, so prompts are O(T) with fresh state
|
|
125
|
+
per turn. The injected state is authoritative — history is not.
|
|
126
|
+
- The generated scripts are self-contained CommonJS. Malformed patches are
|
|
127
|
+
never persisted — the ⊕ merge either fully applies or the hook reports a
|
|
128
|
+
`systemMessage` and leaves the state file untouched.
|
|
129
|
+
- Depends on [`@skillstate/core`](../core) for `PromptTransformer`,
|
|
130
|
+
`atomicWriteFile`, `resolveStatePath`, and the shared
|
|
131
|
+
`resolveHostStateForCwd`.
|
|
100
132
|
|
|
101
133
|
## Related
|
|
102
134
|
|
package/dist/claude-adapter.d.ts
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @non-paper adapter — no adapters exist in arXiv 2608.26263v3.
|
|
3
|
-
*
|
|
4
|
-
* This file bridges the paper-exact core (Algorithm 1 prompt, ⊕ merge,
|
|
5
|
-
* §7 rollback-retry rejection) into Claude Code sessions via hook scripts
|
|
6
|
-
* and prompt boilerplate.
|
|
7
|
-
*
|
|
8
|
-
* LIMITATION: Claude Code hooks are append-only — history cannot be trimmed
|
|
9
|
-
* from hooks. The best strategy is: PreCompact injects state into the
|
|
10
|
-
* compaction summary, SessionStart(source:compact) re-injects state after
|
|
11
|
-
* compaction. True O(1) is not possible without host-side trimming.
|
|
12
|
-
*/
|
|
13
1
|
import type { SkillState, StatePatch, ProceduralSpec, Observation, PlatformAdapter } from '@skillstate/core';
|
|
14
2
|
import type { StatePathRef } from '@skillstate/core';
|
|
3
|
+
export { resolveHostStateForCwd as resolveStateForCwd } from '@skillstate/core';
|
|
4
|
+
/** Claude Code hook events this adapter generates scripts for (script/CLI names). */
|
|
5
|
+
export type ClaudeHookEvent = 'user-prompt-submit' | 'session-start-compact' | 'post-tool-use';
|
|
6
|
+
/** All {@link ClaudeHookEvent} values in generation order. */
|
|
7
|
+
export declare const CLAUDE_HOOK_EVENTS: readonly ["user-prompt-submit", "session-start-compact", "post-tool-use"];
|
|
8
|
+
/** SessionStart matcher that fires after Claude Code compacts the conversation. */
|
|
9
|
+
export declare const CLAUDE_SESSION_START_MATCHER = "^compact$";
|
|
10
|
+
/** PostToolUse matcher restricted to Bash tool results. */
|
|
11
|
+
export declare const CLAUDE_POST_TOOL_USE_MATCHER = "^Bash$";
|
|
12
|
+
/**
|
|
13
|
+
* Hook `timeout` in seconds written into every generated hook entry — the
|
|
14
|
+
* UserPromptSubmit default, spelled out so the budget is explicit.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CLAUDE_HOOK_TIMEOUT_SECONDS = 30;
|
|
17
|
+
/** Options for {@link ClaudeAdapter.generateHooksConfig} / merge. */
|
|
18
|
+
export interface ClaudeHooksConfigOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Directory holding the generated `.cjs` hook scripts. Defaults to the
|
|
21
|
+
* state file's directory when a `statePath` is given, else a
|
|
22
|
+
* `<stateDir>/hooks` placeholder; the CLI install passes
|
|
23
|
+
* `~/.claude/hooks/skillstate`.
|
|
24
|
+
*/
|
|
25
|
+
scriptDir?: string;
|
|
26
|
+
/** Full command override for every event (defaults to `node <script> <event>`). */
|
|
27
|
+
command?: string;
|
|
28
|
+
/** Hook `timeout` in seconds (default {@link CLAUDE_HOOK_TIMEOUT_SECONDS}). */
|
|
29
|
+
timeoutSeconds?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Claude Code platform adapter (@non-paper; see module doc for the 2.1.260
|
|
33
|
+
* hooks contract and the honest O(T) limitation).
|
|
34
|
+
*/
|
|
15
35
|
export declare class ClaudeAdapter implements PlatformAdapter {
|
|
16
36
|
readonly name = "claude";
|
|
17
37
|
private transformer;
|
|
@@ -19,59 +39,131 @@ export declare class ClaudeAdapter implements PlatformAdapter {
|
|
|
19
39
|
extractPatch(response: string): StatePatch | null;
|
|
20
40
|
extractAction(response: string): string | null;
|
|
21
41
|
/**
|
|
22
|
-
* @non-paper
|
|
42
|
+
* @non-paper adapter convenience (delegates to the transformer).
|
|
23
43
|
* Paper-exact callers use `PromptTransformer.formatPaper` (Appendix A.4).
|
|
24
44
|
*/
|
|
25
45
|
formatPrompt(state: SkillState, observation: Observation, spec: ProceduralSpec): string;
|
|
26
|
-
generateHookScript(eventType: 'PreToolUse' | 'PostToolUse', statePath: string, schema?: ProceduralSpec['schema']): string;
|
|
27
46
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
47
|
+
* Canonical absolute path of the hook script for `event` inside
|
|
48
|
+
* `scriptDir` (e.g. `~/.claude/hooks/skillstate/user-prompt-submit.cjs`).
|
|
49
|
+
* {@link generateHooksConfig} and {@link saveHookScript} share this
|
|
50
|
+
* convention so the settings.json commands and the on-disk scripts agree.
|
|
32
51
|
*/
|
|
33
|
-
|
|
52
|
+
claudeHookScriptPath(scriptDir: string, event: ClaudeHookEvent): string;
|
|
34
53
|
/**
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
54
|
+
* Generate the hooks section for `~/.claude/settings.json` (2.1.260
|
|
55
|
+
* schema: `{ hooks: { Event: [ { matcher?, hooks: [ { type: "command",
|
|
56
|
+
* command, timeout } ] } ] } }`). The document carries ONLY the
|
|
57
|
+
* `hooks` key — {@link mergeHooksConfig} is what splices these groups
|
|
58
|
+
* into a live settings.json while preserving every other key:
|
|
59
|
+
*
|
|
60
|
+
* - `UserPromptSubmit` → inject the current state as additionalContext;
|
|
61
|
+
* - `SessionStart` (matcher `^compact$`) → re-inject after compaction;
|
|
62
|
+
* - `PostToolUse` (matcher `^Bash$`) → persist `state_patch` blocks from
|
|
63
|
+
* Bash tool outputs.
|
|
64
|
+
*
|
|
65
|
+
* Commands are absolute `node <script> <event>` lines pointing at the
|
|
66
|
+
* generated `.cjs` scripts in `options.scriptDir` (default: the state
|
|
67
|
+
* file's directory, else a `<stateDir>/hooks` placeholder). No matcher
|
|
68
|
+
* on UserPromptSubmit — the event has no matcher support and fires on
|
|
69
|
+
* every prompt.
|
|
40
70
|
*/
|
|
41
|
-
|
|
71
|
+
generateHooksConfig(statePath?: string | StatePathRef, options?: ClaudeHooksConfigOptions): string;
|
|
42
72
|
/**
|
|
43
|
-
* Generate a
|
|
44
|
-
*
|
|
45
|
-
*
|
|
73
|
+
* Generate a self-contained CommonJS hook script for a Claude Code
|
|
74
|
+
* lifecycle event. The script reads ONE hook JSON document from stdin,
|
|
75
|
+
* resolves the state file from `input.cwd` (the session cwd) via the
|
|
76
|
+
* {@link resolveStateForCwd} semantics, and:
|
|
77
|
+
*
|
|
78
|
+
* - `user-prompt-submit`: emits
|
|
79
|
+
* `{ hookSpecificOutput: { hookEventName: "UserPromptSubmit",
|
|
80
|
+
* additionalContext } }` carrying the current state JSON;
|
|
81
|
+
* - `session-start-compact`: the same injection with
|
|
82
|
+
* `hookEventName: "SessionStart"` (state survives compaction; wire
|
|
83
|
+
* with the `^compact$` matcher);
|
|
84
|
+
* - `post-tool-use`: extracts a `state_patch` from the tool_response
|
|
85
|
+
* (fenced ```json block or raw JSON), applies the ⊕ null-deletion merge
|
|
86
|
+
* and writes the state file; stdout is `{}` or a `systemMessage` when
|
|
87
|
+
* the patch is invalid.
|
|
46
88
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* and
|
|
50
|
-
* injection it saves the current state as the new compact snapshot.
|
|
89
|
+
* `statePath` is accepted for `{ root, name }` confinement (traversal
|
|
90
|
+
* refs throw) and documented in the script header; the content itself is
|
|
91
|
+
* cwd-resolving and never bakes an absolute state path in.
|
|
51
92
|
*/
|
|
52
|
-
|
|
53
|
-
generateCompactHookScript(stateRef: StatePathRef, schema?: ProceduralSpec['schema']): string;
|
|
93
|
+
generateHookScript(event: ClaudeHookEvent, statePath?: string | StatePathRef): string;
|
|
54
94
|
/**
|
|
55
|
-
* Generate a
|
|
56
|
-
*
|
|
57
|
-
*
|
|
95
|
+
* Generate a SKILL.md for Claude Code's skill directory
|
|
96
|
+
* (`~/.claude/skills/<name>/SKILL.md`). The body instructs the agent to
|
|
97
|
+
* treat the hook-injected state as authoritative (history is not
|
|
98
|
+
* reliable), to orient via `state.summary` (full dump via `state.get`),
|
|
99
|
+
* and to persist via `state.patch` — the PostToolUse hook also merges any
|
|
100
|
+
* fenced ```json `state_patch` block printed by a Bash tool call.
|
|
58
101
|
*/
|
|
59
|
-
|
|
60
|
-
generateSessionStartHookScript(stateRef: StatePathRef): string;
|
|
102
|
+
generateSkillMd(spec: ProceduralSpec, statePath?: string): string;
|
|
61
103
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
104
|
+
* Merge the skillstate hook groups into an existing `settings.json`
|
|
105
|
+
* text. Preserves every other top-level key (env, permissions, model,
|
|
106
|
+
* …) and every existing (non-skillstate) hook. Idempotent: if any
|
|
107
|
+
* skillstate command is already wired, the ORIGINAL text is returned
|
|
108
|
+
* byte-unchanged. Malformed/empty input starts from a fresh hooks-only
|
|
109
|
+
* document (the CLI guards a live settings.json before calling).
|
|
110
|
+
*
|
|
111
|
+
* The mechanics live in the shared {@link mergeHookGroups}; this method
|
|
112
|
+
* supplies only the format specifics (generated settings.json groups and
|
|
113
|
+
* the `node <script> <event>` skillstate commands).
|
|
114
|
+
*/
|
|
115
|
+
mergeHooksConfig(existingJson: string, options?: ClaudeHooksConfigOptions): string;
|
|
116
|
+
/**
|
|
117
|
+
* Generate a hook script and persist it via the shared `saveGenerated`
|
|
118
|
+
* (atomic temp-rename write). `target` is the script destination (usually
|
|
119
|
+
* {@link ClaudeAdapter.claudeHookScriptPath}); `statePath` is forwarded
|
|
120
|
+
* to {@link generateHookScript}. Returns the absolute destination path.
|
|
121
|
+
*/
|
|
122
|
+
saveHookScript(event: ClaudeHookEvent, target: string | StatePathRef, statePath?: string | StatePathRef): Promise<string>;
|
|
123
|
+
/**
|
|
124
|
+
* Generate the hooks config document and persist it via the shared
|
|
125
|
+
* `saveGenerated`. Both the destination and the embedded state path
|
|
126
|
+
* accept raw strings or `{ root, name }` refs confined by
|
|
127
|
+
* `resolveStatePath`. Returns the absolute destination path.
|
|
128
|
+
*/
|
|
129
|
+
saveHooksConfig(target: string | StatePathRef, statePath?: string | StatePathRef, options?: ClaudeHooksConfigOptions): Promise<string>;
|
|
130
|
+
/**
|
|
131
|
+
* Generate a SKILL.md and persist it via the shared `saveGenerated`.
|
|
132
|
+
* Returns the absolute destination path.
|
|
65
133
|
*/
|
|
66
|
-
|
|
67
|
-
preCompact: string;
|
|
68
|
-
sessionStartCompact: string;
|
|
69
|
-
};
|
|
70
|
-
generateAllHooksScripts(stateRef: StatePathRef, schema?: ProceduralSpec['schema']): {
|
|
71
|
-
preCompact: string;
|
|
72
|
-
sessionStartCompact: string;
|
|
73
|
-
};
|
|
134
|
+
saveSkillMd(target: string | StatePathRef, spec: ProceduralSpec, statePath?: string): Promise<string>;
|
|
74
135
|
generateAppendPrompt(): string;
|
|
75
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Inject-script template (`UserPromptSubmit` / `SessionStart`): brand
|
|
138
|
+
* header + the shared hook-runtime snippet ({@link hookRuntimeSnippet}
|
|
139
|
+
* inlined via `fn.toString()`) + a stdin loop that emits
|
|
140
|
+
* `{ hookSpecificOutput: { hookEventName, additionalContext } }` with the
|
|
141
|
+
* current state JSON read from the SESSION cwd. Brand specifics stay in
|
|
142
|
+
* the adapter (comment lines only); the `contextSuffix` is the shared
|
|
143
|
+
* {@link HISTORY_UNRELIABLE_NOTE} from `@skillstate/core`.
|
|
144
|
+
*/
|
|
145
|
+
private buildInjectScriptTemplate;
|
|
146
|
+
/** PostToolUse script: extract state_patch, ⊕ merge, persist. */
|
|
147
|
+
private buildPostToolUseScript;
|
|
76
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Surgically remove every skillstate hook handler from an existing
|
|
151
|
+
* `settings.json` text (the uninstall path — restoring a backup would
|
|
152
|
+
* lose live settings, so groups are edited in place):
|
|
153
|
+
*
|
|
154
|
+
* - a handler is skillstate when its command points at a generated
|
|
155
|
+
* `<event>.cjs` script (matched by basename, independent of where the
|
|
156
|
+
* script dir lived);
|
|
157
|
+
* - groups whose handlers are ALL skillstate are dropped; MIXED groups
|
|
158
|
+
* keep their foreign handlers and lose only the skillstate ones;
|
|
159
|
+
* - event arrays left empty are removed from the `hooks` object;
|
|
160
|
+
* - every other top-level key (env, permissions, model, …) survives.
|
|
161
|
+
*
|
|
162
|
+
* Returns the original text untouched (`changed: false`) when it is
|
|
163
|
+
* malformed, has no hooks object, or carries no skillstate handlers.
|
|
164
|
+
*/
|
|
165
|
+
export declare function removeSkillstateHookGroups(existingJson: string): {
|
|
166
|
+
text: string;
|
|
167
|
+
changed: boolean;
|
|
168
|
+
};
|
|
77
169
|
//# sourceMappingURL=claude-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-adapter.d.ts","sourceRoot":"","sources":["../src/claude-adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"claude-adapter.d.ts","sourceRoot":"","sources":["../src/claude-adapter.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAc1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,sBAAsB,IAAI,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,qFAAqF;AACrF,MAAM,MAAM,eAAe,GACvB,oBAAoB,GACpB,uBAAuB,GACvB,eAAe,CAAC;AAEpB,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,2EAIgB,CAAC;AAEhD,mFAAmF;AACnF,eAAO,MAAM,4BAA4B,cAAc,CAAC;AAExD,2DAA2D;AAC3D,eAAO,MAAM,4BAA4B,WAAW,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,aAAc,YAAW,eAAe;IACnD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,WAAW,CAAiD;IAEpE,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAqB3D;IAED,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEhD;IAED,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7C;IAED;;;OAGG;IACH,YAAY,CACV,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,cAAc,GACnB,MAAM,CAER;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAEtE;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAAmB,CACjB,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,EACjC,OAAO,CAAC,EAAE,wBAAwB,GACjC,MAAM,CAuCR;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,kBAAkB,CAChB,KAAK,EAAE,eAAe,EACtB,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,GAChC,MAAM,CAiBR;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhE;IAED;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAYjF;IAED;;;;;OAKG;IACG,cAAc,CAClB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,GAChC,OAAO,CAAC,MAAM,CAAC,CAEjB;IAED;;;;;OAKG;IACG,eAAe,CACnB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,EACjC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,MAAM,CAAC,CAEjB;IAED;;;OAGG;IACG,WAAW,CACf,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,IAAI,EAAE,cAAc,EACpB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAEjB;IAED,oBAAoB,IAAI,MAAM,CAS7B;IAMD;;;;;;;;OAQG;IACH,OAAO,CAAC,yBAAyB;IAiDjC,iEAAiE;IACjE,OAAO,CAAC,sBAAsB;CAkE/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAiDpC"}
|
package/dist/claude-adapter.js
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code adapter (2.1.260 hooks contract, verified against the Claude
|
|
3
|
+
* Code hooks reference, Sep 2026):
|
|
4
|
+
*
|
|
5
|
+
* - hooks are configured in `~/.claude/settings.json` (user scope) as
|
|
6
|
+
* `{ hooks: { Event: [ { matcher?, if?, hooks: [ { type: "command",
|
|
7
|
+
* command, timeout } ] } ] } }`; handler `timeout` is seconds;
|
|
8
|
+
* - `UserPromptSubmit` (+`prompt`): JSON stdout
|
|
9
|
+
* `{ hookSpecificOutput: { hookEventName: "UserPromptSubmit",
|
|
10
|
+
* additionalContext } }` is added to Claude's context (plain stdout is
|
|
11
|
+
* added too); no matcher support — fires on every prompt; default
|
|
12
|
+
* timeout 30s;
|
|
13
|
+
* - `SessionStart` (+`source`): matcher `^compact$` fires after auto or
|
|
14
|
+
* manual compaction and supports `additionalContext` — this is how state
|
|
15
|
+
* SURVIVES compaction;
|
|
16
|
+
* - `PostToolUse` (+`tool_name`, `tool_input`, `tool_response`): matcher
|
|
17
|
+
* `^Bash$` restricts to Bash results; stdout `{}` or a `systemMessage`
|
|
18
|
+
* when the response carried an invalid state patch.
|
|
19
|
+
*
|
|
20
|
+
* HONEST LIMITATION: history trimming from hooks is IMPOSSIBLE in Claude
|
|
21
|
+
* Code. The compact-adjacent events cannot inject context — the compaction
|
|
22
|
+
* hook supports only `decision: "block"` (forbid compaction) and the
|
|
23
|
+
* post-compaction hook has NO decision control (its `systemMessage` is
|
|
24
|
+
* discarded). So the model here is state-injection per prompt
|
|
25
|
+
* (`UserPromptSubmit`), survival through compaction
|
|
26
|
+
* (`SessionStart` matcher `^compact$`), patch persistence per Bash result
|
|
27
|
+
* (`PostToolUse`), and full read/write via the skillstate MCP tools
|
|
28
|
+
* (`state.get` / `state.patch`). Prompts stay O(T) with fresh state at
|
|
29
|
+
* every turn; true O(1) requires host-side trimming which the host does
|
|
30
|
+
* not expose.
|
|
31
|
+
*
|
|
32
|
+
* Every generated hook script is a SELF-CONTAINED CommonJS file (Node
|
|
33
|
+
* builtins only, no `@skillstate/*` import) that resolves the per-project
|
|
34
|
+
* state from `input.cwd` at runtime — one global `settings.json` hooks
|
|
35
|
+
* section + one script directory serve every project.
|
|
36
|
+
*
|
|
37
|
+
* @non-paper — no adapters exist in arXiv 2608.26263v3.
|
|
38
|
+
*/
|
|
39
|
+
import * as path from 'node:path';
|
|
1
40
|
import { PromptTransformer } from '@skillstate/core';
|
|
2
|
-
import {
|
|
41
|
+
import { describeSchema, HISTORY_UNRELIABLE_NOTE, hookRuntimeSnippet, isPlainObject, mergeHookGroups, resolveTarget, saveGenerated, skillMdBody, STATE_PATCH_CONTRACT, } from '@skillstate/core';
|
|
42
|
+
export { resolveHostStateForCwd as resolveStateForCwd } from '@skillstate/core';
|
|
43
|
+
/** All {@link ClaudeHookEvent} values in generation order. */
|
|
44
|
+
export const CLAUDE_HOOK_EVENTS = [
|
|
45
|
+
'user-prompt-submit',
|
|
46
|
+
'session-start-compact',
|
|
47
|
+
'post-tool-use',
|
|
48
|
+
];
|
|
49
|
+
/** SessionStart matcher that fires after Claude Code compacts the conversation. */
|
|
50
|
+
export const CLAUDE_SESSION_START_MATCHER = '^compact$';
|
|
51
|
+
/** PostToolUse matcher restricted to Bash tool results. */
|
|
52
|
+
export const CLAUDE_POST_TOOL_USE_MATCHER = '^Bash$';
|
|
53
|
+
/**
|
|
54
|
+
* Hook `timeout` in seconds written into every generated hook entry — the
|
|
55
|
+
* UserPromptSubmit default, spelled out so the budget is explicit.
|
|
56
|
+
*/
|
|
57
|
+
export const CLAUDE_HOOK_TIMEOUT_SECONDS = 30;
|
|
58
|
+
/**
|
|
59
|
+
* Claude Code platform adapter (@non-paper; see module doc for the 2.1.260
|
|
60
|
+
* hooks contract and the honest O(T) limitation).
|
|
61
|
+
*/
|
|
3
62
|
export class ClaudeAdapter {
|
|
4
63
|
name = 'claude';
|
|
5
64
|
transformer = new PromptTransformer({ platform: 'claude' });
|
|
6
65
|
injectState(state, spec) {
|
|
7
66
|
const stateJson = JSON.stringify(state);
|
|
8
|
-
const schemaDesc =
|
|
67
|
+
const schemaDesc = describeSchema(spec.schema);
|
|
9
68
|
return `# System
|
|
10
69
|
|
|
11
70
|
You are ${spec.name}. ${spec.instructions}
|
|
@@ -22,17 +81,7 @@ ${stateJson}
|
|
|
22
81
|
|
|
23
82
|
Based on your current state, provide your response with:
|
|
24
83
|
|
|
25
|
-
|
|
26
|
-
2. A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
|
|
27
|
-
|
|
28
|
-
\`\`\`json
|
|
29
|
-
{
|
|
30
|
-
"state_patch": { "key": "new_value", "obsolete_key": null },
|
|
31
|
-
"action": "your_action_here"
|
|
32
|
-
}
|
|
33
|
-
\`\`\`
|
|
34
|
-
|
|
35
|
-
In \`state_patch\`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.`;
|
|
84
|
+
${STATE_PATCH_CONTRACT}`;
|
|
36
85
|
}
|
|
37
86
|
extractPatch(response) {
|
|
38
87
|
return this.transformer.extractStatePatch(response);
|
|
@@ -41,283 +90,371 @@ In \`state_patch\`, set keys to null to delete them. Only include fields you wan
|
|
|
41
90
|
return this.transformer.extractAction(response);
|
|
42
91
|
}
|
|
43
92
|
/**
|
|
44
|
-
* @non-paper
|
|
93
|
+
* @non-paper adapter convenience (delegates to the transformer).
|
|
45
94
|
* Paper-exact callers use `PromptTransformer.formatPaper` (Appendix A.4).
|
|
46
95
|
*/
|
|
47
96
|
formatPrompt(state, observation, spec) {
|
|
48
97
|
return this.transformer.formatForClaude(spec, state, observation);
|
|
49
98
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Canonical absolute path of the hook script for `event` inside
|
|
101
|
+
* `scriptDir` (e.g. `~/.claude/hooks/skillstate/user-prompt-submit.cjs`).
|
|
102
|
+
* {@link generateHooksConfig} and {@link saveHookScript} share this
|
|
103
|
+
* convention so the settings.json commands and the on-disk scripts agree.
|
|
104
|
+
*/
|
|
105
|
+
claudeHookScriptPath(scriptDir, event) {
|
|
106
|
+
return path.join(scriptDir, `${event}.cjs`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate the hooks section for `~/.claude/settings.json` (2.1.260
|
|
110
|
+
* schema: `{ hooks: { Event: [ { matcher?, hooks: [ { type: "command",
|
|
111
|
+
* command, timeout } ] } ] } }`). The document carries ONLY the
|
|
112
|
+
* `hooks` key — {@link mergeHooksConfig} is what splices these groups
|
|
113
|
+
* into a live settings.json while preserving every other key:
|
|
114
|
+
*
|
|
115
|
+
* - `UserPromptSubmit` → inject the current state as additionalContext;
|
|
116
|
+
* - `SessionStart` (matcher `^compact$`) → re-inject after compaction;
|
|
117
|
+
* - `PostToolUse` (matcher `^Bash$`) → persist `state_patch` blocks from
|
|
118
|
+
* Bash tool outputs.
|
|
119
|
+
*
|
|
120
|
+
* Commands are absolute `node <script> <event>` lines pointing at the
|
|
121
|
+
* generated `.cjs` scripts in `options.scriptDir` (default: the state
|
|
122
|
+
* file's directory, else a `<stateDir>/hooks` placeholder). No matcher
|
|
123
|
+
* on UserPromptSubmit — the event has no matcher support and fires on
|
|
124
|
+
* every prompt.
|
|
125
|
+
*/
|
|
126
|
+
generateHooksConfig(statePath, options) {
|
|
127
|
+
const scriptDir = options?.scriptDir ??
|
|
128
|
+
(statePath === undefined ? '<stateDir>/hooks' : path.dirname(resolveTarget(statePath)));
|
|
129
|
+
const command = (event) => options?.command ?? `node ${JSON.stringify(this.claudeHookScriptPath(scriptDir, event))} ${event}`;
|
|
130
|
+
const timeout = options?.timeoutSeconds ?? CLAUDE_HOOK_TIMEOUT_SECONDS;
|
|
131
|
+
const entry = (event) => ({
|
|
132
|
+
type: 'command',
|
|
133
|
+
command: command(event),
|
|
134
|
+
timeout,
|
|
135
|
+
});
|
|
136
|
+
const doc = {
|
|
137
|
+
hooks: {
|
|
138
|
+
UserPromptSubmit: [
|
|
139
|
+
{
|
|
140
|
+
hooks: [entry('user-prompt-submit')],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
SessionStart: [
|
|
144
|
+
{
|
|
145
|
+
matcher: CLAUDE_SESSION_START_MATCHER,
|
|
146
|
+
hooks: [
|
|
147
|
+
entry('session-start-compact'),
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
PostToolUse: [
|
|
152
|
+
{
|
|
153
|
+
matcher: CLAUDE_POST_TOOL_USE_MATCHER,
|
|
154
|
+
hooks: [entry('post-tool-use')],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Generate a self-contained CommonJS hook script for a Claude Code
|
|
163
|
+
* lifecycle event. The script reads ONE hook JSON document from stdin,
|
|
164
|
+
* resolves the state file from `input.cwd` (the session cwd) via the
|
|
165
|
+
* {@link resolveStateForCwd} semantics, and:
|
|
166
|
+
*
|
|
167
|
+
* - `user-prompt-submit`: emits
|
|
168
|
+
* `{ hookSpecificOutput: { hookEventName: "UserPromptSubmit",
|
|
169
|
+
* additionalContext } }` carrying the current state JSON;
|
|
170
|
+
* - `session-start-compact`: the same injection with
|
|
171
|
+
* `hookEventName: "SessionStart"` (state survives compaction; wire
|
|
172
|
+
* with the `^compact$` matcher);
|
|
173
|
+
* - `post-tool-use`: extracts a `state_patch` from the tool_response
|
|
174
|
+
* (fenced ```json block or raw JSON), applies the ⊕ null-deletion merge
|
|
175
|
+
* and writes the state file; stdout is `{}` or a `systemMessage` when
|
|
176
|
+
* the patch is invalid.
|
|
177
|
+
*
|
|
178
|
+
* `statePath` is accepted for `{ root, name }` confinement (traversal
|
|
179
|
+
* refs throw) and documented in the script header; the content itself is
|
|
180
|
+
* cwd-resolving and never bakes an absolute state path in.
|
|
181
|
+
*/
|
|
182
|
+
generateHookScript(event, statePath) {
|
|
183
|
+
const resolved = statePath === undefined ? undefined : resolveTarget(statePath);
|
|
184
|
+
const header = `// State file (per-project resolver): ${resolved ?? '<cwd>/.skillstate/skillstate.json (global bucket when cwd === home)'}`;
|
|
185
|
+
if (event === 'post-tool-use') {
|
|
186
|
+
return this.buildPostToolUseScript(header);
|
|
77
187
|
}
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
188
|
+
const hookEventName = event === 'session-start-compact' ? 'SessionStart' : 'UserPromptSubmit';
|
|
189
|
+
// A1 text is shared: both hosts append the SAME HISTORY_UNRELIABLE_NOTE
|
|
190
|
+
// (MCP tools + fenced state_patch channel) after the state JSON.
|
|
191
|
+
return this.buildInjectScriptTemplate({
|
|
192
|
+
header,
|
|
193
|
+
hookEventName,
|
|
194
|
+
contextSuffix: HISTORY_UNRELIABLE_NOTE,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Generate a SKILL.md for Claude Code's skill directory
|
|
199
|
+
* (`~/.claude/skills/<name>/SKILL.md`). The body instructs the agent to
|
|
200
|
+
* treat the hook-injected state as authoritative (history is not
|
|
201
|
+
* reliable), to orient via `state.summary` (full dump via `state.get`),
|
|
202
|
+
* and to persist via `state.patch` — the PostToolUse hook also merges any
|
|
203
|
+
* fenced ```json `state_patch` block printed by a Bash tool call.
|
|
204
|
+
*/
|
|
205
|
+
generateSkillMd(spec, statePath) {
|
|
206
|
+
return skillMdBody({
|
|
207
|
+
hostLabel: 'Claude Code',
|
|
208
|
+
injectionPhrase: 'injected into your context via hooks',
|
|
209
|
+
spec,
|
|
210
|
+
statePath,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Merge the skillstate hook groups into an existing `settings.json`
|
|
215
|
+
* text. Preserves every other top-level key (env, permissions, model,
|
|
216
|
+
* …) and every existing (non-skillstate) hook. Idempotent: if any
|
|
217
|
+
* skillstate command is already wired, the ORIGINAL text is returned
|
|
218
|
+
* byte-unchanged. Malformed/empty input starts from a fresh hooks-only
|
|
219
|
+
* document (the CLI guards a live settings.json before calling).
|
|
220
|
+
*
|
|
221
|
+
* The mechanics live in the shared {@link mergeHookGroups}; this method
|
|
222
|
+
* supplies only the format specifics (generated settings.json groups and
|
|
223
|
+
* the `node <script> <event>` skillstate commands).
|
|
224
|
+
*/
|
|
225
|
+
mergeHooksConfig(existingJson, options) {
|
|
226
|
+
const scriptDir = options?.scriptDir ?? '<stateDir>/hooks';
|
|
227
|
+
const command = (event) => `node ${JSON.stringify(this.claudeHookScriptPath(scriptDir, event))} ${event}`;
|
|
228
|
+
const generated = JSON.parse(this.generateHooksConfig(undefined, { ...options, scriptDir }));
|
|
229
|
+
return mergeHookGroups({
|
|
230
|
+
existingJson,
|
|
231
|
+
generatedGroups: generated.hooks,
|
|
232
|
+
commandsOf: new Set(CLAUDE_HOOK_EVENTS.map((event) => JSON.stringify(command(event)))),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Generate a hook script and persist it via the shared `saveGenerated`
|
|
237
|
+
* (atomic temp-rename write). `target` is the script destination (usually
|
|
238
|
+
* {@link ClaudeAdapter.claudeHookScriptPath}); `statePath` is forwarded
|
|
239
|
+
* to {@link generateHookScript}. Returns the absolute destination path.
|
|
240
|
+
*/
|
|
241
|
+
async saveHookScript(event, target, statePath) {
|
|
242
|
+
return saveGenerated(target, this.generateHookScript(event, statePath));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Generate the hooks config document and persist it via the shared
|
|
246
|
+
* `saveGenerated`. Both the destination and the embedded state path
|
|
247
|
+
* accept raw strings or `{ root, name }` refs confined by
|
|
248
|
+
* `resolveStatePath`. Returns the absolute destination path.
|
|
249
|
+
*/
|
|
250
|
+
async saveHooksConfig(target, statePath, options) {
|
|
251
|
+
return saveGenerated(target, this.generateHooksConfig(statePath, options));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Generate a SKILL.md and persist it via the shared `saveGenerated`.
|
|
255
|
+
* Returns the absolute destination path.
|
|
256
|
+
*/
|
|
257
|
+
async saveSkillMd(target, spec, statePath) {
|
|
258
|
+
return saveGenerated(target, this.generateSkillMd(spec, statePath));
|
|
259
|
+
}
|
|
260
|
+
generateAppendPrompt() {
|
|
261
|
+
return `You are operating in state-based execution mode. Your state is maintained across steps.
|
|
262
|
+
|
|
263
|
+
After each step, you MUST respond with:
|
|
264
|
+
|
|
265
|
+
${STATE_PATCH_CONTRACT}
|
|
266
|
+
|
|
267
|
+
- \`action\` indicates what you want to do next (e.g., "continue", "done", "deploy").
|
|
268
|
+
- Put anything you need to persist into \`state_patch\`.`;
|
|
269
|
+
}
|
|
270
|
+
/* ------------------------------------------------------------------ */
|
|
271
|
+
/* Internal helpers */
|
|
272
|
+
/* ------------------------------------------------------------------ */
|
|
273
|
+
/**
|
|
274
|
+
* Inject-script template (`UserPromptSubmit` / `SessionStart`): brand
|
|
275
|
+
* header + the shared hook-runtime snippet ({@link hookRuntimeSnippet}
|
|
276
|
+
* inlined via `fn.toString()`) + a stdin loop that emits
|
|
277
|
+
* `{ hookSpecificOutput: { hookEventName, additionalContext } }` with the
|
|
278
|
+
* current state JSON read from the SESSION cwd. Brand specifics stay in
|
|
279
|
+
* the adapter (comment lines only); the `contextSuffix` is the shared
|
|
280
|
+
* {@link HISTORY_UNRELIABLE_NOTE} from `@skillstate/core`.
|
|
281
|
+
*/
|
|
282
|
+
buildInjectScriptTemplate(options) {
|
|
85
283
|
return [
|
|
86
|
-
'
|
|
87
|
-
|
|
88
|
-
'//
|
|
284
|
+
'#!/usr/bin/env node',
|
|
285
|
+
`// skillstate Claude Code hook — ${options.hookEventName} (generated by @skillstate/claude).`,
|
|
286
|
+
'// Self-contained CommonJS: reads one hook JSON document on stdin,',
|
|
287
|
+
'// resolves the state from the SESSION cwd, and emits',
|
|
288
|
+
'// { hookSpecificOutput: { hookEventName, additionalContext } }.',
|
|
289
|
+
options.header,
|
|
290
|
+
"'use strict';",
|
|
89
291
|
'const fs = require("fs");',
|
|
90
|
-
'const
|
|
91
|
-
'const
|
|
292
|
+
'const os = require("os");',
|
|
293
|
+
'const path = require("path");',
|
|
92
294
|
'',
|
|
93
|
-
|
|
94
|
-
' return typeof v === "object" && v !== null && !Array.isArray(v);',
|
|
95
|
-
'}',
|
|
295
|
+
`const HOOK_EVENT_NAME = ${JSON.stringify(options.hookEventName)};`,
|
|
96
296
|
'',
|
|
97
|
-
|
|
98
|
-
'// recursively, everything else overwrites. Never mutates `base` —',
|
|
99
|
-
'// builds and returns a copy.',
|
|
100
|
-
'function mergePatch(base, patch) {',
|
|
101
|
-
' function mergeInto(result, patchObj) {',
|
|
102
|
-
' for (const key of Object.keys(patchObj)) {',
|
|
103
|
-
' const value = patchObj[key];',
|
|
104
|
-
' if (value === null) {',
|
|
105
|
-
' delete result[key];',
|
|
106
|
-
' } else if (isPlainObject(value) && isPlainObject(result[key])) {',
|
|
107
|
-
' result[key] = mergeInto({ ...result[key] }, value);',
|
|
108
|
-
' } else {',
|
|
109
|
-
' result[key] = value;',
|
|
110
|
-
' }',
|
|
111
|
-
' }',
|
|
112
|
-
' return result;',
|
|
113
|
-
' }',
|
|
114
|
-
' return mergeInto({ ...base }, patch);',
|
|
115
|
-
'}',
|
|
297
|
+
hookRuntimeSnippet(),
|
|
116
298
|
'',
|
|
117
|
-
'
|
|
118
|
-
'function validatePatchAgainstSchema(patch) {',
|
|
119
|
-
' for (const key of Object.keys(patch)) {',
|
|
120
|
-
' const field = schema[key];',
|
|
121
|
-
' if (!field) {',
|
|
122
|
-
' return "Unknown key: " + key;',
|
|
123
|
-
' }',
|
|
124
|
-
' const value = patch[key];',
|
|
125
|
-
' if (value === null) continue;',
|
|
126
|
-
' const expected = field.type;',
|
|
127
|
-
' let ok = false;',
|
|
128
|
-
' if (expected === "string") ok = typeof value === "string";',
|
|
129
|
-
' else if (expected === "number") ok = typeof value === "number";',
|
|
130
|
-
' else if (expected === "boolean") ok = typeof value === "boolean";',
|
|
131
|
-
' else if (expected === "array") ok = Array.isArray(value);',
|
|
132
|
-
' else if (expected === "object") ok = isPlainObject(value);',
|
|
133
|
-
' if (!ok) {',
|
|
134
|
-
' return "Invalid type for field \'" + key + "\': expected " + expected +',
|
|
135
|
-
' ", got " + (Array.isArray(value) ? "array" : typeof value);',
|
|
136
|
-
' }',
|
|
137
|
-
' }',
|
|
138
|
-
' return null;',
|
|
139
|
-
'}',
|
|
140
|
-
'',
|
|
141
|
-
'let state = {};',
|
|
142
|
-
'try {',
|
|
143
|
-
' if (fs.existsSync(stateFilePath)) {',
|
|
144
|
-
' state = JSON.parse(fs.readFileSync(stateFilePath, "utf-8"));',
|
|
145
|
-
' }',
|
|
146
|
-
'} catch (e) { state = {}; }',
|
|
147
|
-
'let input = "";',
|
|
299
|
+
'let raw = "";',
|
|
148
300
|
'process.stdin.setEncoding("utf-8");',
|
|
149
|
-
'process.stdin.on("data", (chunk) => {
|
|
301
|
+
'process.stdin.on("data", (chunk) => { raw += chunk; });',
|
|
150
302
|
'process.stdin.on("end", () => {',
|
|
151
|
-
'
|
|
303
|
+
' let cwd = process.cwd();',
|
|
152
304
|
' try {',
|
|
153
|
-
' const
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
' const match = content.match(re);',
|
|
157
|
-
' if (match) {',
|
|
158
|
-
' // Guarded parse: malformed JSON inside the fence leaves state untouched.',
|
|
159
|
-
' let json;',
|
|
160
|
-
' try {',
|
|
161
|
-
' json = JSON.parse(match[1]);',
|
|
162
|
-
' } catch (parseError) {',
|
|
163
|
-
' output.error = "Malformed JSON in state patch block: " + parseError.message;',
|
|
164
|
-
' process.stdout.write(JSON.stringify(output));',
|
|
165
|
-
' return;',
|
|
166
|
-
' }',
|
|
167
|
-
' if (json.state_patch && typeof json.state_patch === "object" && !Array.isArray(json.state_patch)) {',
|
|
168
|
-
' const validationError = validatePatchAgainstSchema(json.state_patch);',
|
|
169
|
-
' if (validationError) {',
|
|
170
|
-
' // Reject: report the error, never write state.',
|
|
171
|
-
' output.error = validationError;',
|
|
172
|
-
' process.stdout.write(JSON.stringify(output));',
|
|
173
|
-
' return;',
|
|
174
|
-
' }',
|
|
175
|
-
' state = mergePatch(state, json.state_patch);',
|
|
176
|
-
' fs.writeFileSync(stateFilePath, JSON.stringify(state, null, 2));',
|
|
177
|
-
' }',
|
|
305
|
+
' const input = JSON.parse(raw);',
|
|
306
|
+
' if (typeof input.cwd === "string" && input.cwd.length > 0) {',
|
|
307
|
+
' cwd = input.cwd;',
|
|
178
308
|
' }',
|
|
179
|
-
' } catch (
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'
|
|
309
|
+
' } catch (error) {}',
|
|
310
|
+
' const state = readStateEnvelope(',
|
|
311
|
+
' resolveStatePathForCwd(path.resolve(cwd), path.resolve(os.homedir())),',
|
|
312
|
+
' (p) => fs.readFileSync(p, "utf-8"),',
|
|
313
|
+
' );',
|
|
314
|
+
' const output = {',
|
|
315
|
+
' hookSpecificOutput: {',
|
|
316
|
+
' hookEventName: HOOK_EVENT_NAME,',
|
|
317
|
+
' additionalContext: "Current skill state (JSON): " + JSON.stringify(state)',
|
|
318
|
+
` + ${JSON.stringify(options.contextSuffix)},`,
|
|
319
|
+
' },',
|
|
320
|
+
' };',
|
|
183
321
|
' process.stdout.write(JSON.stringify(output));',
|
|
184
322
|
'});',
|
|
323
|
+
'',
|
|
185
324
|
].join('\n');
|
|
186
325
|
}
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* embedded state path accept raw strings or
|
|
191
|
-
* `{ root, name }` refs confined by `resolveStatePath`. Returns the
|
|
192
|
-
* absolute destination path.
|
|
193
|
-
*/
|
|
194
|
-
async saveHookScript(target, eventType, statePath, schema) {
|
|
195
|
-
const dest = typeof target === 'string'
|
|
196
|
-
? target
|
|
197
|
-
: resolveStatePath(target.root, target.name);
|
|
198
|
-
const resolvedState = typeof statePath === 'string'
|
|
199
|
-
? statePath
|
|
200
|
-
: resolveStatePath(statePath.root, statePath.name);
|
|
201
|
-
const script = this.generateHookScript(eventType, resolvedState, schema);
|
|
202
|
-
await atomicWriteFile(dest, script);
|
|
203
|
-
return dest;
|
|
204
|
-
}
|
|
205
|
-
generateCompactHookScript(statePathOrRef, schema) {
|
|
206
|
-
const statePath = typeof statePathOrRef === 'string'
|
|
207
|
-
? statePathOrRef
|
|
208
|
-
: resolveStatePath(statePathOrRef.root, statePathOrRef.name);
|
|
209
|
-
const sp = JSON.stringify(statePath);
|
|
210
|
-
const schemaJson = JSON.stringify(schema ?? {});
|
|
211
|
-
const lastCompactPath = JSON.stringify(statePath + '.last-compact.json');
|
|
326
|
+
/** PostToolUse script: extract state_patch, ⊕ merge, persist. */
|
|
327
|
+
buildPostToolUseScript(header) {
|
|
328
|
+
const fence = '```';
|
|
212
329
|
return [
|
|
213
|
-
'
|
|
214
|
-
'//
|
|
215
|
-
'//
|
|
330
|
+
'#!/usr/bin/env node',
|
|
331
|
+
'// skillstate Claude Code hook — PostToolUse (generated by @skillstate/claude).',
|
|
332
|
+
'// Self-contained CommonJS: reads one hook JSON document on stdin,',
|
|
333
|
+
'// extracts state_patch from the tool_response (fenced ```json block or',
|
|
334
|
+
'// raw JSON), applies the null-deletion merge and writes the state file.',
|
|
335
|
+
'// stdout is "{}" or a systemMessage when the patch is invalid.',
|
|
336
|
+
header,
|
|
337
|
+
"'use strict';",
|
|
216
338
|
'const fs = require("fs");',
|
|
217
|
-
'const
|
|
218
|
-
'const
|
|
219
|
-
'const schema = ' + schemaJson + ';',
|
|
220
|
-
'',
|
|
221
|
-
'function readJsonSafe(filePath) {',
|
|
222
|
-
' try {',
|
|
223
|
-
' if (fs.existsSync(filePath)) {',
|
|
224
|
-
' return JSON.parse(fs.readFileSync(filePath, "utf-8"));',
|
|
225
|
-
' }',
|
|
226
|
-
' } catch {}',
|
|
227
|
-
' return {};',
|
|
228
|
-
'}',
|
|
339
|
+
'const os = require("os");',
|
|
340
|
+
'const path = require("path");',
|
|
229
341
|
'',
|
|
230
|
-
|
|
231
|
-
'const lastCompact = readJsonSafe(lastCompactFilePath);',
|
|
342
|
+
`const FENCE = ${JSON.stringify(fence)};`,
|
|
232
343
|
'',
|
|
233
|
-
|
|
234
|
-
'const diff = {};',
|
|
235
|
-
'const allKeys = new Set([...Object.keys(current), ...Object.keys(lastCompact)]);',
|
|
236
|
-
'for (const key of allKeys) {',
|
|
237
|
-
' const cur = current[key];',
|
|
238
|
-
' const prev = lastCompact[key];',
|
|
239
|
-
' if (cur === undefined) {',
|
|
240
|
-
' diff[key] = "(deleted)";',
|
|
241
|
-
' } else if (prev === undefined) {',
|
|
242
|
-
' diff[key] = cur;',
|
|
243
|
-
' } else if (JSON.stringify(cur) !== JSON.stringify(prev)) {',
|
|
244
|
-
' diff[key] = { from: prev, to: cur };',
|
|
245
|
-
' }',
|
|
246
|
-
'}',
|
|
247
|
-
'',
|
|
248
|
-
'const contextParts = [',
|
|
249
|
-
' "Current skill state (JSON): " + JSON.stringify(current),',
|
|
250
|
-
'];',
|
|
251
|
-
'if (Object.keys(diff).length > 0) {',
|
|
252
|
-
' contextParts.push("Changes since last compact: " + JSON.stringify(diff));',
|
|
253
|
-
'}',
|
|
344
|
+
hookRuntimeSnippet(),
|
|
254
345
|
'',
|
|
255
|
-
'
|
|
256
|
-
'
|
|
257
|
-
'
|
|
346
|
+
'let raw = "";',
|
|
347
|
+
'process.stdin.setEncoding("utf-8");',
|
|
348
|
+
'process.stdin.on("data", (chunk) => { raw += chunk; });',
|
|
349
|
+
'process.stdin.on("end", () => {',
|
|
350
|
+
' const output = {};',
|
|
351
|
+
' try {',
|
|
352
|
+
' const input = JSON.parse(raw);',
|
|
353
|
+
' let cwd = process.cwd();',
|
|
354
|
+
' if (typeof input.cwd === "string" && input.cwd.length > 0) {',
|
|
355
|
+
' cwd = input.cwd;',
|
|
356
|
+
' }',
|
|
357
|
+
' const statePath = resolveStatePathForCwd(path.resolve(cwd), path.resolve(os.homedir()));',
|
|
358
|
+
' const response = input.tool_response;',
|
|
359
|
+
' let result;',
|
|
360
|
+
' if (isPlainObject(response) && isPlainObject(response.state_patch)) {',
|
|
361
|
+
' result = { patch: response.state_patch };',
|
|
362
|
+
' } else {',
|
|
363
|
+
' const text = readResponseText(response);',
|
|
364
|
+
' result = findFencedPatch(text);',
|
|
365
|
+
' if (result.absent) result = findRawPatch(text);',
|
|
366
|
+
' }',
|
|
367
|
+
' if (result.patch !== undefined) {',
|
|
368
|
+
' const merged = mergePatch(',
|
|
369
|
+
' readStateEnvelope(statePath, (p) => fs.readFileSync(p, "utf-8")),',
|
|
370
|
+
' result.patch,',
|
|
371
|
+
' );',
|
|
372
|
+
' try {',
|
|
373
|
+
' fs.mkdirSync(path.dirname(statePath), { recursive: true });',
|
|
374
|
+
' saveStateEnvelope(statePath, merged, (p, data) => fs.writeFileSync(p, data));',
|
|
375
|
+
' } catch (writeError) {',
|
|
376
|
+
" output.systemMessage = 'skillstate: failed to persist state (' + writeError.message + ')';",
|
|
377
|
+
' }',
|
|
378
|
+
' process.stdout.write(JSON.stringify(output));',
|
|
379
|
+
' return;',
|
|
380
|
+
' }',
|
|
381
|
+
' if (result.invalid) {',
|
|
382
|
+
' output.systemMessage =',
|
|
383
|
+
' "skillstate: ignored an invalid state patch (expected a " + FENCE + "json block with a state_patch object)";',
|
|
384
|
+
' }',
|
|
385
|
+
' } catch (error) {',
|
|
386
|
+
' output.systemMessage = "skillstate: failed to process PostToolUse input: " + error.message;',
|
|
258
387
|
' }',
|
|
259
|
-
'
|
|
260
|
-
'',
|
|
261
|
-
'// Save current state as the new compact snapshot.',
|
|
262
|
-
'try {',
|
|
263
|
-
' fs.writeFileSync(lastCompactFilePath, JSON.stringify(current, null, 2));',
|
|
264
|
-
'} catch {}',
|
|
388
|
+
' process.stdout.write(JSON.stringify(output));',
|
|
389
|
+
'});',
|
|
265
390
|
'',
|
|
266
|
-
'process.stdout.write(JSON.stringify(output));',
|
|
267
391
|
].join('\n');
|
|
268
392
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
'};',
|
|
291
|
-
'process.stdout.write(JSON.stringify(output));',
|
|
292
|
-
].join('\n');
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Surgically remove every skillstate hook handler from an existing
|
|
396
|
+
* `settings.json` text (the uninstall path — restoring a backup would
|
|
397
|
+
* lose live settings, so groups are edited in place):
|
|
398
|
+
*
|
|
399
|
+
* - a handler is skillstate when its command points at a generated
|
|
400
|
+
* `<event>.cjs` script (matched by basename, independent of where the
|
|
401
|
+
* script dir lived);
|
|
402
|
+
* - groups whose handlers are ALL skillstate are dropped; MIXED groups
|
|
403
|
+
* keep their foreign handlers and lose only the skillstate ones;
|
|
404
|
+
* - event arrays left empty are removed from the `hooks` object;
|
|
405
|
+
* - every other top-level key (env, permissions, model, …) survives.
|
|
406
|
+
*
|
|
407
|
+
* Returns the original text untouched (`changed: false`) when it is
|
|
408
|
+
* malformed, has no hooks object, or carries no skillstate handlers.
|
|
409
|
+
*/
|
|
410
|
+
export function removeSkillstateHookGroups(existingJson) {
|
|
411
|
+
let doc;
|
|
412
|
+
try {
|
|
413
|
+
doc = JSON.parse(existingJson);
|
|
293
414
|
}
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
preCompact: this.generateCompactHookScript(statePathOrRef, schema),
|
|
297
|
-
sessionStartCompact: this.generateSessionStartHookScript(statePathOrRef),
|
|
298
|
-
};
|
|
415
|
+
catch {
|
|
416
|
+
return { text: existingJson, changed: false };
|
|
299
417
|
}
|
|
300
|
-
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
418
|
+
if (!isPlainObject(doc) || !isPlainObject(doc['hooks'])) {
|
|
419
|
+
return { text: existingJson, changed: false };
|
|
420
|
+
}
|
|
421
|
+
const scriptBasenames = CLAUDE_HOOK_EVENTS.map((event) => `${event}.cjs`);
|
|
422
|
+
const isSkillstateCommand = (command) => typeof command === 'string' && scriptBasenames.some((b) => command.includes(b));
|
|
423
|
+
const hooks = doc['hooks'];
|
|
424
|
+
let changed = false;
|
|
425
|
+
for (const event of Object.keys(hooks)) {
|
|
426
|
+
const groups = hooks[event];
|
|
427
|
+
if (!Array.isArray(groups))
|
|
428
|
+
continue;
|
|
429
|
+
const keptGroups = [];
|
|
430
|
+
let eventChanged = false;
|
|
431
|
+
for (const group of groups) {
|
|
432
|
+
if (!isPlainObject(group) || !Array.isArray(group['hooks'])) {
|
|
433
|
+
keptGroups.push(group);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const handlers = group['hooks'];
|
|
437
|
+
const keptHandlers = handlers.filter((handler) => !(isPlainObject(handler) && isSkillstateCommand(handler['command'])));
|
|
438
|
+
if (keptHandlers.length === handlers.length) {
|
|
439
|
+
keptGroups.push(group);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
eventChanged = true;
|
|
443
|
+
if (keptHandlers.length > 0) {
|
|
444
|
+
keptGroups.push({ ...group, hooks: keptHandlers });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (keptGroups.length === 0) {
|
|
448
|
+
delete hooks[event];
|
|
449
|
+
}
|
|
450
|
+
else if (eventChanged) {
|
|
451
|
+
hooks[event] = keptGroups;
|
|
452
|
+
}
|
|
453
|
+
changed = changed || eventChanged;
|
|
315
454
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
.map(([name, field]) => `- ${name} (${field.type}): ${field.description ?? 'no description'}`)
|
|
319
|
-
.join('\n');
|
|
320
|
-
return `## Schema\n${fields}`;
|
|
455
|
+
if (!changed) {
|
|
456
|
+
return { text: existingJson, changed: false };
|
|
321
457
|
}
|
|
458
|
+
return { text: `${JSON.stringify(doc, null, 2)}\n`, changed: true };
|
|
322
459
|
}
|
|
323
460
|
//# sourceMappingURL=claude-adapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-adapter.js","sourceRoot":"","sources":["../src/claude-adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"claude-adapter.js","sourceRoot":"","sources":["../src/claude-adapter.ts"],"names":[],"mappings":"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"}
|