Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## v4.5.0 (2026-04-18) — unreleased

### Added — Reflexes tier (inspired by fs-cortex, credit: Fernando Montero / MIT)
- **`seeds/reflexes.json`** (5 new passive rules): `read-before-edit`, `test-after-change`, `git-push-safety`, `git-merge-verify`, `instinct-downvote`. All carry `origin: seed:fermontero-fs-cortex` for traceability. Ported from `fs-cortex/core/reflexes.default.json` after deduping against Sinapsis's existing 6 rules.
- **`core/_reflex-merge.mjs`** (new, 90 LOC Node.js): merges seed reflexes into the user's `~/.claude/skills/_passive-rules.json`. Idempotent. **User customizations win** — rules with an existing id are preserved (no overwrite). CLI flags: `--seeds-path`, `--index-path`, `--dry-run`. Atomic write (tmp → rename). Recomputes `totalTokens`.
- **10 TDD tests** (`tests/test-reflexes.sh`): presence, seed structure, fresh merge, idempotency, user preservation, conflict resolution, token recomputation, activator integration (`read-before-edit` fires on Edit, `git-push-safety` on `git push`), `--dry-run`. 10/10 GREEN.

### Changed
- `install.sh`: new Step 5c runs the reflex merger after copying hook scripts. Safe on upgrade — preserves user-customized rules.
- `install.bat`: mirrors Step 5c so Windows installs get the merger too (Codex review: P1).
- `_reflex-merge.mjs`: atomic write preserves existing file mode (0600 on upgraded installs, falls back to explicit 0600 on fresh install) to avoid relaxing `_passive-rules.json` to 0644 via the user's umask (Codex review: P2).
- `seeds/reflexes.json`: `test-after-change` trigger scoped to `^(Edit|Write)\s.*(...)` so it does not fire on Read/Grep and waste passive-rule slots on non-mutating tools (Codex review: P2).

### Skipped from fs-cortex (already covered)
- `env-never-commit`, `git-commit-quality`, `api-auth-check` (→ `api-auth-reminder`), `security-headers` (→ `security-headers-check`), `capture-decision` (→ `decision-capture`). Sinapsis equivalents kept as-is.

### Adapted
- `instinct-downvote`: fs-cortex references its own `/cx-downvote`; Sinapsis version uses the native `/downvote` command.

### Rationale
Sinapsis shipped 6 passive rules focused on security and workflow; fs-cortex shipped 10 reflexes with broader coverage (read-before-edit discipline, test-after-change reminders, git push/merge safety). Porting the 5 non-duplicate ones gives Sinapsis users a fuller baseline without replacing their customizations. Merge pattern matches the seeds importer (Ola 1) — additive, idempotent, user-first.

### Test badge
116 → 127 passing (suite-wide, 8 test files; +11 reflex tests including a regression guard for the Read/Grep scope fix).

---

## v4.4.1 (2026-04-17)

### Fixed
Expand Down
100 changes: 100 additions & 0 deletions core/_reflex-merge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env node
// Sinapsis v4.5 — Reflex Merger
//
// Merges seed passive rules (seeds/reflexes.json) into the user's
// ~/.claude/skills/_passive-rules.json. Idempotent — rules with an id that
// already exists in the user's index are skipped (user customizations win).
//
// Inspired by fs-cortex reflex tier — credit: Fernando Montero (MIT, 2026).
//
// Usage:
// node core/_reflex-merge.mjs [--seeds-path <path>] [--index-path <path>] [--dry-run]

import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

function parseArgs(argv) {
const out = { seedsPath: null, indexPath: null, dryRun: false };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === "--seeds-path") out.seedsPath = argv[++i];
else if (a === "--index-path") out.indexPath = argv[++i];
else if (a === "--dry-run") out.dryRun = true;
}
return out;
}

function defaultSeedsPath() {
const repoSeed = path.join(__dirname, "..", "seeds", "reflexes.json");
if (fs.existsSync(repoSeed)) return repoSeed;
return null;
}

