@softspark/ai-toolkit 4.15.1 → 4.16.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/CHANGELOG.md +34 -0
- package/README.md +19 -11
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/ARCHITECTURE.md +4 -3
- package/app/hooks/_hook-io.sh +18 -3
- package/app/hooks/ai-toolkit-statusline.sh +30 -5
- package/app/hooks/filter-tool-output.sh +76 -0
- package/app/hooks/governance-capture.sh +1 -1
- package/app/hooks/guard-path.sh +2 -2
- package/app/hooks/post-tool-use.sh +5 -3
- package/app/hooks/pre-compact-save.sh +4 -3
- package/app/hooks/quality-gate.sh +12 -1
- package/app/hooks/revert-guard.sh +5 -2
- package/app/hooks/save-session.sh +4 -2
- package/app/hooks/session-end.sh +36 -4
- package/app/hooks/session-start.sh +11 -5
- package/app/hooks.json +10 -0
- package/app/output-filter-policy.json +15 -0
- package/app/skills/brand-voice/scripts/measure.py +7 -5
- package/benchmarks/ecosystem-doctor-snapshot.json +22 -22
- package/benchmarks/output-filter/README.md +11 -0
- package/benchmarks/output-filter/scenarios.json +25 -0
- package/bin/ai-toolkit.js +2 -0
- package/kb/history/completed/native-tool-output-filter-plan.md +517 -0
- package/kb/procedures/release-preparation-sop.md +6 -5
- package/kb/reference/architecture-overview.md +6 -5
- package/kb/reference/cli-reference.md +19 -2
- package/kb/reference/codex-cli-compatibility.md +1 -0
- package/kb/reference/copilot-compatibility.md +173 -0
- package/kb/reference/enterprise-config-guide.md +28 -2
- package/kb/reference/global-install-model.md +1 -0
- package/kb/reference/hooks-catalog.md +105 -16
- package/kb/reference/opencode-compatibility.md +1 -0
- package/kb/reference/supported-tools-registry.md +10 -5
- package/kb/reference/tool-output-filter.md +288 -0
- package/llms-full.txt +1173 -35
- package/llms.txt +3 -0
- package/manifest.json +9 -6
- package/package.json +3 -2
- package/scripts/benchmark_output_filter.py +343 -0
- package/scripts/check_deps.py +16 -0
- package/scripts/claude_app.py +30 -2
- package/scripts/config_cli.py +4 -4
- package/scripts/config_lock.py +120 -14
- package/scripts/config_merger.py +103 -20
- package/scripts/config_resolver.py +22 -2
- package/scripts/config_validator.py +268 -16
- package/scripts/doctor.py +1 -0
- package/scripts/generate_codex_hooks.py +2 -0
- package/scripts/generate_gemini_hooks.py +33 -10
- package/scripts/generate_opencode_plugin.py +28 -12
- package/scripts/install_steps/ai_tools.py +101 -2
- package/scripts/install_steps/hooks.py +25 -1
- package/scripts/output_filter_cli.py +347 -0
- package/scripts/output_filter_hook.py +23 -0
- package/scripts/plugin_schema.py +27 -1
- package/scripts/schemas/ai-toolkit-config.schema.json +83 -5
- package/scripts/session_state.py +156 -42
- package/scripts/tool_output_filter/__init__.py +33 -0
- package/scripts/tool_output_filter/contracts.py +173 -0
- package/scripts/tool_output_filter/engine.py +260 -0
- package/scripts/tool_output_filter/hook_runtime.py +369 -0
- package/scripts/tool_output_filter/input.py +56 -0
- package/scripts/tool_output_filter/invariants.py +40 -0
- package/scripts/tool_output_filter/policy.py +153 -0
- package/scripts/tool_output_filter/profiles/__init__.py +68 -0
- package/scripts/tool_output_filter/profiles/repeat_lines.py +71 -0
- package/scripts/tool_output_filter/profiles/tap_success.py +154 -0
- package/scripts/tool_output_filter/recovery.py +846 -0
- package/scripts/tool_output_filter/telemetry.py +13 -0
- package/scripts/uninstall.py +96 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.16.0 — Native tool-output filtering (2026-07-24)
|
|
11
|
+
|
|
12
|
+
Minor release. Adds a dependency-free, opt-in native tool-output filter for Claude Code and hardens the enterprise configuration, session-isolation, and editor-adapter surfaces around it. The filter ships disabled; `observe` and `safe` are per-project opt-ins. No source catalog count change (44 agents, 108 skills).
|
|
13
|
+
|
|
14
|
+
### BREAKING
|
|
15
|
+
|
|
16
|
+
- **Unknown config keys rejected** — `.softspark-toolkit.json` and `ai-toolkit.config.json` are now validated against a closed set of top-level keys. Files carrying typos or unsupported keys that previously validated will fail `config validate` and `config check` until the key is removed.
|
|
17
|
+
- **Constitution Article VII reserved** — reserved articles expand from I-VI to I-VII, so custom amendments must start at article 8 instead of 7. A base config that defines article 7 was valid before and now aborts validation.
|
|
18
|
+
- **Plugin manifests require `requires`** — `plugin.json` must declare a non-empty `requires` map of non-empty string constraints. Existing manifests without the field fail schema validation.
|
|
19
|
+
- **Lock files go stale on every toolkit version bump** — `.softspark-toolkit.lock.json` records the toolkit version and is reported stale when it differs from the running version. This is intentional integrity design: re-run `install --local` or `update --local` after upgrading to refresh the lock.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Native tool-output filter** — added a dependency-free, opt-in Claude Code `PostToolUse` filter with `off`, `observe`, and `safe` modes. The initial version handles only allowlisted successful Bash test and validation output through deterministic `repeat-lines` and `tap-success` profiles.
|
|
24
|
+
- **Exact ephemeral recovery** — every safe replacement is preceded by a private, bounded, session-scoped copy of the exact native response object. Opaque handles support manual recovery and cleanup without storing commands, paths, session IDs, or raw output in telemetry.
|
|
25
|
+
- **Output-filter operations** — added inspect, status, recover, and clean entry points under `ai-toolkit output-filter`, plus a deterministic benchmark script for the built-in profiles.
|
|
26
|
+
- **Project policy inheritance** — `toolOutputFilter` participates in schema validation, inheritance, merge, lock, local install, and managed project-policy cleanup.
|
|
27
|
+
- **GitHub Copilot compatibility reference** — added `kb/reference/copilot-compatibility.md` documenting the Copilot integration surface, with cross-links from the Codex CLI, opencode, and global-install references.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Hook safety** — `guard-path.sh` now blocks when `jq` is unavailable instead of silently skipping path validation. Codex Bash hooks apply the same path guard during `PreToolUse` and `PermissionRequest`.
|
|
32
|
+
- **Filter eligibility** — allowlisted task names now use exact token boundaries, and recovery-backed filtering rejects unsafe or unbounded native session identifiers instead of creating mismatched cleanup paths.
|
|
33
|
+
- **Session isolation** — edit tracking uses per-session state files, hook adapters preserve native OpenCode and Augment session identifiers, unsafe identifiers receive collision-resistant normalized names, and pre-compact snapshots prefer the payload session identifier.
|
|
34
|
+
- **Editor adapter integrity** — OpenCode propagates guard exit code 2 as a blocked tool execution, Gemini preserves malformed settings, rejects symlinked destinations, and writes valid updates atomically, while Claude app exports omit the Claude Code-only output replacement hook.
|
|
35
|
+
- **Statusline token trend** — the documented baseline file now drives a visible input/output token trend, including payloads without optional cost or effort fields.
|
|
36
|
+
- **Brand-voice fact checks** — missing extracted facts now fail the quality gate, while inline-code extraction is restricted to identifier-shaped values to avoid false positives.
|
|
37
|
+
- **Enterprise configuration** — config and lock readers reject non-object JSON and invalid UTF-8 without traceback, invalid output-filter updates preserve the last valid managed policy, and lock staleness checks cover lock format, toolkit version, source, resolved version, and content integrity. Constitution Articles I through VII remain reserved, `requiredPlugins` materializes enforceable enable intent, and plugin manifests validate non-empty dependency constraints.
|
|
38
|
+
- **Recovery cleanup** — session end and global uninstall remove only validated ai-toolkit recovery artifacts, remain idempotent for foreign-only trees, and abort before other mutations when the recovery namespace is unsafe.
|
|
39
|
+
- **Flaky pack-audit tests** — npm's update-notifier stderr no longer corrupts JSON parsing in the package-content tests (`npm_config_update_notifier=false` in the test environment).
|
|
40
|
+
- **Linux-only test failures** — the jq-unavailable guard test hides `jq` via a stub `PATH` (plain `PATH=/bin` keeps `jq` visible on usrmerge systems where `/bin` is `/usr/bin`), and the foreign-recovery uninstall test now asserts the message `uninstall.py` actually prints. Both passed on macOS only because bash 3.2 does not enforce bare `[[ ]]` bats assertions.
|
|
41
|
+
|
|
42
|
+
Hook entries: 28 → 29. Test count: 1377 → 1477.
|
|
43
|
+
|
|
10
44
|
## v4.15.1 — Copilot profile cleanup (2026-07-15)
|
|
11
45
|
|
|
12
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
|
-
## What's New in v4.
|
|
11
|
+
## What's New in v4.16.0
|
|
12
12
|
|
|
13
|
-
v4.
|
|
13
|
+
v4.16.0 adds a dependency-free, opt-in native tool-output filter for Claude Code and hardens the enterprise configuration surface around it.
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **Native tool-output filter**: opt-in `PostToolUse` filter with `off`, `observe`, and `safe` modes; ships disabled, handles only allowlisted successful Bash test/validation output.
|
|
16
|
+
- **Exact ephemeral recovery**: every safe replacement keeps a private, bounded, session-scoped copy of the original response, recoverable via `ai-toolkit output-filter recover`.
|
|
17
|
+
- **Output-filter CLI**: `ai-toolkit output-filter status|inspect|recover|clean` plus a deterministic benchmark for the built-in profiles.
|
|
18
|
+
- **Stricter config validation**: unknown top-level config keys are rejected, plugin manifests must declare `requires`, and lock files go stale on toolkit version bumps.
|
|
19
|
+
- **GitHub Copilot compatibility reference**: new `kb/reference/copilot-compatibility.md` documenting the Copilot integration surface.
|
|
18
20
|
|
|
19
21
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
20
22
|
|
|
@@ -89,7 +91,10 @@ ai-toolkit claude-app export --verify
|
|
|
89
91
|
```
|
|
90
92
|
|
|
91
93
|
Re-export and re-upload after toolkit or registered-rule updates. Skills work
|
|
92
|
-
in Chat and Cowork; hooks and sub-agents are active only in Cowork.
|
|
94
|
+
in Chat and Cowork; hooks and sub-agents are active only in Cowork. The native
|
|
95
|
+
tool-output replacement hook remains Claude Code-only and is excluded from the
|
|
96
|
+
Claude app archive until that runtime has an independently verified replacement
|
|
97
|
+
contract.
|
|
93
98
|
|
|
94
99
|
### Install Profiles
|
|
95
100
|
|
|
@@ -140,7 +145,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
|
|
|
140
145
|
| `skills/` (hybrid) | 30 | Slash commands with agent knowledge base |
|
|
141
146
|
| `skills/` (knowledge) | 46 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
|
|
142
147
|
| `agents/` | 44 | Specialized agents across 10 categories |
|
|
143
|
-
| `hooks/` |
|
|
148
|
+
| `hooks/` | 29 entries / 14 events + statusLine | Quality gates, path safety, prompt governance, loop guard, output filtering, session lifecycle |
|
|
144
149
|
| `plugins/` | 11 packs | Opt-in domain bundles (security, research, frontend, enterprise, 6 language packs) |
|
|
145
150
|
| `constitution.md` | 7 articles | Machine-enforced safety rules |
|
|
146
151
|
| `rules/` | auto-synced | Global/project rule files for Claude and other editors |
|
|
@@ -156,7 +161,7 @@ ai-toolkit/
|
|
|
156
161
|
│ ├── agents/ # 44 agent definitions
|
|
157
162
|
│ ├── skills/ # 108 skills (task / hybrid / knowledge)
|
|
158
163
|
│ ├── rules/ # Source rules synced into Claude/editor rule files
|
|
159
|
-
│ ├── hooks/ # Hook scripts (
|
|
164
|
+
│ ├── hooks/ # Hook scripts (29 entries, 14 lifecycle events)
|
|
160
165
|
│ ├── claude-app/ # Generated Chat/Cowork plugin rules, hooks, instructions
|
|
161
166
|
│ ├── plugins/ # 11 experimental plugin packs (opt-in)
|
|
162
167
|
│ ├── output-styles/ # System prompt output style overrides
|
|
@@ -164,7 +169,7 @@ ai-toolkit/
|
|
|
164
169
|
│ └── ARCHITECTURE.md # Full system design
|
|
165
170
|
├── kb/ # Reference docs, procedures, plans
|
|
166
171
|
├── scripts/ # Validation, install, evaluation scripts
|
|
167
|
-
├── tests/ # Bats test suite (
|
|
172
|
+
├── tests/ # Bats and Python test suite (1477 tests)
|
|
168
173
|
└── CHANGELOG.md
|
|
169
174
|
```
|
|
170
175
|
|
|
@@ -176,7 +181,9 @@ ai-toolkit/
|
|
|
176
181
|
|
|
177
182
|
**Machine-enforced constitution** — 7-article safety constitution enforced via `PreToolUse` hooks that actually block `rm -rf`, `DROP TABLE`, and irreversible operations. Not just documentation.
|
|
178
183
|
|
|
179
|
-
**29 lifecycle
|
|
184
|
+
**29 lifecycle hook entries:** Executable handlers across 14 events (SessionStart → SessionEnd, plus InstructionsLoaded + ConfigChange). Guards, governance, quality gates, session persistence, MCP health checks, revert protection, test-cohesion enforcement, loop guard, search-first discipline, and opt-in output filtering. See [Hooks Catalog](kb/reference/hooks-catalog.md).
|
|
185
|
+
|
|
186
|
+
**Native tool-output filtering** — dependency-free, post-execution filtering for successful Claude Code Bash output, shipped **disabled by default** (`off`). Neither `off` nor `observe` ever replaces output; opt-in `safe` mode replaces only validated `repeat-lines` or `tap-success` results after exact ephemeral recovery is available. Failures, diagnostics, unsupported payloads, and unavailable recovery always pass through unchanged. See [Tool Output Filter](kb/reference/tool-output-filter.md).
|
|
180
187
|
|
|
181
188
|
**Security scanning** — `/skill-audit` for code-level risks, `/cve-scan` for dependency CVEs. Both CI-ready with exit codes.
|
|
182
189
|
|
|
@@ -291,6 +298,7 @@ Need multi-agent coordination?
|
|
|
291
298
|
| Ecosystem Comparison | [kb/reference/comparison.md](kb/reference/comparison.md) |
|
|
292
299
|
| Codex CLI Compatibility | [kb/reference/codex-cli-compatibility.md](kb/reference/codex-cli-compatibility.md) |
|
|
293
300
|
| opencode Compatibility | [kb/reference/opencode-compatibility.md](kb/reference/opencode-compatibility.md) |
|
|
301
|
+
| GitHub Copilot Compatibility | [kb/reference/copilot-compatibility.md](kb/reference/copilot-compatibility.md) |
|
|
294
302
|
| Maintenance SOP | [kb/procedures/maintenance-sop.md](kb/procedures/maintenance-sop.md) |
|
|
295
303
|
|
|
296
304
|
---
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ai-toolkit",
|
|
4
4
|
"displayName": "AI Toolkit",
|
|
5
5
|
"description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.16.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SoftSpark",
|
|
9
9
|
"url": "https://github.com/softspark"
|
package/app/ARCHITECTURE.md
CHANGED
|
@@ -8,7 +8,7 @@ Universal multi-agent system for software development. Works across all reposito
|
|
|
8
8
|
|-----------|-------|
|
|
9
9
|
| Agents | See agents catalog |
|
|
10
10
|
| Skills | See skills catalog |
|
|
11
|
-
| Hooks | 14 events /
|
|
11
|
+
| Hooks | 14 events / 29 entries (SessionStart ×2, Notification ×1, PreToolUse ×5, UserPromptSubmit ×2, PostToolUse ×6, Stop ×4, TaskCompleted ×1, TeammateIdle ×1, SubagentStart ×1, SubagentStop ×1, PreCompact ×2, SessionEnd ×1, InstructionsLoaded ×1, ConfigChange ×1) plus statusLine |
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -333,6 +333,7 @@ Lead Session (You)
|
|
|
333
333
|
| `UserPromptSubmit` | Before prompt execution | Usage tracking (skill invocations) |
|
|
334
334
|
| `PostToolUse` | After edit/write tools | Lightweight validation reminder |
|
|
335
335
|
| `PostToolUse` | After search tools | Clears the per-session search-first flag |
|
|
336
|
+
| `PostToolUse` | After successful Bash tools | Observes or safely replaces eligible text output after exact recovery is stored; Claude Code only |
|
|
336
337
|
| `Stop` | After Claude response | Multi-language quality check + saves session context |
|
|
337
338
|
| `Stop` | After Claude response | Blocks skipped search-first only when a real MCP/Web search provider is configured, with Codex log fallback |
|
|
338
339
|
| `TaskCompleted` | Teammate marks task done | Multi-language lint + type check (blocking) |
|
|
@@ -341,7 +342,7 @@ Lead Session (You)
|
|
|
341
342
|
| `SubagentStop` | Subagent completes | Handoff checklist for spawned subagents |
|
|
342
343
|
| `Notification` | Claude notification | OS notification |
|
|
343
344
|
| `PreCompact` | Before compaction | Saves context before compaction boundary |
|
|
344
|
-
| `SessionEnd` | Claude session ends |
|
|
345
|
+
| `SessionEnd` | Claude session ends | Cleans owned output recovery, then writes the next-session handoff snapshot |
|
|
345
346
|
|
|
346
347
|
---
|
|
347
348
|
|
|
@@ -376,7 +377,7 @@ The `inject_section_cli.py` script provides a stable marker-based injection API.
|
|
|
376
377
|
`scripts/compile_slm.py` compiles the full toolkit (20K+ tokens) into a minimal system prompt for Small Language Models (2K-16K tokens). Pipeline: Parse → Score → Compress → Pack → Emit. Supports 4 compression levels (ultra-light, light, standard, extended), 4 output formats (raw, ollama, json-string, aider), persona-aware scoring, and language-aware rule filtering. Profile `offline-slm` in `manifest.json`. Constitution is always included (non-negotiable).
|
|
377
378
|
|
|
378
379
|
### Config Inheritance (`extends`)
|
|
379
|
-
`scripts/config_resolver.py`, `config_merger.py`, `config_validator.py`, `config_cli.py`, `config_scaffold.py`, `config_lock.py`. Enterprise configuration inheritance via `.softspark-toolkit.json` `extends` field. Resolves base configs from npm packages, Git URLs, or local paths. Layered deep merge with constitution immutability (Articles I-VII absolute), enforce constraints (`requiredAgents`, `forbidOverride`, `minHookProfile`), override validation (`override: true` + justification), and lock file (`.softspark-toolkit.lock.json`). CLI: `config validate`, `config diff`, `config init`, `config create-base`, `config check`. Integrated into `install --local` and `update --local` flows.
|
|
380
|
+
`scripts/config_resolver.py`, `config_merger.py`, `config_validator.py`, `config_cli.py`, `config_scaffold.py`, `config_lock.py`. Enterprise configuration inheritance via `.softspark-toolkit.json` `extends` field. Resolves base configs from npm packages, Git URLs, or local paths. Layered deep merge with constitution immutability (Articles I-VII absolute), enforce constraints (`requiredAgents`, `requiredPlugins`, `forbidOverride`, `minHookProfile`), override validation (`override: true` + justification), and a source/version/integrity lock file (`.softspark-toolkit.lock.json`). `requiredPlugins` materializes configuration intent and does not install plugin packs. CLI: `config validate`, `config diff`, `config init`, `config create-base`, `config check`. Integrated into `install --local` and `update --local` flows.
|
|
380
381
|
|
|
381
382
|
### Project Registry
|
|
382
383
|
`scripts/install_steps/project_registry.py`, `scripts/update_projects.py`, `scripts/projects_cli.py`. Tracks all `--local` installed projects in `~/.softspark/ai-toolkit/projects.json`. `ai-toolkit update` propagates to all registered projects in parallel via `ThreadPoolExecutor`. CLI: `ai-toolkit projects`, `--prune`, `remove <path>`.
|
package/app/hooks/_hook-io.sh
CHANGED
|
@@ -20,8 +20,11 @@ hook_prompt() {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
hook_session_id() {
|
|
23
|
-
local sid
|
|
24
|
-
sid=$(hook_json '.session_id // empty')
|
|
23
|
+
local sid raw_sid digest
|
|
24
|
+
sid=$(hook_json '.session_id // .conversation_id // .conversationId // empty')
|
|
25
|
+
if [ -z "$sid" ] || [ "$sid" = "null" ]; then
|
|
26
|
+
sid="${CLAUDE_SESSION_ID:-}"
|
|
27
|
+
fi
|
|
25
28
|
if [ -z "$sid" ] || [ "$sid" = "null" ]; then
|
|
26
29
|
local tp
|
|
27
30
|
tp=$(hook_json '.transcript_path // empty')
|
|
@@ -30,7 +33,19 @@ hook_session_id() {
|
|
|
30
33
|
fi
|
|
31
34
|
fi
|
|
32
35
|
[ -z "$sid" ] && sid="default"
|
|
33
|
-
|
|
36
|
+
raw_sid="$sid"
|
|
37
|
+
sid=$(printf '%s' "$sid" | LC_ALL=C tr -c 'a-zA-Z0-9_-' '_')
|
|
38
|
+
if [ "$sid" != "$raw_sid" ] || [ "${#sid}" -gt 160 ]; then
|
|
39
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
40
|
+
digest=$(printf '%s' "$raw_sid" | shasum -a 256 | cut -d ' ' -f 1)
|
|
41
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
42
|
+
digest=$(printf '%s' "$raw_sid" | sha256sum | cut -d ' ' -f 1)
|
|
43
|
+
else
|
|
44
|
+
digest=$(printf '%s' "$raw_sid" | cksum | cut -d ' ' -f 1)
|
|
45
|
+
fi
|
|
46
|
+
sid="${sid:0:135}-${digest:0:24}"
|
|
47
|
+
fi
|
|
48
|
+
printf '%s' "$sid"
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
hook_command() {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
# AI_TOOLKIT_STATUSLINE_NO_GIT — "1" hides git segment
|
|
27
27
|
# AI_TOOLKIT_STATUSLINE_NO_EFFORT — "1" hides effort segment
|
|
28
28
|
# AI_TOOLKIT_STATUSLINE_SHOW_COST — "1" appends Claude Code's reported cost
|
|
29
|
+
# AI_TOOLKIT_STATUSLINE_BASELINE — totals JSON file used for token trend
|
|
29
30
|
# AI_TOOLKIT_STATUSLINE_DUMP — "1" writes stdin to /tmp/cc-statusline-input.json
|
|
30
31
|
#
|
|
31
32
|
# Performance: ~50ms (single python3 parse of stdin, one git invocation).
|
|
@@ -62,7 +63,7 @@ if [ "${AI_TOOLKIT_STATUSLINE_DUMP:-0}" = "1" ] && [ -n "$INPUT" ]; then
|
|
|
62
63
|
fi
|
|
63
64
|
|
|
64
65
|
PARSED="$(printf '%s' "$INPUT" | python3 -c '
|
|
65
|
-
import json, sys
|
|
66
|
+
import json, os, sys
|
|
66
67
|
def get(d, path):
|
|
67
68
|
for k in path.split("."):
|
|
68
69
|
d = d.get(k) if isinstance(d, dict) else None
|
|
@@ -77,6 +78,11 @@ def fmt_tokens(n):
|
|
|
77
78
|
if n >= 1000:
|
|
78
79
|
return f"{n/1000:.1f}k"
|
|
79
80
|
return str(n)
|
|
81
|
+
def as_int(n):
|
|
82
|
+
try:
|
|
83
|
+
return int(n)
|
|
84
|
+
except (TypeError, ValueError):
|
|
85
|
+
return 0
|
|
80
86
|
try:
|
|
81
87
|
d = json.loads(sys.stdin.read() or "{}")
|
|
82
88
|
except Exception:
|
|
@@ -84,18 +90,34 @@ except Exception:
|
|
|
84
90
|
cwd = get(d, "cwd") or ""
|
|
85
91
|
model = get(d, "model.display_name") or get(d, "model.id") or ""
|
|
86
92
|
ctx_pct = get(d, "context_window.used_percentage")
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
in_raw = get(d, "context_window.total_input_tokens")
|
|
94
|
+
out_raw = get(d, "context_window.total_output_tokens")
|
|
95
|
+
in_tok = fmt_tokens(in_raw)
|
|
96
|
+
out_tok = fmt_tokens(out_raw)
|
|
89
97
|
cost = get(d, "cost.total_cost_usd")
|
|
90
98
|
try:
|
|
91
99
|
cost_str = f"{float(cost):.2f}" if cost != "" else ""
|
|
92
100
|
except (TypeError, ValueError):
|
|
93
101
|
cost_str = ""
|
|
94
102
|
effort = get(d, "effort.level") or ""
|
|
95
|
-
|
|
103
|
+
trend = ""
|
|
104
|
+
baseline_path = os.environ.get("AI_TOOLKIT_STATUSLINE_BASELINE", "")
|
|
105
|
+
if baseline_path:
|
|
106
|
+
try:
|
|
107
|
+
with open(os.path.expanduser(baseline_path), encoding="utf-8") as baseline_file:
|
|
108
|
+
baseline = json.load(baseline_file)
|
|
109
|
+
baseline_total = baseline.get("total") if isinstance(baseline, dict) else None
|
|
110
|
+
if isinstance(baseline_total, (int, float)) and baseline_total > 0:
|
|
111
|
+
delta = (as_int(in_raw) + as_int(out_raw) - baseline_total) / baseline_total
|
|
112
|
+
arrow = "↓" if delta < 0 else "↑" if delta > 0 else "·"
|
|
113
|
+
trend = f"{arrow}{abs(delta) * 100:.0f}%"
|
|
114
|
+
except (OSError, json.JSONDecodeError, TypeError, ValueError):
|
|
115
|
+
pass
|
|
116
|
+
fields = (cwd, model, ctx_pct, in_tok, out_tok, cost_str, effort, trend)
|
|
117
|
+
print("\x1f".join(str(field) for field in fields))
|
|
96
118
|
' 2>/dev/null)"
|
|
97
119
|
|
|
98
|
-
IFS=$'\
|
|
120
|
+
IFS=$'\x1f' read -r CWD MODEL_NAME CTX_USED IN_TOK OUT_TOK COST EFFORT TREND <<< "$PARSED"
|
|
99
121
|
[ -z "$CWD" ] && CWD="$PWD"
|
|
100
122
|
|
|
101
123
|
# ── Segment: prompt + dir ────────────────────────────────────────────────────
|
|
@@ -162,6 +184,9 @@ SEG_TOKENS=""
|
|
|
162
184
|
if [ "${AI_TOOLKIT_STATUSLINE_NO_TOKENS:-0}" != "1" ] && [ -n "$IN_TOK" ] && \
|
|
163
185
|
{ [ "$IN_TOK" != "0" ] || [ "$OUT_TOK" != "0" ]; }; then
|
|
164
186
|
SEG_TOKENS=" ${C_BOLD_GREEN}\xe2\x86\x91${IN_TOK}${C_RESET} ${C_RED}\xe2\x86\x93${OUT_TOK}${C_RESET}"
|
|
187
|
+
if [ -n "$TREND" ]; then
|
|
188
|
+
SEG_TOKENS+=" ${C_DIM}trend:${C_RESET}${TREND}"
|
|
189
|
+
fi
|
|
165
190
|
if [ "${AI_TOOLKIT_STATUSLINE_SHOW_COST:-0}" = "1" ] && [ -n "$COST" ]; then
|
|
166
191
|
SEG_TOKENS+=" ${C_BOLD_GREEN}\$${COST}${C_RESET}"
|
|
167
192
|
fi
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude PostToolUse adapter for the native tool-output filter.
|
|
3
|
+
|
|
4
|
+
HOOK_SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
HOOK_DIR="${HOOK_SOURCE%/*}"
|
|
6
|
+
[[ "$HOOK_DIR" == "$HOOK_SOURCE" ]] && HOOK_DIR="."
|
|
7
|
+
# shellcheck source=_profile-check.sh
|
|
8
|
+
source "$HOOK_DIR/_profile-check.sh"
|
|
9
|
+
|
|
10
|
+
OWNER_MARKER="ai-toolkit-output-filter-policy-v1"
|
|
11
|
+
GLOBAL_POLICY="$HOME/.softspark/ai-toolkit/hooks/output-filter-policy.json"
|
|
12
|
+
PROJECTS_REGISTRY="$HOME/.softspark/ai-toolkit/projects.json"
|
|
13
|
+
|
|
14
|
+
is_regular_file() {
|
|
15
|
+
[[ -f "$1" && -r "$1" && ! -L "$1" ]]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# A project policy is trusted only for projects the user registered via
|
|
19
|
+
# `ai-toolkit install --local`. The owner marker alone is a public constant,
|
|
20
|
+
# so a cloned repo must never be able to self-enable filtering with it.
|
|
21
|
+
is_registered_project() {
|
|
22
|
+
is_regular_file "$PROJECTS_REGISTRY" &&
|
|
23
|
+
grep -qF "\"$1\"" "$PROJECTS_REGISTRY" 2>/dev/null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if [[ "${AI_TOOLKIT_OUTPUT_FILTER_DISABLE:-}" == "1" ]]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [[ -n "${AI_TOOLKIT_OUTPUT_FILTER_POLICY:-}" ]]; then
|
|
31
|
+
POLICY_PATH="$AI_TOOLKIT_OUTPUT_FILTER_POLICY"
|
|
32
|
+
if ! is_regular_file "$POLICY_PATH"; then
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
else
|
|
36
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
37
|
+
PROJECT_POLICY="$PROJECT_ROOT/.claude/ai-toolkit-output-filter.json"
|
|
38
|
+
PROJECT_OWNER="$PROJECT_ROOT/.claude/.ai-toolkit-output-filter.owner"
|
|
39
|
+
if [[ -L "$PROJECT_ROOT" || -L "$PROJECT_ROOT/.claude" ]]; then
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
if is_registered_project "$PROJECT_ROOT" &&
|
|
43
|
+
is_regular_file "$PROJECT_OWNER" &&
|
|
44
|
+
[[ "$(<"$PROJECT_OWNER")" == "$OWNER_MARKER" ]]; then
|
|
45
|
+
if ! is_regular_file "$PROJECT_POLICY"; then
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
POLICY_PATH="$PROJECT_POLICY"
|
|
49
|
+
else
|
|
50
|
+
POLICY_PATH="$GLOBAL_POLICY"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if ! is_regular_file "$POLICY_PATH"; then
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
POLICY_CONTENT="$(<"$POLICY_PATH")" || exit 0
|
|
58
|
+
if [[ ${#POLICY_CONTENT} -gt 65536 ]]; then
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
MODE_OFF_PATTERN='"mode"[[:space:]]*:[[:space:]]*"off"'
|
|
62
|
+
MODE_ACTIVE_PATTERN='"mode"[[:space:]]*:[[:space:]]*"(observe|safe)"'
|
|
63
|
+
if [[ "$POLICY_CONTENT" =~ $MODE_OFF_PATTERN ]]; then
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
if [[ ! "$POLICY_CONTENT" =~ $MODE_ACTIVE_PATTERN ]]; then
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
RUNTIME_PATH="${AI_TOOLKIT_OUTPUT_FILTER_HOOK_RUNTIME:-${AI_TOOLKIT_OUTPUT_FILTER_CLI:-$HOME/.softspark/ai-toolkit/scripts/output_filter_hook.py}}"
|
|
71
|
+
if ! is_regular_file "$RUNTIME_PATH"; then
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
python3 -S "$RUNTIME_PATH" hook --policy "$POLICY_PATH" 2>/dev/null || true
|
|
75
|
+
|
|
76
|
+
exit 0
|
|
@@ -11,12 +11,12 @@ source "$(dirname "$0")/_profile-check.sh"
|
|
|
11
11
|
LOG_DIR="$HOME/.softspark/ai-toolkit"
|
|
12
12
|
LOG_FILE="$LOG_DIR/governance.log"
|
|
13
13
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
14
|
-
SESSION="${CLAUDE_SESSION_ID:-unknown}"
|
|
15
14
|
|
|
16
15
|
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
17
16
|
INPUT=$(cat)
|
|
18
17
|
# shellcheck source=_hook-io.sh
|
|
19
18
|
source "$(dirname "$0")/_hook-io.sh"
|
|
19
|
+
SESSION=$(hook_session_id)
|
|
20
20
|
TOOL_NAME=$(hook_tool_name)
|
|
21
21
|
|
|
22
22
|
if [ -z "$TOOL_NAME" ]; then
|
package/app/hooks/guard-path.sh
CHANGED
|
@@ -14,8 +14,8 @@ REAL_HOME="$HOME"
|
|
|
14
14
|
|
|
15
15
|
# Verify jq is available — required for JSON parsing
|
|
16
16
|
if ! command -v jq >/dev/null 2>&1; then
|
|
17
|
-
echo "
|
|
18
|
-
exit
|
|
17
|
+
echo "BLOCKED: guard-path.sh requires jq but it is not installed. Path validation cannot run safely." >&2
|
|
18
|
+
exit 2
|
|
19
19
|
fi
|
|
20
20
|
|
|
21
21
|
# Collect ALL path values from tool input into a single list (one per line).
|
|
@@ -12,18 +12,20 @@ source "$(dirname "$0")/_locate-toolkit.sh"
|
|
|
12
12
|
# shellcheck source=_hook-io.sh
|
|
13
13
|
source "$(dirname "$0")/_hook-io.sh"
|
|
14
14
|
|
|
15
|
-
# Read from stdin (Claude Code passes JSON with .tool_name, .tool_input)
|
|
15
|
+
# Read from stdin (Claude Code passes JSON with .tool_name, .tool_input).
|
|
16
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
16
17
|
INPUT=$(cat)
|
|
17
18
|
TOOL_NAME=$(hook_tool_name)
|
|
18
19
|
[ -z "$TOOL_NAME" ] && TOOL_NAME="unknown"
|
|
19
20
|
FILE_PATH=$(hook_file_path)
|
|
20
|
-
SESSION_ID=$(
|
|
21
|
+
SESSION_ID=$(hook_session_id)
|
|
22
|
+
SESSION_ARGS=(--session-id "$SESSION_ID")
|
|
21
23
|
|
|
22
24
|
# Append edit to session state (used by revert-guard, test-cohesion, quality-gate).
|
|
23
25
|
if [ -n "$FILE_PATH" ] && [ -n "$TOOLKIT_DIR" ] && command -v python3 >/dev/null 2>&1; then
|
|
24
26
|
python3 "$TOOLKIT_DIR/scripts/session_state.py" append \
|
|
25
27
|
--tool "$TOOL_NAME" --path "$FILE_PATH" \
|
|
26
|
-
${
|
|
28
|
+
"${SESSION_ARGS[@]}" >/dev/null 2>&1 || true
|
|
27
29
|
fi
|
|
28
30
|
|
|
29
31
|
if [ -z "$FILE_PATH" ]; then
|
|
@@ -7,19 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
|
+
# shellcheck source=_hook-io.sh
|
|
11
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
10
12
|
|
|
11
13
|
SAVE_DIR="$HOME/.softspark/ai-toolkit/compactions"
|
|
12
14
|
mkdir -p "$SAVE_DIR"
|
|
13
15
|
|
|
14
16
|
# Read from stdin (Claude Code passes JSON with .session_id)
|
|
17
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
15
18
|
INPUT=$(cat)
|
|
16
19
|
TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S")
|
|
17
|
-
SESSION=$(
|
|
18
|
-
[ -z "$SESSION" ] && SESSION="$$"
|
|
20
|
+
SESSION=$(hook_session_id)
|
|
19
21
|
SAVE_FILE="$SAVE_DIR/${TIMESTAMP}_${SESSION}.txt"
|
|
20
22
|
|
|
21
23
|
# Gather context
|
|
22
|
-
SESSION="${CLAUDE_SESSION_ID:-unknown}"
|
|
23
24
|
WORKDIR="$(pwd)"
|
|
24
25
|
BRANCH=""
|
|
25
26
|
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
# shellcheck source=_locate-toolkit.sh
|
|
11
11
|
source "$(dirname "$0")/_locate-toolkit.sh"
|
|
12
|
+
# shellcheck source=_hook-io.sh
|
|
13
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
14
|
+
|
|
15
|
+
INPUT=""
|
|
16
|
+
if [ ! -t 0 ]; then
|
|
17
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
fi
|
|
20
|
+
SESSION_ID=$(hook_session_id)
|
|
21
|
+
SESSION_ARGS=(--session-id "$SESSION_ID")
|
|
12
22
|
|
|
13
23
|
run_required() {
|
|
14
24
|
local label="$1"
|
|
@@ -44,7 +54,8 @@ cohesion_for_session_edits() {
|
|
|
44
54
|
command -v python3 >/dev/null 2>&1 || return 0
|
|
45
55
|
|
|
46
56
|
local edits
|
|
47
|
-
edits=$(python3 "$TOOLKIT_DIR/scripts/session_state.py" list
|
|
57
|
+
edits=$(python3 "$TOOLKIT_DIR/scripts/session_state.py" list \
|
|
58
|
+
"${SESSION_ARGS[@]}" 2>/dev/null)
|
|
48
59
|
[ -z "$edits" ] && return 0
|
|
49
60
|
|
|
50
61
|
# shellcheck disable=SC2206 # word-splitting is intentional, paths have no spaces
|
|
@@ -25,6 +25,8 @@ source "$(dirname "$0")/_hook-io.sh"
|
|
|
25
25
|
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
26
26
|
INPUT=$(cat)
|
|
27
27
|
COMMAND=$(hook_command)
|
|
28
|
+
SESSION_ID=$(hook_session_id)
|
|
29
|
+
SESSION_ARGS=(--session-id "$SESSION_ID")
|
|
28
30
|
|
|
29
31
|
[ -z "$COMMAND" ] && exit 0
|
|
30
32
|
[ "${CLAUDE_REVERT_OK:-0}" = "1" ] && exit 0
|
|
@@ -54,7 +56,7 @@ EOF
|
|
|
54
56
|
|
|
55
57
|
# git reset --hard / git clean -fd: clobber-style, scope = all session edits.
|
|
56
58
|
if printf '%s' "$COMMAND" | grep -Eq 'reset[[:space:]]+(-+[A-Za-z-]+[[:space:]]+)*--hard|clean[[:space:]]+-[A-Za-z]*[df]'; then
|
|
57
|
-
edited=$(python3 "$TOOLKIT_DIR/scripts/session_state.py" list 2>/dev/null | head -10)
|
|
59
|
+
edited=$(python3 "$TOOLKIT_DIR/scripts/session_state.py" list "${SESSION_ARGS[@]}" 2>/dev/null | head -10)
|
|
58
60
|
if [ -n "$edited" ]; then
|
|
59
61
|
_block "destructive 'git reset --hard' / 'git clean' with session edits in tree" "$edited"
|
|
60
62
|
fi
|
|
@@ -73,7 +75,8 @@ if printf '%s' "$COMMAND" | grep -Eq '(checkout|restore)([[:space:]].*)?[[:space
|
|
|
73
75
|
/*) abs="$f" ;;
|
|
74
76
|
*) abs="$PWD/$f" ;;
|
|
75
77
|
esac
|
|
76
|
-
if python3 "$TOOLKIT_DIR/scripts/session_state.py" was-edited "$abs"
|
|
78
|
+
if python3 "$TOOLKIT_DIR/scripts/session_state.py" was-edited "$abs" \
|
|
79
|
+
"${SESSION_ARGS[@]}" >/dev/null 2>&1; then
|
|
77
80
|
blocked="$blocked $f"
|
|
78
81
|
fi
|
|
79
82
|
done
|
|
@@ -8,15 +8,17 @@
|
|
|
8
8
|
source "$(dirname "$0")/_profile-check.sh"
|
|
9
9
|
# shellcheck source=_session-paths.sh
|
|
10
10
|
source "$(dirname "$0")/_session-paths.sh"
|
|
11
|
+
# shellcheck source=_hook-io.sh
|
|
12
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
11
13
|
|
|
12
14
|
# Read from stdin (Claude Code passes JSON with .session_id, .last_assistant_message)
|
|
13
15
|
INPUT=$(cat)
|
|
14
|
-
SESSION_ID=$(
|
|
16
|
+
SESSION_ID=$(hook_session_id)
|
|
15
17
|
LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // "No summary available"' 2>/dev/null | head -5)
|
|
16
18
|
|
|
17
19
|
SESSION_FILE="$SESSION_CONTEXT_FILE"
|
|
18
20
|
|
|
19
|
-
if [
|
|
21
|
+
if [ "$SESSION_ID" != "default" ]; then
|
|
20
22
|
mkdir -p "$SESSION_DIR"
|
|
21
23
|
|
|
22
24
|
# Gather git state for richer context
|
package/app/hooks/session-end.sh
CHANGED
|
@@ -3,12 +3,45 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Fires on: SessionEnd
|
|
5
5
|
# Matcher: all
|
|
6
|
-
#
|
|
6
|
+
# The handoff snapshot is skipped for the minimal profile. Owned recovery
|
|
7
|
+
# cleanup still runs for every profile.
|
|
7
8
|
|
|
8
|
-
# shellcheck source=_profile-check.sh
|
|
9
|
-
source "$(dirname "$0")/_profile-check.sh"
|
|
10
9
|
# shellcheck source=_session-paths.sh
|
|
11
10
|
source "$(dirname "$0")/_session-paths.sh"
|
|
11
|
+
# shellcheck source=_hook-io.sh
|
|
12
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
13
|
+
|
|
14
|
+
# Cleanup must run before _profile-check.sh can exit for the minimal profile.
|
|
15
|
+
INPUT=""
|
|
16
|
+
if [ ! -t 0 ]; then
|
|
17
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
fi
|
|
20
|
+
SESSION_ID=$(hook_session_id)
|
|
21
|
+
RECOVERY_ROOT="$SESSION_DIR/output-filter"
|
|
22
|
+
OUTPUT_FILTER_CLI="${AI_TOOLKIT_OUTPUT_FILTER_CLI:-$HOME/.softspark/ai-toolkit/scripts/output_filter_cli.py}"
|
|
23
|
+
if [ "$SESSION_ID" != "default" ] &&
|
|
24
|
+
[ -d "$RECOVERY_ROOT" ] &&
|
|
25
|
+
[ ! -L "$RECOVERY_ROOT" ] &&
|
|
26
|
+
[ -f "$OUTPUT_FILTER_CLI" ] &&
|
|
27
|
+
[ ! -L "$OUTPUT_FILTER_CLI" ] &&
|
|
28
|
+
command -v python3 >/dev/null 2>&1; then
|
|
29
|
+
python3 -S "$OUTPUT_FILTER_CLI" clean \
|
|
30
|
+
--base-directory "$SESSION_DIR" \
|
|
31
|
+
--session-id "$SESSION_ID" >/dev/null 2>&1 || true
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
SESSION_STATE_CLI="${AI_TOOLKIT_SESSION_STATE_CLI:-$HOME/.softspark/ai-toolkit/scripts/session_state.py}"
|
|
35
|
+
if [ "$SESSION_ID" != "default" ] &&
|
|
36
|
+
[ -f "$SESSION_STATE_CLI" ] &&
|
|
37
|
+
[ ! -L "$SESSION_STATE_CLI" ] &&
|
|
38
|
+
command -v python3 >/dev/null 2>&1; then
|
|
39
|
+
python3 -S "$SESSION_STATE_CLI" clean \
|
|
40
|
+
--session-id "$SESSION_ID" >/dev/null 2>&1 || true
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# shellcheck source=_profile-check.sh
|
|
44
|
+
source "$(dirname "$0")/_profile-check.sh"
|
|
12
45
|
|
|
13
46
|
SESSION_FILE="$SESSION_CONTEXT_FILE"
|
|
14
47
|
HANDOFF_FILE="$SESSION_END_FILE"
|
|
@@ -35,4 +68,3 @@ else
|
|
|
35
68
|
fi
|
|
36
69
|
|
|
37
70
|
exit 0
|
|
38
|
-
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
source "$(dirname "$0")/_locate-toolkit.sh"
|
|
9
9
|
# shellcheck source=_session-paths.sh
|
|
10
10
|
source "$(dirname "$0")/_session-paths.sh"
|
|
11
|
+
# shellcheck source=_hook-io.sh
|
|
12
|
+
source "$(dirname "$0")/_hook-io.sh"
|
|
11
13
|
|
|
12
14
|
emit_context() {
|
|
13
15
|
[ "${AI_TOOLKIT_HOOK_QUIET:-0}" = "1" ] && return 0
|
|
@@ -20,14 +22,18 @@ emit_context "MANDATORY: Before answering ANY technical question, apply ALL rule
|
|
|
20
22
|
emit_context "REMINDER: When writing features or fixing bugs, ensure tests cover the changes. When modifying API, config, or setup, update relevant documentation. Propose these steps to the user — do not silently skip them."
|
|
21
23
|
|
|
22
24
|
# 1a. Reset per-session edit state (used by revert-guard, test-cohesion, quality-gate)
|
|
23
|
-
|
|
25
|
+
INPUT=""
|
|
24
26
|
if [ ! -t 0 ]; then
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
# shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
|
|
28
|
+
INPUT="$(cat)"
|
|
27
29
|
fi
|
|
28
|
-
|
|
30
|
+
SESSION_ID_INPUT=$(hook_session_id)
|
|
31
|
+
SESSION_SOURCE=$(hook_json '.source // empty')
|
|
32
|
+
if [ "$SESSION_SOURCE" != "compact" ] &&
|
|
33
|
+
[ -n "$TOOLKIT_DIR" ] &&
|
|
34
|
+
command -v python3 >/dev/null 2>&1; then
|
|
29
35
|
python3 "$TOOLKIT_DIR/scripts/session_state.py" reset \
|
|
30
|
-
|
|
36
|
+
--session-id "$SESSION_ID_INPUT" >/dev/null 2>&1 || true
|
|
31
37
|
fi
|
|
32
38
|
|
|
33
39
|
# 1b. GC stale per-session search-required flags (older than 60 min)
|
package/app/hooks.json
CHANGED
|
@@ -158,6 +158,16 @@
|
|
|
158
158
|
"command": "\"$HOME/.softspark/ai-toolkit/hooks/search-tracker.sh\""
|
|
159
159
|
}
|
|
160
160
|
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"_source": "ai-toolkit",
|
|
164
|
+
"matcher": "Bash",
|
|
165
|
+
"hooks": [
|
|
166
|
+
{
|
|
167
|
+
"type": "command",
|
|
168
|
+
"command": "\"$HOME/.softspark/ai-toolkit/hooks/filter-tool-output.sh\""
|
|
169
|
+
}
|
|
170
|
+
]
|
|
161
171
|
}
|
|
162
172
|
],
|
|
163
173
|
"Stop": [
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mode": "off",
|
|
3
|
+
"profiles": [
|
|
4
|
+
"repeat-lines",
|
|
5
|
+
"tap-success"
|
|
6
|
+
],
|
|
7
|
+
"maxInputBytes": 8388608,
|
|
8
|
+
"minSavingsBytes": 1024,
|
|
9
|
+
"minSavingsRatio": 0.15,
|
|
10
|
+
"recovery": {
|
|
11
|
+
"mode": "ephemeral",
|
|
12
|
+
"ttlMinutes": 60,
|
|
13
|
+
"maxSessionBytes": 33554432
|
|
14
|
+
}
|
|
15
|
+
}
|