Skip to content
Merged
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to fs-cortex will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [4.2.2] — 2026-07-05

### Fixed
- `/cx-maintain` Step 7 (`commands/cx-maintain.md` + `bin/cx-maintain.sh`,
identical embedded script): **learn markers now reset after a maintenance
pass**. `observe.py` touches `.learn-pending` every LEARN_THRESHOLD
observations, but nothing cleared it after `/cx-analyze` retired in v4 — so
SessionStart's "N+ new observations, run /cx-maintain" banner nagged
forever, even immediately after a pass. Step 7 now snapshots the current
observation total into `.last-learn-count` (atomic tmp+rename), zeroes
`.obs-count` and deletes `.learn-pending`, so `check_learn_pending()`
measures "since last maintenance" again. `test_cx_maintain_runner` 9→10
(seeded markers reset end-to-end); the suite also gets its missing row in
the FEATURES.md tests table (gap since v4.0.0).

## [4.2.1] — 2026-07-05

**AD follow-up (Codex GPT-5.5).** After merging v4.2.0, a read-only adversarial
Expand Down
26 changes: 24 additions & 2 deletions bin/cx-maintain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,35 @@ if not DRY_RUN:
else:
report["steps"].append({"name": "write-digest", "ok": True, "detail": "(dry-run: not written)"})

# ── Step 7: compat markers .last-distill / .last-dream ──
# ── Step 7: compat markers .last-distill / .last-dream + learn markers ──
# v4.2.2: also reset the learn markers. observe.py touches .learn-pending
# every LEARN_THRESHOLD observations, but nothing cleared it after
# /cx-analyze retired in v4 — so SessionStart's "N+ new observations, run
# /cx-maintain" banner nagged forever, even right after a pass. Snapshot the
# current observation total into .last-learn-count, zero .obs-count and drop
# the flag, so check_learn_pending() measures "since last maintenance" again.
if not DRY_RUN:
try:
now_iso = datetime.now(timezone.utc).isoformat()
(CORTEX_DIR / ".last-distill").write_text(now_iso + "\n", encoding="utf-8")
(CORTEX_DIR / ".last-dream").write_text(now_iso + "\n", encoding="utf-8")
report["steps"].append({"name": "compat-markers", "ok": True, "detail": "touched .last-distill, .last-dream"})
total_obs = 0
for obs_file in (CORTEX_DIR / "projects").glob("*/observations.jsonl"):
try:
with open(obs_file, encoding="utf-8") as f:
total_obs += sum(1 for _ in f)
except OSError:
pass
for name, value in ((".last-learn-count", str(total_obs)), (".obs-count", "0")):
tmp = CORTEX_DIR / f"{name}.tmp.{os.getpid()}"
tmp.write_text(value, encoding="utf-8")
os.replace(tmp, CORTEX_DIR / name)
try:
(CORTEX_DIR / ".learn-pending").unlink()
except FileNotFoundError:
pass
report["steps"].append({"name": "compat-markers", "ok": True,
"detail": f"touched .last-distill, .last-dream; .last-learn-count={total_obs}; .obs-count=0; cleared .learn-pending"})
except Exception as e:
report["steps"].append({"name": "compat-markers", "ok": False, "detail": str(e)})
report["errors"].append(f"compat-markers: {e}")
Expand Down
26 changes: 24 additions & 2 deletions commands/cx-maintain.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,35 @@ if not DRY_RUN:
else:
report["steps"].append({"name": "write-digest", "ok": True, "detail": "(dry-run: not written)"})

# ── Step 7: compat markers .last-distill / .last-dream ──
# ── Step 7: compat markers .last-distill / .last-dream + learn markers ──
# v4.2.2: also reset the learn markers. observe.py touches .learn-pending
# every LEARN_THRESHOLD observations, but nothing cleared it after
# /cx-analyze retired in v4 — so SessionStart's "N+ new observations, run
# /cx-maintain" banner nagged forever, even right after a pass. Snapshot the
# current observation total into .last-learn-count, zero .obs-count and drop
# the flag, so check_learn_pending() measures "since last maintenance" again.
if not DRY_RUN:
try:
now_iso = datetime.now(timezone.utc).isoformat()
(CORTEX_DIR / ".last-distill").write_text(now_iso + "\n", encoding="utf-8")
(CORTEX_DIR / ".last-dream").write_text(now_iso + "\n", encoding="utf-8")
report["steps"].append({"name": "compat-markers", "ok": True, "detail": "touched .last-distill, .last-dream"})
total_obs = 0
for obs_file in (CORTEX_DIR / "projects").glob("*/observations.jsonl"):
try:
with open(obs_file, encoding="utf-8") as f:
total_obs += sum(1 for _ in f)
except OSError:
pass
for name, value in ((".last-learn-count", str(total_obs)), (".obs-count", "0")):
tmp = CORTEX_DIR / f"{name}.tmp.{os.getpid()}"
tmp.write_text(value, encoding="utf-8")
os.replace(tmp, CORTEX_DIR / name)
try:
(CORTEX_DIR / ".learn-pending").unlink()
except FileNotFoundError:
pass
report["steps"].append({"name": "compat-markers", "ok": True,
"detail": f"touched .last-distill, .last-dream; .last-learn-count={total_obs}; .obs-count=0; cleared .learn-pending"})
except Exception as e:
report["steps"].append({"name": "compat-markers", "ok": False, "detail": str(e)})
report["errors"].append(f"compat-markers: {e}")
Expand Down
6 changes: 4 additions & 2 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# fs-cortex v4.2.1 — Feature Reference
# fs-cortex v4.2.2 — Feature Reference