function defaultIndexPath() {
const home = process.env.SINAPSIS_HOME || path.join(process.env.HOME || process.env.USERPROFILE, ".claude");
return path.join(home, "skills", "_passive-rules.json");
}

function atomicWrite(filePath, text) {
const tmp = filePath + ".tmp";
fs.writeFileSync(tmp, text);
// Preserve existing file mode (install.sh hardens this file to 0600).
// If target does not exist yet (fresh install), fall back to the explicit
// Sinapsis default 0o600 to match install.sh's documented protection.
try {
const mode = fs.existsSync(filePath) ? fs.statSync(filePath).mode & 0o777 : 0o600;
fs.chmodSync(tmp, mode);
} catch (e) { /* best-effort on platforms without POSIX modes (Windows) */ }
fs.renameSync(tmp, filePath);
}

const args = parseArgs(process.argv);
const seedsPath = args.seedsPath || defaultSeedsPath();
const indexPath = args.indexPath || defaultIndexPath();

if (!seedsPath || !fs.existsSync(seedsPath)) {
console.error(`ERROR: seeds file missing: ${seedsPath}`);
process.exit(1);
}
if (!fs.existsSync(indexPath)) {
console.error(`ERROR: passive rules index missing: ${indexPath}`);
process.exit(1);
}

const seeds = JSON.parse(fs.readFileSync(seedsPath, "utf8"));
const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));

if (!Array.isArray(index.rules)) index.rules = [];

const existing = new Set(index.rules.map(r => r.id));
const imported = [];
const already = [];

for (const rule of seeds.rules || []) {
if (!rule.id) continue;
if (existing.has(rule.id)) {
already.push(rule.id);
continue;
}
index.rules.push(rule);
imported.push(rule.id);
}

// Recompute totalTokens
const totalTokens = index.rules.reduce((sum, r) => sum + (Number(r.tokens) || 0), 0);
index.totalTokens = totalTokens;

if (args.dryRun) {
console.log("[DRY-RUN] No files modified.");
} else if (imported.length > 0) {
atomicWrite(indexPath, JSON.stringify(index, null, 2) + "\n");
console.log(`[OK] Wrote ${indexPath}`);
}

console.log(`Imported (${imported.length}):`);
for (const id of imported) console.log(` + ${id}`);
if (already.length) console.log(`Already present (${already.length}): ${already.join(", ")}`);
6 changes: 3 additions & 3 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Sinapsis v4.3.3 — Feature Reference
# Sinapsis v4.5 — Feature Reference

> Complete inventory of all features, commands, hooks, modules, and capabilities.
> **Update this file with every feat/fix commit.**
> Last updated: 2026-04-13
> Last updated: 2026-04-18

---

