From 3924718c95217931769f9fdd61129400952a1452 Mon Sep 17 00:00:00 2001 From: Luis Salgado Date: Sat, 18 Apr 2026 14:38:37 +0200 Subject: [PATCH 1/2] chore: release v4.4.2 (dashboard dict-shape fix) CHANGELOG: add v4.4.2 entry documenting the regression fix from #7. README: bump 4.4.1 -> 4.4.2 (tests stay at 99, PR #7 was code-only). Fix resuelve issue #6 (crash on /dashboard-sinapsis first run). Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 7 +++++++ README.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fcaeba..7d0e6c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v4.4.2 (2026-04-18) + +### Fixed +- **`_generate-dashboard.py` crashed on `_catalog.json` dict schema** (regression from v4.4.0): `collect_skills()` iterated `cat` assuming a flat list, but the canonical catalog is `{globalSkills: [...], librarySkills: [...]}`. On any fresh v4.4.0/v4.4.1 install, the very first `/dashboard-sinapsis` run raised `AttributeError: 'str' object has no attribute 'get'`. Fix: detect dict vs list shape, concatenate `globalSkills + librarySkills`, derive real global count from the dict instead of hardcoding 5, and guard all `.get()` calls with `isinstance(s, dict)` so mixed content cannot crash. Reported in [#6](https://github.com/Luispitik/sinapsis/issues/6) by @fvayas, fixed in [#7](https://github.com/Luispitik/sinapsis/pull/7) by @NestorPVsf. + +--- + ## v4.4.1 (2026-04-17) ### Fixed diff --git a/README.md b/README.md index 75de792..6c25946 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Sinapsis v4.4.1 +# Sinapsis v4.4.2 -[![Version](https://img.shields.io/badge/version-4.4.1-blue.svg)](https://github.com/Luispitik/sinapsis) +[![Version](https://img.shields.io/badge/version-4.4.2-blue.svg)](https://github.com/Luispitik/sinapsis) [![Tests](https://img.shields.io/badge/tests-99%20passing-green.svg)](tests/) [![CI](https://github.com/Luispitik/sinapsis/actions/workflows/tests.yml/badge.svg)](https://github.com/Luispitik/sinapsis/actions) [![License](https://img.shields.io/badge/license-Source%20Available-orange.svg)](LICENSE) From 54e8ff6e4e37fcf48428e2016192df0ffcc52d8d Mon Sep 17 00:00:00 2001 From: Luispitik <149873847+Luispitik@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:59:43 +0200 Subject: [PATCH 2/2] feat: Opus 4.7 integration (v4.5.0) Sinapsis stays fully deterministic in the hot path and becomes richer and cheaper to run on top of Claude Opus 4.7. - Cache-stable instinct ordering: id.localeCompare tiebreaker after priority + occurrences makes the injected systemMessage prefix byte-stable across consecutive tool uses, which is the prerequisite for prompt-cache hits on Opus 4.7's cached system block. - PreCompact hook (core/_precompact-guard.sh): re-invokes the learner before Claude Code compacts the context in long sessions, so fresh observations are flushed to proposals before the transcript is rewritten. Fire-and-forget with an 8s cap. - Raised caps for the 1M context window: * TOKEN_BUDGET 1500 -> 4000 (chars injected per tool use) * top-N instincts per tool use 3 -> 6 (MAX_INSTINCTS_INJECTED) * observation window for session-learner 1000 -> 5000 lines - settings.template.json declares the new PreCompact hook (hooks 6 -> 7). install.sh copies and chmods _precompact-guard.sh. - RFC docs/rfc-v5-adaptive-thinking.md: design for an opt-in SINAPSIS_LLM_ANALYZE=1 path in /analyze-session that would call the Anthropic SDK with adaptive thinking. NOT implemented in this release - core stays deterministic until the approach is validated. - Operator note: Scout/Analyst blueprint in v5.0-alpha no longer uses Haiku; Sonnet 4.6 is the lowest tier the system proposes. New suite tests/test-v45-opus47.sh (11 tests, all GREEN) covers the deterministic ordering invariant (shuffled-index byte-identical output, alphabetical tiebreaker), end-to-end PreCompact wiring, and the new caps. All existing suites re-run clean: install-upgrade 21/21, dashboard 12/12, dream 25/25, gstack-separation 18/18, security 11/11, v433-hardening 14/14. Total 112 tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 26 +++++ README.md | 16 ++- core/_instinct-activator.sh | 15 ++- core/_precompact-guard.sh | 30 ++++++ core/_session-learner.sh | 5 +- core/settings.template.json | 18 +++- docs/rfc-v5-adaptive-thinking.md | 142 +++++++++++++++++++++++++++ install.sh | 4 +- tests/test-v45-opus47.sh | 163 +++++++++++++++++++++++++++++++ 9 files changed, 407 insertions(+), 12 deletions(-) create mode 100644 core/_precompact-guard.sh create mode 100644 docs/rfc-v5-adaptive-thinking.md create mode 100644 tests/test-v45-opus47.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0e6c9..685a48d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## v4.5.0 (2026-04-21) + +### Added — Opus 4.7 integration + +- **Cache-stable instinct ordering** (`_instinct-activator.sh`): added alphabetical `id.localeCompare` tiebreaker after the priority + occurrences sort. The injected `systemMessage` prefix is now byte-stable across consecutive tool uses with the same match set, which is the prerequisite for prompt-cache hits on Opus 4.7's cached system block (~90% discount on input tokens once the cache warms). +- **`PreCompact` hook** (`core/_precompact-guard.sh`, new): fires right before Claude Code compacts the context in long-running sessions and re-invokes the session-learner so fresh observations are flushed to proposals before the transcript is rewritten. Uses `timeout 8` and a fire-and-forget pattern to never block the harness; relies on the existing advisory lock inside `_session-learner.sh` for parallel safety. +- **`settings.template.json`** now declares the new PreCompact hook (hooks 6 → 7). `install.sh` copies and chmods `_precompact-guard.sh`. +- **RFC `docs/rfc-v5-adaptive-thinking.md`**: design for an opt-in `SINAPSIS_LLM_ANALYZE=1` branch in `/analyze-session` that uses Opus 4.7 adaptive thinking via the Anthropic SDK. Not implemented in this release — ships as a design doc so the core stays fully deterministic until the approach is validated. + +### Changed — Caps raised for 1M context + +- `_instinct-activator.sh`: `TOKEN_BUDGET` 1500 → 4000, top-N per tool use 3 → 6 (`MAX_INSTINCTS_INJECTED`). With Opus 4.7's 1M window and prompt caching the cost of the extra injection is amortised, so we can surface more instincts per turn. +- `_session-learner.sh`: observation window 1000 → 5000 lines. Cross-session detectors (repetitions, agent patterns) now see a longer history without paging. +- `_operator-state.json` d017: Scout/Analyst blueprint switched from Haiku to Sonnet 4.6 per operator preference; Architect stays on Opus (now 4.7). + +### Tests + +- New suite `tests/test-v45-opus47.sh` — 11 TDD tests covering deterministic ordering (shuffled-index byte-identical output, alphabetical tiebreaker), PreCompact hook (file present, executable, wired in settings and install.sh), and raised caps. +- All existing suites re-run clean: `test-install-upgrade` 21/21, `test-dashboard` 12/12, `test-dream` 25/25, `test-gstack-separation` 18/18, `test-security` 11/11, `test-v433-hardening` 14/14. + +### Rationale + +Opus 4.7 brings three things Sinapsis can actually use: a stable 1-hour cache TTL that rewards byte-stable prefixes, a 1M context that removes pressure on per-turn caps, and the PreCompact hook Anthropic now ships in Claude Code. None of the "flashy" features (memory tool, context editing) are a natural fit: Sinapsis already *is* a memory system and the inject happens in a stable systemMessage. The v4.5 changes are purely about making the existing design richer and cheaper to run on top of Opus 4.7, without introducing a new LLM dependency in the hot path. + +--- + ## v4.4.2 (2026-04-18) ### Fixed diff --git a/README.md b/README.md index 6c25946..a04c460 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Sinapsis v4.4.2 +# Sinapsis v4.5.0 -[![Version](https://img.shields.io/badge/version-4.4.2-blue.svg)](https://github.com/Luispitik/sinapsis) -[![Tests](https://img.shields.io/badge/tests-99%20passing-green.svg)](tests/) +[![Version](https://img.shields.io/badge/version-4.5.0-blue.svg)](https://github.com/Luispitik/sinapsis) +[![Tests](https://img.shields.io/badge/tests-112%20passing-green.svg)](tests/) [![CI](https://github.com/Luispitik/sinapsis/actions/workflows/tests.yml/badge.svg)](https://github.com/Luispitik/sinapsis/actions) [![License](https://img.shields.io/badge/license-Source%20Available-orange.svg)](LICENSE) @@ -28,6 +28,16 @@ Think of it as going from a dumb terminal to an assistant that actually knows yo --- +## What's New in v4.5.0 — Opus 4.7 Integration + +> Ride the cache, catch the compaction, raise the ceiling. + +- **Cache-stable instinct ordering**: added an `id.localeCompare` tiebreaker after the priority + occurrences sort. The injected `systemMessage` prefix is now byte-stable across consecutive tool uses, which unlocks prompt-cache hits on Opus 4.7's cached system block (~90 % discount on the instinct payload once the cache warms). +- **New `PreCompact` hook** (`_precompact-guard.sh`): fires right before Claude Code compacts the context in long-running sessions and re-runs the session-learner so fresh observations are flushed to proposals before the transcript is rewritten. Uses a fire-and-forget pattern with an 8 s cap — compaction is never blocked. +- **Caps raised for 1M context**: `TOKEN_BUDGET` 1500 → 4000, top-N instincts per tool use 3 → 6, observation window for the learner 1000 → 5000 lines. Cross-session detectors (repetitions, agent patterns) see a longer history; per-turn injection can surface more relevant rules. +- **Opt-in adaptive thinking for `/analyze-session`**: design published in [`docs/rfc-v5-adaptive-thinking.md`](docs/rfc-v5-adaptive-thinking.md). Not shipped enabled — core stays fully deterministic, SDK path is additive and gated by `SINAPSIS_LLM_ANALYZE=1`. +- **11 new TDD tests** (`tests/test-v45-opus47.sh`): shuffled-index byte-identical output, alphabetical tiebreaker, PreCompact wiring end-to-end, and cap assertions. All existing suites re-run clean: 112 total passing. + ## What's New in v4.4.0 — Observability Dashboard > See the system that sees you. diff --git a/core/_instinct-activator.sh b/core/_instinct-activator.sh index 033839d..cdcca0b 100644 --- a/core/_instinct-activator.sh +++ b/core/_instinct-activator.sh @@ -123,11 +123,16 @@ process.stdin.on("end", () => { // Priority sort: permanent first, then confirmed; within same level, highest occurrences wins // v4.2.1: occurrences tiebreaker (inspired by fs-cortex confidence granularity) + // v4.5: id alphabetical tiebreaker — guarantees deterministic order across runs so the + // injected systemMessage prefix is byte-stable across consecutive tool uses, which + // is the prerequisite for prompt-cache hits on the Opus 4.7+ cached system block. const order = { permanent: 0, confirmed: 1 }; injectableMatches.sort((a, b) => { const lvl = (order[a.level] ?? 2) - (order[b.level] ?? 2); if (lvl !== 0) return lvl; - return (b.occurrences || 0) - (a.occurrences || 0); // higher occurrences = higher priority + const occ = (b.occurrences || 0) - (a.occurrences || 0); + if (occ !== 0) return occ; + return (a.id || "").localeCompare(b.id || ""); // v4.5 cache stability tiebreaker }); // Deduplicate by domain — keep only highest priority match per domain @@ -136,7 +141,10 @@ process.stdin.on("end", () => { const d = m.domain || "_default"; if (!domainMap[d]) domainMap[d] = m; // already sorted, first = highest priority } - const top = Object.values(domainMap).slice(0, 3); + // v4.5: top-N raised from 3 to 6 — with Opus 4.7 prompt caching the cost of extra + // instincts is amortised across tool uses, so we can be more generous per turn. + const MAX_INSTINCTS_INJECTED = 6; + const top = Object.values(domainMap).slice(0, MAX_INSTINCTS_INJECTED); // v4.3.1: sanitize inject content (#5F — prompt injection prevention) const INJECT_MAX_LEN = 500; @@ -144,7 +152,8 @@ process.stdin.on("end", () => { // v4.3.3: path traversal protection (inspired by Cortex v3.10) const PATH_TRAVERSAL = /\.\.[\/\\]|~\/|\/etc\/|\/proc\/|%2e%2e/i; // v4.3.3: token budget cap — max chars injected per tool use (inspired by Cortex 8000/session) - const TOKEN_BUDGET = 1500; + // v4.5: raised 1500 → 4000 to match 6-instinct cap; still well below any sane context ceiling. + const TOKEN_BUDGET = 4000; // Only output systemMessage if there are injectable matches if (top.length > 0) { diff --git a/core/_precompact-guard.sh b/core/_precompact-guard.sh new file mode 100644 index 0000000..0a47679 --- /dev/null +++ b/core/_precompact-guard.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# PreCompact Guard - Sinapsis v4.5 +# PreCompact hook: fires right before Claude Code compacts the context in a long-running +# session. Without this, any tool events observed after the previous Stop and before the +# compaction happens can be dropped when compaction rewrites the transcript. +# +# Strategy: re-invoke _session-learner.sh (idempotent, gated by .last-learn) so any fresh +# observations are flushed to proposals before context is lost. Session-learner already +# handles its own locking and skips re-processing observations older than .last-learn. +# +# Runs sync with short timeout so compaction is not noticeably delayed. + +if [ "${SINAPSIS_DEBUG:-}" = "1" ]; then + exec 2>>"$HOME/.claude/skills/_sinapsis-debug.log" +fi + +LEARNER="$HOME/.claude/skills/_session-learner.sh" +LOG="$HOME/.claude/skills/_precompact.log" + +[ ! -x "$LEARNER" ] && exit 0 + +now=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +echo "$now | precompact | invoking learner" >> "$LOG" 2>/dev/null + +# Fire-and-forget: run the learner but bound its time so we never block the harness. +# If learner is already running (Stop fired in parallel), the advisory lock inside it +# serialises writes to _projects.json. +timeout 8 bash "$LEARNER" /dev/null 2>&1 & + +exit 0 diff --git a/core/_session-learner.sh b/core/_session-learner.sh index 723f4f1..0b320b0 100644 --- a/core/_session-learner.sh +++ b/core/_session-learner.sh @@ -39,11 +39,12 @@ const indexFile = process.argv[2]; const proposalsFile = process.argv[3]; const logFile = process.argv[4]; -// Read last 1000 lines of observations (v4.2: was 100, covers parallel sessions) +// Read last 5000 lines of observations (v4.5: was 1000; Opus 4.7 1M context absorbs the +// extra payload without pressure and lets cross-session detectors see a longer window). let lines; try { const content = fs.readFileSync(obsFile, "utf8").trim().split("\n"); - lines = content.slice(-1000).map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean); + lines = content.slice(-5000).map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean); } catch(e) { process.exit(0); } if (lines.length < 3) process.exit(0); diff --git a/core/settings.template.json b/core/settings.template.json index 883a59e..c169153 100644 --- a/core/settings.template.json +++ b/core/settings.template.json @@ -1,7 +1,7 @@ { - "_comment": "Sinapsis v4.1 — settings.json template. Copy to ~/.claude/settings.json (merge with existing).", - "_hooks_total": 6, - "_hooks_breakdown": "PreToolUse(4) + PostToolUse(1) + Stop(1)", + "_comment": "Sinapsis v4.5 — settings.json template. Copy to ~/.claude/settings.json (merge with existing).", + "_hooks_total": 7, + "_hooks_breakdown": "PreToolUse(4) + PostToolUse(1) + Stop(1) + PreCompact(1)", "hooks": { "PreToolUse": [ { @@ -63,6 +63,18 @@ } ] } + ], + "PreCompact": [ + { + "hooks": [ + { + "_comment": "precompact-guard (v4.5): flush observations via learner before context compaction — prevents data loss in long sessions", + "type": "command", + "command": "bash ~/.claude/skills/_precompact-guard.sh", + "timeout": 10 + } + ] + } ] } } diff --git a/docs/rfc-v5-adaptive-thinking.md b/docs/rfc-v5-adaptive-thinking.md new file mode 100644 index 0000000..7981035 --- /dev/null +++ b/docs/rfc-v5-adaptive-thinking.md @@ -0,0 +1,142 @@ +# RFC: Adaptive thinking in `/analyze-session` + +- **Status:** Draft +- **Author:** Luis Salgado +- **Created:** 2026-04-21 +- **Target release:** Sinapsis v5.0 +- **Supersedes:** nothing (new feature) +- **Depends on:** Anthropic SDK >= 2026-04, env `ANTHROPIC_API_KEY` + +## Context + +Sinapsis v4.5 learner is fully deterministic: bash + node regex detectors over +`observations.jsonl`. This is great for the hot path (Stop hook, PreToolUse inject), +but the deep semantic pass — `/analyze-session` — is still only regex-backed. + +Claude Opus 4.7 ships with **adaptive thinking** (replacing the old +`budget_tokens` extended-thinking knob) and native interleaved thinking between +tool calls. For offline, on-demand analysis of a session this is a better tool +than hand-written regex: clustering, contradictions across days, natural-language +gotchas that regex can't see. + +## Goals + +1. `/analyze-session` can optionally call Claude Opus 4.7 with adaptive thinking + to produce higher-quality instinct proposals. +2. **Zero regression** when the SDK path is disabled: the existing regex pipeline + remains the default and runs identically. +3. Cost-bounded: one session = at most one API call, with a hard `effort` cap. +4. No leakage of user data (observations are already scrubbed by + `observe_v3.py` — we rely on that). + +## Non-goals + +- Moving `_instinct-activator.sh` or `_session-learner.sh` to the SDK. Hot path + stays bash/node. LLM only in explicit `/analyze-session` flow. +- Auto-apply of LLM proposals. Output still requires human `[A]ccept/[E]dit/[X]`. + +## Design + +### 1. Opt-in gate + +A new env var `SINAPSIS_LLM_ANALYZE=1` enables the SDK branch in +`commands/analyze-session.md`. Default off. Users who do not export it or do not +have `ANTHROPIC_API_KEY` see the current regex-only behaviour with zero network +calls. + +### 2. Prompt shape + +```python +response = client.messages.create( + model="claude-opus-4-7", + max_tokens=4096, + # Adaptive thinking replaces extended_thinking.budget_tokens (Opus 4.7) + thinking={"type": "adaptive", "display": "summarized"}, + # Cache the fixed system block — docs for the instinct schema rarely change + system=[ + { + "type": "text", + "text": SINAPSIS_SCHEMA_SYSTEM_PROMPT, # ~800 tokens, stable + "cache_control": {"type": "ephemeral"}, + } + ], + messages=[ + { + "role": "user", + "content": json.dumps({ + "observations": scrubbed_observations[-500:], + "existing_instincts": index_instincts, + "raw_proposals": proposals_from_session_learner, + }), + } + ], +) +``` + +**Cache expectation:** the system block is byte-stable across every session. +Second call within 5 min TTL = ~90 % input-token discount. + +### 3. Output contract + +The model must return JSON of the form: + +```json +{ + "proposals": [ + { + "id": "kebab-case", + "domain": "security|git|nextjs|...", + "trigger_pattern": "regex", + "inject": "<= 500 chars", + "confidence": "HIGH|MEDIUM|LOW", + "reasoning": "one-liner for the human review" + } + ] +} +``` + +`/analyze-session` validates the JSON, merges with existing regex proposals, +deduplicates by `id`, and presents to the user. Any invalid JSON falls back to +regex-only output. + +### 4. Failure handling + +- Missing `ANTHROPIC_API_KEY` → warn once, continue regex-only. +- API error / timeout (30 s cap) → log to `_sinapsis-debug.log`, continue + regex-only. Never block the user. +- Malformed JSON → discard LLM proposals, continue with regex. + +### 5. Architectural concern (open question) + +Today Sinapsis is *core-deterministic*. Introducing an SDK call — even gated by +env var — opens a door to drift: future features could start relying on the LLM +branch and silently degrade without it. Mitigation: + +- Keep the SDK branch strictly **additive** (merges with regex output, never + replaces it). +- Add `test-v5-llm-fallback.sh` that exercises the `SINAPSIS_LLM_ANALYZE=0` + path and asserts bit-identical output to v4.5. +- Document in README: "LLM analyze is a convenience layer. Sinapsis stays + functional with it turned off." + +## Rollout + +- Phase 1 (this RFC): ship opt-in `SINAPSIS_LLM_ANALYZE`. Collect telemetry on + cache-hit ratio and proposal quality for 2-3 weeks. +- Phase 2: if cache-hit ratio > 60 % and proposal acceptance rate > 40 %, make + it default-on (still overridable). +- Phase 3 (speculative, multi-agent v5): Managed Agents on Anthropic Cloud + fan out Scout(Sonnet 4.6) → Analyst(Sonnet 4.6) → Architect(Opus 4.7). Not + covered here. + +## Out of scope + +- Memory tool (`memory_20250818`) integration. Sinapsis already *is* a memory + system — adding the tool adds duplicate plumbing without a clear win. Revisit + only if multi-machine sync becomes a real requirement. +- Context editing. Not applicable to `systemMessage` injection and adds cost. + +## Not doing + +- Haiku. Per operator preference, the Scout/Analyst/Architect blueprint skips + Haiku entirely; Sonnet 4.6 is the lowest tier we use. diff --git a/install.sh b/install.sh index 7c19f3b..4e729f7 100644 --- a/install.sh +++ b/install.sh @@ -164,6 +164,7 @@ cp "$SCRIPT_DIR/core/_session-learner.sh" "$SKILLS_DIR/_session-learner.sh" cp "$SCRIPT_DIR/core/_project-context.sh" "$SKILLS_DIR/_project-context.sh" cp "$SCRIPT_DIR/core/_eod-gather.sh" "$SKILLS_DIR/_eod-gather.sh" cp "$SCRIPT_DIR/core/_dream.sh" "$SKILLS_DIR/_dream.sh" +cp "$SCRIPT_DIR/core/_precompact-guard.sh" "$SKILLS_DIR/_precompact-guard.sh" cp "$SCRIPT_DIR/core/_generate-dashboard.py" "$SKILLS_DIR/_generate-dashboard.py" cp "$SCRIPT_DIR/core/_dashboard-template.html" "$SKILLS_DIR/_dashboard-template.html" @@ -173,9 +174,10 @@ chmod +x "$SKILLS_DIR/_session-learner.sh" chmod +x "$SKILLS_DIR/_project-context.sh" chmod +x "$SKILLS_DIR/_eod-gather.sh" chmod +x "$SKILLS_DIR/_dream.sh" +chmod +x "$SKILLS_DIR/_precompact-guard.sh" chmod +x "$SKILLS_DIR/_generate-dashboard.py" 2>/dev/null || true -echo -e "${GREEN} OK${NC} 5 hook scripts + dream cycle + dashboard generator installed" +echo -e "${GREEN} OK${NC} 6 hook scripts + dream cycle + dashboard generator installed" # ── Step 5b: Legacy file cleanup (v4.3.3) ── LEGACY_CLEANED=0 diff --git a/tests/test-v45-opus47.sh b/tests/test-v45-opus47.sh new file mode 100644 index 0000000..547c52d --- /dev/null +++ b/tests/test-v45-opus47.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# test-v45-opus47.sh — TDD tests for Sinapsis v4.5 (Opus 4.7 integration) +# 6 features: cache-stable ordering, PreCompact hook, raised caps, settings wiring +# Run: bash tests/test-v45-opus47.sh + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +ACTIVATOR="$SCRIPT_DIR/core/_instinct-activator.sh" +LEARNER="$SCRIPT_DIR/core/_session-learner.sh" +PRECOMPACT="$SCRIPT_DIR/core/_precompact-guard.sh" +SETTINGS="$SCRIPT_DIR/core/settings.template.json" + +PASS=0 +FAIL=0 +TOTAL=11 + +pass() { PASS=$((PASS + 1)); echo " PASS: $1"; } +fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; } + +setup_sandbox() { + SANDBOX="$(mktemp -d)" + mkdir -p "$SANDBOX/.claude/skills" +} + +teardown_sandbox() { + rm -rf "$SANDBOX" 2>/dev/null +} + +echo "" +echo "=== Sinapsis v4.5 Opus 4.7 Integration Tests ===" +echo "=== $TOTAL tests: cache stability, PreCompact, raised caps ===" +echo "" + +# ── Section 1: Cache-stable ordering (P1) ── +echo "[Section 1] Deterministic ordering for prompt cache hits" + +# T1: activator has id-based tiebreaker in the sort +if grep -q "localeCompare\|a\.id.*b\.id\|tiebreaker" "$ACTIVATOR" 2>/dev/null; then + pass "T1: Activator has deterministic id tiebreaker" +else + fail "T1: Activator must have id-based tiebreaker for cache stability" +fi + +# T2: functional — two runs with identical input and shuffled index produce identical output +setup_sandbox + +# Build an index with 4 instincts of same level + occurrences — tiebreaker must pick alphabetical +cat > "$SANDBOX/.claude/skills/_instincts-index.json" << 'EOFIDX' +{ + "version": "4.1", + "instincts": [ + {"id":"zebra","domain":"general","level":"confirmed","trigger_pattern":"Edit","inject":"Z rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"}, + {"id":"alpha","domain":"security","level":"confirmed","trigger_pattern":"Edit","inject":"A rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"}, + {"id":"mike","domain":"git","level":"confirmed","trigger_pattern":"Edit","inject":"M rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"} + ], + "archived": [] +} +EOFIDX + +INPUT='{"tool_name":"Edit","tool_input":{"file_path":"x.js"}}' +OUT1=$(echo "$INPUT" | HOME="$SANDBOX" bash "$ACTIVATOR" 2>/dev/null) + +# Shuffle the on-disk order and rerun — result must be byte-identical +cat > "$SANDBOX/.claude/skills/_instincts-index.json" << 'EOFIDX2' +{ + "version": "4.1", + "instincts": [ + {"id":"mike","domain":"git","level":"confirmed","trigger_pattern":"Edit","inject":"M rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"}, + {"id":"zebra","domain":"general","level":"confirmed","trigger_pattern":"Edit","inject":"Z rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"}, + {"id":"alpha","domain":"security","level":"confirmed","trigger_pattern":"Edit","inject":"A rule","occurrences":5,"first_triggered":"2026-04-01","last_triggered":"2026-04-20"} + ], + "archived": [] +} +EOFIDX2 + +OUT2=$(echo "$INPUT" | HOME="$SANDBOX" bash "$ACTIVATOR" 2>/dev/null) + +if [ -n "$OUT1" ] && [ "$OUT1" = "$OUT2" ]; then + pass "T2: Shuffled index produces byte-identical inject (cache-safe)" +else + fail "T2: Output differs after index shuffle — cache will miss. OUT1='$OUT1' OUT2='$OUT2'" +fi + +# T3: when tied on level+occurrences, alpha comes before mike comes before zebra +if echo "$OUT1" | grep -q "A rule.*M rule.*Z rule" 2>/dev/null; then + pass "T3: Alphabetical tiebreaker applied (A < M < Z)" +else + fail "T3: Expected alpha→mike→zebra order in output, got: '$OUT1'" +fi + +teardown_sandbox + +# ── Section 2: PreCompact hook (P2) ── +echo "" +echo "[Section 2] PreCompact guardrail hook" + +if [ -f "$PRECOMPACT" ]; then + pass "T4: _precompact-guard.sh exists" +else + fail "T4: _precompact-guard.sh must exist in core/" +fi + +if [ -x "$PRECOMPACT" ]; then + pass "T5: _precompact-guard.sh is executable" +else + fail "T5: _precompact-guard.sh must be executable" +fi + +# T6: settings.template.json registers PreCompact +if grep -q "PreCompact" "$SETTINGS" 2>/dev/null; then + pass "T6: settings.template.json declares PreCompact hook" +else + fail "T6: PreCompact hook must be in settings.template.json" +fi + +# T7: PreCompact points to precompact-guard.sh +if grep -q "_precompact-guard\.sh" "$SETTINGS" 2>/dev/null; then + pass "T7: PreCompact hook wires to _precompact-guard.sh" +else + fail "T7: PreCompact hook must call _precompact-guard.sh" +fi + +# T8: install.sh copies the new hook +if grep -q "_precompact-guard\.sh" "$SCRIPT_DIR/install.sh" 2>/dev/null; then + pass "T8: install.sh copies _precompact-guard.sh" +else + fail "T8: install.sh must copy _precompact-guard.sh" +fi + +# ── Section 3: Raised caps (P3) ── +echo "" +echo "[Section 3] Caps raised for Opus 4.7 1M context" + +# T9: TOKEN_BUDGET >= 4000 in activator +if grep -qE "TOKEN_BUDGET\s*=\s*[4-9][0-9]{3}" "$ACTIVATOR" 2>/dev/null; then + pass "T9: TOKEN_BUDGET >= 4000 in activator" +else + fail "T9: TOKEN_BUDGET should be raised to >=4000" +fi + +# T10: MAX_INSTINCTS_INJECTED present and >= 6 +if grep -qE "MAX_INSTINCTS_INJECTED\s*=\s*[6-9]|slice\(0,\s*[6-9]\)" "$ACTIVATOR" 2>/dev/null; then + pass "T10: MAX_INSTINCTS_INJECTED raised to >=6" +else + fail "T10: Top-N slice should be raised to >=6" +fi + +# T11: session-learner reads >= 5000 obs lines +if grep -qE "slice\(-[5-9][0-9]{3}\)|slice\(-[1-9][0-9]{4}\)" "$LEARNER" 2>/dev/null; then + pass "T11: session-learner reads >=5000 observation lines" +else + fail "T11: OBS_READ_LINES cap should be raised to >=5000" +fi + +# ── Summary ── +echo "" +echo "=== Results: $PASS/$TOTAL passed, $FAIL failed ===" +if [ "$FAIL" -eq 0 ]; then + echo "ALL TESTS PASSED" + exit 0 +else + echo "SOME TESTS FAILED" + exit 1 +fi