> Complete inventory of all features, commands, hooks, modules, and capabilities.
> Last updated: 2026-07-05
Expand Down Expand Up @@ -252,7 +252,7 @@ mapping: [`MIGRATION-V4.md`](MIGRATION-V4.md).
| Command | Purpose | Token Cost |
|---|---|---|
| `/cx-status` | Visual glance dashboard: 🟢🟡🔴 semaphore per section (laws, instincts, reflexes, sistema) + a SALUD GLOBAL header box (worst-of), confidence bars, top-5 projects/tracking, domain grouping collapsed to top4+resto. Same collector JSON as v3 (nothing removed, overflow moves to compact secondary lines / on-request detail). `--impact`, `--reflexes`, `--pipeline` flags unchanged from v3 | ~200 |
| `/cx-maintain` | **New in v4, deterministic, cron-able.** Single pass: engine decay/purge/auto-validate/deterministic law-promotion, Jaccard dedup by subtopic, storage rotation, proposals↔instincts reconciliation, health check, writes `.review-digest.json` for `/cx-review`. Zero questions, idempotent. Also ships as `bin/cx-maintain.sh` — a plain bash mirror of the same steps for cron/launchd, no LLM call, 0 tokens | ~400 / 0 (script) |
| `/cx-maintain` | **New in v4, deterministic, cron-able.** Single pass: engine decay/purge/auto-validate/deterministic law-promotion, Jaccard dedup by subtopic, storage rotation, proposals↔instincts reconciliation, health check, writes `.review-digest.json` for `/cx-review`. Resets the learn markers (`.last-learn-count`, `.obs-count`, `.learn-pending`) so the SessionStart "N+ new observations" banner measures since-last-maintenance (v4.2.2). Zero questions, idempotent. Also ships as `bin/cx-maintain.sh` — a plain bash mirror of the same steps for cron/launchd, no LLM call, 0 tokens | ~400 / 0 (script) |
| `/cx-review` | **New in v4, the only command with judgment, weekly.** Presents the accumulated digest (human-gated proposals + evolve drafts + law deprecation candidate) as ONE shorthand list. Fuses the old `cx-validate` + `cx-evolve` confirmation + `cx-downvote` + `cx-distill --swap` | ~600 |
| `/cx-eod` | End-of-day summary. Deterministic gather (`core/_cx-eod-gather.sh`, no LLM); structurally cumulative (rereads the full day on every run); **v4**: per-project `context` field from `context.md`, `[eod-eisenhower]` Q1–Q4 keyword classification of "for tomorrow" bullets on next-day reinjection, review-digest pending count surfaced in `--write` mode | ~300 / 0 (auto) |
| `/cx-gotcha` | Capture error→fix as high-priority instinct (unchanged) | ~200 |
Expand Down Expand Up @@ -422,6 +422,7 @@ Deterministic rules via hooks — not probabilistic instructions. Triggers are r
| `test_integrity.sh` | 14 | observe.py direct, **21 commands** validated (EXPECTED_COMMANDS subset; 24 command files total as of v4.0.0 — `ls commands/*.md` — incl. new `cx-maintain`/`cx-review` not yet in EXPECTED_COMMANDS), core file schemas, **version consistency** |
| `test_distill_v4.sh` | 6 | **(v4.0.0)** `auto_promote_to_law` deterministic gate: conf/projects/occurrences_v4/noise-14d criteria, `law_eligible: false` veto, lazy `occurrences_legacy`/`occurrences_v4` migration |
| `test_injector_v4.sh` | 13 | **(v4.0.0)** `status: draft` never injects, `status: confirmed`/legacy-no-field inject normally, subtopic dedup + domain soft-ceiling at conf>=0.85, JSON-fragment + prompt-injection action guards, degenerate-trigger static validation, live `reflexes.json` polling conditions |
| `test_cx_maintain_runner.sh` | 10 | **(v4.0.0, inventoried v4.2.2)** `bin/cx-maintain.sh` hermetic sandbox: exit 0 on repeated runs, decay exactly-once (same-day idempotency via `last_decay_at`), compat markers, valid `.review-digest.json`, no leftover lock dir, `--dry-run` writes nothing; **v4.2.2**: learn markers reset (`.last-learn-count` snapshot, `.obs-count` zeroed, `.learn-pending` cleared) |
| `test_install_ps1.ps1` | 10 | PowerShell syntax, version consistency, security features, backup categories, hook config, v3.26-v3.28 source files present, **CI on windows-latest** |
| `test_impact.sh` | 32 | **Sprint 0/1 funnel** — schema v1, JS↔Python compat, concurrent writes (10 parallel → 0 loss), rotation, gate GO/NO-GO, formulas, input validation, **v3.17.0 source split** (user/agent ratios, gate input, legacy default), **v3.18.0 auto-eval** (3 evaluator types, no-evaluator default, reflex iid prefix) |
| `test_cross_day_tracker.sh` | 10 | Roundtrip, boost tiers (1/2/4/8 days), confidence cap 0.95, Jaccard match, single-token guard, prune >365d, concurrent append |
Expand Down Expand Up @@ -668,4 +669,5 @@ locally (not installed to `~/.claude/`):
| v4.0.0 | 2026-07-02 | **"Signal-first, zero-decision" — full redesign of capture and command set.** `docs/DESIGN-V4.md` + `docs/SPEC-PORT-SINAPSIS.md` (port of Sinapsis v4.6.1, MIT, Luis Salgado). **BREAKING:** 17 of 24 commands deprecated to notice-only stubs (mapping in `docs/MIGRATION-V4.md`); 7 remain active (`cx-status`, `cx-maintain`, `cx-review`, `cx-eod`, `cx-gotcha`, `cx-backup`, `cx-restore`). New `/cx-maintain` (deterministic, cron-able: decay+purge+auto-validate+deterministic law-promotion+Jaccard dedup-by-subtopic+storage rotation+proposals↔instincts reconciliation+health check, writes `.review-digest.json`). New `/cx-review` (the only command with human judgment, weekly, fuses old validate+evolve-confirm+downvote+distill-swap into one shorthand pass). `hooks/observe.py`: per-line capture guards before trusting an error match (`[codex]`/npm-log prefixes, version listings, grep headers, `0 errors`, bare `warning:`), `output` cap 8000→10000, `err_msg` now the first non-guarded matching line. New `status: draft`/`confirmed` instinct lifecycle (`session-learner.js`, `injector-engine.js`) — drafts track silently, auto-promote at `occurrences>=5 AND sessions_seen>=3`; legacy instincts default `confirmed`. Law promotion gate replaced with a fully deterministic one (`conf>=0.95`, `projects_seen>=3`, `occurrences_v4>=10` via lazy `occurrences_legacy` migration, 0 noise 14d) — `auto-distill-candidates.md` buzón removed. Injector: JSON-fragment + prompt-injection guards at load, degenerate-trigger static validation, dedup switched from per-domain to per-subtopic (2 first words of id) with a soft 2-per-domain ceiling at conf>=0.85. `core/_cx-eod-gather.sh`: per-project `context` field, `[eod-eisenhower]` Q1-Q4 keyword classification of "for tomorrow" bullets, review-digest pending count surfaced. New test suites `test_distill_v4.sh` (6/6 PASS), `test_injector_v4.sh` (13/13 PASS). |
| v4.1.0 | 2026-07-04 | **Audit hardening (multi-agent audit + adversarial verify).** `session-start.py` injects full law text (not just line 1). `session-learner.js`: tombstone gate matches by trigger vs `proposals-history.jsonl`, dup-trigger dedup, `$HOME`→`~` paths, head+tail sampling. Reflexes: re-enabled `python3-bypass-write-tool`, hardened `bash-cat-use-read`. `storage-rotation.js`: prunes `cross-day-tracker` past 1.5× threshold, stale `.lock`/`.bak` cleanup, `timeline.jsonl` rotation. `distill_engine.py`: `prune_instinct_tracking()` + `reap_stale_nudge_state()` + CLI subcommands. `injector-engine.js`: session token budget 8000→12000 with a visible suppression warning, optional per-reflex `domains` filter. |
| v4.2.0 | 2026-07-05 | **Laws layer: de-dup, tier legibility, post-promotion audit** (`docs/DESIGN-laws-v4.2.md`; 3-agent adversarial pass). Fixed double injection (`read-instructions` 41×, `pref-fix` 17× redundant PreToolUse) — `injector-engine.js` skips instinct candidates with a `laws/{id}.txt` twin; `manual_swap_promote` archives the source instinct like `auto_promote_to_law`. `advisor-escalation` trimmed 1505→717 chars. Reverted the v4.1.0 `bash-cat-use-read` lookahead regression (broke `test_reflex_matchers`). Added law presentation split by tier (`[principios]`/`[herramienta]`, `core/laws-meta.default.json`) + `law_audit()` + `law-audit` CLI. |
| v4.2.2 | 2026-07-05 | **Learn markers reset in `/cx-maintain` Step 7.** Nothing cleared `.learn-pending` after `/cx-analyze` retired in v4, so the SessionStart "N+ new observations, run /cx-maintain" banner nagged forever. Step 7 (both `commands/cx-maintain.md` and `bin/cx-maintain.sh`) now snapshots the obs total into `.last-learn-count`, zeroes `.obs-count` and drops the flag. `test_cx_maintain_runner` 9→10 + suite inventoried in the tests table (gap since v4.0.0). |
| v4.2.1 | 2026-07-05 | **AD follow-up (Codex GPT-5.5 review of v4.1/v4.2).** `load_laws()` (`session-start.py`) now tolerant of a malformed `laws-meta.json` — a non-dict schema no longer crashes the whole SessionStart injection. `impact.archive` retention fixed from count-based `keep=5` (could delete chunks < 90 days old, contradicting the append-only "never deleted" contract) to 90-day age-based per `DESIGN-V4 §7`; the stale "never deleted" comments in `impact_log.py`/`session-learner.js` reconciled. New laws-meta robustness tests; version history caught up. |
2 changes: 1 addition & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ $CommandsDir = Join-Path $ClaudeDir "commands"
$HooksDir = Join-Path (Join-Path $ClaudeDir "hooks") "cortex"
$SettingsFile = Join-Path $ClaudeDir "settings.json"
$ClaudeMd = Join-Path $ClaudeDir "CLAUDE.md"
$NewVersion = "4.2.1"
$NewVersion = "4.2.2"