Expand All @@ -23,7 +23,7 @@ OBSERVATIONS → PROPOSALS → INSTINCTS (JSON)
```

Parallel systems (not part of the confidence pipeline):
- **Passive Rules** (6 default) — deterministic regex rules, always fire on matcher
- **Passive Rules** (11 default: 6 original + 5 fs-cortex seed reflexes) — deterministic regex rules, always fire on matcher
- **Operator State** — strategic decisions persisted across all projects
- **Skill Router** — on-demand skill loading from dormant catalog

Expand Down
8 changes: 7 additions & 1 deletion install.bat
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,14 @@ copy /Y "%SCRIPT_DIR%core\_session-learner.sh" "%SKILLS_DIR%\_session-learner.sh
copy /Y "%SCRIPT_DIR%core\_project-context.sh" "%SKILLS_DIR%\_project-context.sh" >nul
copy /Y "%SCRIPT_DIR%core\_eod-gather.sh" "%SKILLS_DIR%\_eod-gather.sh" >nul
copy /Y "%SCRIPT_DIR%core\_dream.sh" "%SKILLS_DIR%\_dream.sh" >nul
copy /Y "%SCRIPT_DIR%core\_reflex-merge.mjs" "%SKILLS_DIR%\_reflex-merge.mjs" >nul

echo OK 5 hook scripts + dream cycle installed
:: Step 5c: Merge seed reflexes (v4.5) — idempotent, preserves user customizations
if exist "%SCRIPT_DIR%seeds\reflexes.json" (
node "%SKILLS_DIR%\_reflex-merge.mjs" --seeds-path "%SCRIPT_DIR%seeds\reflexes.json" --index-path "%SKILLS_DIR%\_passive-rules.json" 2>&1
)

echo OK 5 hook scripts + dream cycle + reflex merger installed
echo NOTE: On Windows, hooks run via Git Bash or WSL. See README for details.

:: Step 6: Configure settings.json
Expand Down
12 changes: 11 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ 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/_generate-dashboard.py" "$SKILLS_DIR/_generate-dashboard.py"
cp "$SCRIPT_DIR/core/_dashboard-template.html" "$SKILLS_DIR/_dashboard-template.html"
cp "$SCRIPT_DIR/core/_reflex-merge.mjs" "$SKILLS_DIR/_reflex-merge.mjs"

chmod +x "$SKILLS_DIR/_passive-activator.sh"
chmod +x "$SKILLS_DIR/_instinct-activator.sh"
Expand All @@ -175,7 +176,16 @@ chmod +x "$SKILLS_DIR/_eod-gather.sh"
chmod +x "$SKILLS_DIR/_dream.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"
# ── Step 5c: Merge seed reflexes (v4.5) ──
# Merge shipped passive rules (seeds/reflexes.json) into user's _passive-rules.json.
# Idempotent — rules with existing IDs are preserved (user customizations win).
if [ -f "$SCRIPT_DIR/seeds/reflexes.json" ]; then
node "$SKILLS_DIR/_reflex-merge.mjs" \
--seeds-path "$SCRIPT_DIR/seeds/reflexes.json" \
--index-path "$SKILLS_DIR/_passive-rules.json" 2>&1 | sed 's/^/ /' || true
fi

echo -e "${GREEN} OK${NC} 5 hook scripts + dream cycle + dashboard generator + reflex merger installed"

# ── Step 5b: Legacy file cleanup (v4.3.3) ──
LEGACY_CLEANED=0
Expand Down
52 changes: 52 additions & 0 deletions seeds/reflexes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"_comment": "Seed passive rules (reflexes). Ported from fs-cortex (MIT © Fernando Montero). Merged into ~/.claude/skills/_passive-rules.json by install.sh Step 5d. Idempotent — existing rules preserved.",
"_credit": "Fernando Montero / fs-cortex (https://github.com/fermonterom/fs-cortex)",
"_license": "MIT",
"rules": [
{
"id": "read-before-edit",
"trigger": "^(Edit|Write)\\s",
"inject": "Before editing: verify the file was Read recently. If not, Read it first to avoid blind edits.",
"severity": "high",
"category": "quality",
"tokens": 25,
"origin": "seed:fermontero-fs-cortex"
},
{
"id": "test-after-change",
"trigger": "^(Edit|Write)\\s.*(\\.test\\.|\\.spec\\.|route\\.ts|\\.component\\.)",
"inject": "Code modified. Consider running tests (unit + e2e) to catch regressions.",
"severity": "medium",
"category": "quality",
"tokens": 20,
"origin": "seed:fermontero-fs-cortex"
},
{
"id": "git-push-safety",
"trigger": "git push|gh pr create",
"inject": "Before pushing: fetch + rebase first. For force pushes use --force-with-lease (never --force).",
"severity": "high",
"category": "git",
"tokens": 25,
"origin": "seed:fermontero-fs-cortex"
},
{
"id": "git-merge-verify",
"trigger": "gh pr merge|git merge ",
"inject": "Before merging: verify all CI checks pass. Clean up local branch after merge.",
"severity": "high",
"category": "git",
"tokens": 20,
"origin": "seed:fermontero-fs-cortex"
},
{
"id": "instinct-downvote",
"trigger": "/downvote|wrong instinct|ignore instinct|bad instinct|instinct incorrecto",
"inject": "Negative instinct feedback detected. Use /downvote <id> to reduce confidence or archive.",
"severity": "low",
"category": "learning",
"tokens": 25,
"origin": "seed:fermontero-fs-cortex"
}
]
}
Loading