# v3.25.1 — explicit downgrade flag (parity with install.sh).
# A behind-remote repo would silently rewind hooks otherwise.
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ COMMANDS_DIR="$CLAUDE_DIR/commands"
HOOKS_DIR="$CLAUDE_DIR/hooks/cortex"
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
NEW_VERSION="4.2.1"
NEW_VERSION="4.2.2"

# v3.25.1 — explicit downgrade flag. The installer is a copy-not-merge of
# hooks/commands, so running an older `install.sh` over a newer install
Expand Down
21 changes: 21 additions & 0 deletions tests/test_cx_maintain_runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ status: confirmed
---
YAML

# v4.2.2 — seed learn markers + observations so Test 3b can prove the reset:
# observe.py increments .obs-count and touches .learn-pending at threshold;
# the runner must snapshot the total, zero the count and drop the flag.
mkdir -p "$CDIR/projects/testproj"
printf '{"ts":"2026-07-05T10:00:00Z","ev":"tc","tool":"Bash"}\n%.0s' 1 2 3 > "$CDIR/projects/testproj/observations.jsonl"
echo "7" > "$CDIR/.obs-count"
touch "$CDIR/.learn-pending"

# ── Test 1: first run exits 0 ────────────────────────────────────────────────
echo "--- Test 1: first run exits 0 ---"
OUT1=$(CORTEX_DIR="$CDIR" "$RUNNER" 2>&1)
Expand All @@ -85,6 +93,19 @@ else
fail ".last-distill or .last-dream missing"
fi

# ── Test 3b: learn markers reset (v4.2.2) ────────────────────────────────────
# Nothing cleared .learn-pending after /cx-analyze retired in v4, so the
# SessionStart "N+ new observations" banner nagged forever. The runner must
# snapshot the obs total, zero .obs-count and drop the flag.
echo "--- Test 3b: learn markers reset after run ---"
LEARN_COUNT=$(cat "$CDIR/.last-learn-count" 2>/dev/null)
OBS_COUNT=$(cat "$CDIR/.obs-count" 2>/dev/null)
if [ "$LEARN_COUNT" = "3" ] && [ "$OBS_COUNT" = "0" ] && [ ! -f "$CDIR/.learn-pending" ]; then
pass "Learn markers reset (.last-learn-count=3, .obs-count=0, .learn-pending gone)"
else
fail "Learn markers not reset: last-learn-count='$LEARN_COUNT' obs-count='$OBS_COUNT' pending=$([ -f "$CDIR/.learn-pending" ] && echo yes || echo no)"
fi

# ── Test 4: digest is valid JSON with expected keys ──────────────────────────
echo "--- Test 4: .review-digest.json is valid JSON ---"
if [ -f "$CDIR/.review-digest.json" ]; then
Expand Down
Loading