diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7409b64 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +name: ci + +# macOS only, deliberately: mcs is a Swift/Homebrew tool that installs on macOS, +# so it is the only platform that can actually install this pack. The suite runs +# the hooks exactly as mcs does, by executing them and letting the shebang pick +# the interpreter. +on: + push: + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + strategy: + fail-fast: false + matrix: + # 22.6 is the floor: `--experimental-strip-types` does not exist below it, + # and a bare '22' resolves to current 22.x, which cannot reach that range. + node: ['22.6.0', '22', '24'] + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: node is new enough to strip types + run: | + command -v git >/dev/null || { echo "::error::git is required"; exit 1; } + node --experimental-strip-types -e 'const [maj, min] = process.versions.node.split(".").map(Number); + if (maj < 22 || (maj === 22 && min < 6)) { console.error("::error::node " + process.versions.node + " cannot strip types"); process.exit(1); } + console.log("node " + process.versions.node + " strips types");' + + - name: install typecheck deps (CI-only, never shipped) + run: npm install --no-save --silent typescript@5.9.2 @types/node@22.9.0 + + - name: typecheck + run: npm run typecheck + + - name: unit, contract and behaviour tests + run: npm test + + # A migration artifact: it pinned the premise that commit subjects were + # unchanged by the port. Cheap to keep, not load-bearing going forward. + - name: the hostname equivalence holds on this runner + run: | + from_shell=$(hostname -s) + from_node=$(node -e 'process.stdout.write(require("node:os").hostname().split(".")[0])') + echo "shell='$from_shell' node='$from_node'" + [ "$from_shell" = "$from_node" ] || { echo "::error::hostname -s and os.hostname() disagree"; exit 1; } + + # The pack contains no shell at all. Hooks and pack scripts are TypeScript + # executed through their shebang. + - name: no shell crept back in + run: | + stray=$(find . -name '*.sh' -not -path './node_modules/*' -not -path './.git/*' || true) + if [ -n "$stray" ]; then + echo "::error::this pack contains no shell scripts" + echo "$stray" + exit 1 + fi + + # A test that writes outside its temp dir is a bug wherever it lands. + - name: the test run left the working tree clean + run: | + residue=$(git status --porcelain --untracked-files=all) + if [ -n "$residue" ]; then + echo "::error::the test run left files behind" + echo "$residue" + exit 1 + fi diff --git a/.gitignore b/.gitignore index 10179f4..e950fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ *.swo .idea/ .vscode/ + +# Node typecheck deps (CI + local only; runtime is zero-deps) +node_modules/ diff --git a/README.md b/README.md index 1528c70..e725485 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Shared Memories +# ๐Ÿง  Shared Memories A [tech pack](https://github.com/mcs-cli/mcs) that auto-syncs Claude Code's `.claude/memories/` across a team via a dedicated shared git repo. Captures are handled by [`mcs-cli/memory`](https://github.com/mcs-cli/memory) (the `continuous-learning` skill + semantic retrieval); this pack **shares** those captures across the team without anyone remembering to commit or push. @@ -6,12 +6,14 @@ Built for the [`mcs`](https://github.com/mcs-cli/mcs) configuration engine. ``` identifier: shared-memories -requires: mcs >= 2026.4.12 +requires: mcs >= 2026.9.3 ``` +**Contents** โ€” [When is this useful?](#-when-is-this-useful) ยท [The problem](#-the-problem) ยท [How it works](#-how-it-works) ยท [What's included](#-whats-included) ยท [Installation](#-installation) ยท [Directory structure](#-directory-structure) ยท [Migration](#-migration-from-an-existing-local-memories-folder) ยท [Side branches](#-optional-push-to-a-side-branch) ยท [Auto-push modes](#-auto-push-modes) ยท [Deletions](#-intentional-deletion-workflow) ยท [Troubleshooting](#-troubleshooting) ยท [Development](#-development) + --- -## When Is This Useful? +## ๐Ÿค” When Is This Useful? **You probably don't need this pack if** your team commits `.claude/memories/` directly into the project repo โ€” normal git workflow already shares those memories across the team and this pack adds nothing. @@ -23,7 +25,7 @@ requires: mcs >= 2026.4.12 --- -## The Problem +## ๐Ÿงฉ The Problem Claude Code's `.claude/memories/` is great โ€” you accumulate `learning_*.md` and `decision_*.md` files and Claude gets smarter about your codebase over time. But memories are **per-engineer**: when someone figures out a gnarly integration quirk or pins down a subtle architecture decision, only they benefit. @@ -32,36 +34,25 @@ The obvious fix is a shared git repo. Two friction points kill adoption: 1. **Remembering to push.** People forget. Memories sit on laptops for weeks. 2. **Branch protection on the shared repo.** If every Claude turn needs a PR + ticket + approval, nobody will bother pushing their tiny observations. -## The Solution +## ๐Ÿ” The Solution This pack implements a **closed-loop sharing system** that pulls the latest team memories at session start and pushes new ones when Claude finishes a turn. -``` - SHARED MEMORIES LOOP - - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ SESSION โ”‚ โ”‚ TEAM KB โ”‚ โ”‚ WORK โ”‚ โ”‚ STOP โ”‚ - โ”‚ START โ”‚โ”€โ”€โ”€โ”€>โ”‚ PULL โ”‚โ”€โ”€โ”€โ”€>โ”‚ SESSION โ”‚โ”€โ”€โ”€โ”€>โ”‚ AUTO-PUSH โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - ^ | | | - | | | filename guard | - | | | + configurable | - | v v push policy v - | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - | โ”‚ โ”‚ - | โ”‚ memories/ โ”‚ - | โ”‚ learning_background_task_watchdog_timeout.md โ”‚ - +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ learning_orm_batch_insert_memory_spike.md โ”‚ - โ”‚ decision_architecture_mvvm_coordinators.md โ”‚ - โ”‚ ... โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +```mermaid +flowchart TD + S([Session starts]) --> P["SessionStart hook
git pull --ff-only"] + P --> W["Work session
continuous-learning writes
learning_*.md ยท decision_*.md"] + W --> T["Stop hook
auto-push"] + T -->|"filename guard
+ configurable push policy"| R + R[("shared memories repo โ€” memories/

learning_background_task_watchdog_timeout.md
learning_orm_batch_insert_memory_spike.md
decision_architecture_mvvm_coordinators.md
โ€ฆ")] + R -->|"teammates fast-forward at
their next session start"| P ``` Captures still come from [`mcs-cli/memory`](https://github.com/mcs-cli/memory). This pack is the distribution layer that makes them team-shared. --- -## How It Works +## ๐Ÿ”ฉ How It Works ### The Pieces @@ -82,7 +73,7 @@ Captures still come from [`mcs-cli/memory`](https://github.com/mcs-cli/memory). 3. **During work** โ€” Claude uses the [`continuous-learning`](https://github.com/mcs-cli/memory) skill to write new `learning_*.md` / `decision_*.md` files -4. **Claude finishes a turn** โ€” the Stop hook collects dirty files and dispatches by mode (`MEMORIES_AUTOPUSH_MODE`, see [Auto-Push Modes](#auto-push-modes)): +4. **Claude finishes a turn** โ€” the Stop hook collects dirty files and dispatches by mode (`MEMORIES_AUTOPUSH_MODE`, see [Auto-Push Modes](#-auto-push-modes)): - **Naming guardrail (all modes)** โ€” any file failing `^memories/(learning|decision)_[a-zA-Z0-9_-]+\.md$` halts everything until renamed - **`auto` (default)** โ€” adds/mods auto-pushed; deletions parked in the working tree for manual review - **`full`** โ€” adds/mods AND deletions auto-pushed in one commit @@ -90,17 +81,37 @@ Captures still come from [`mcs-cli/memory`](https://github.com/mcs-cli/memory). 5. **Next session** โ€” teammates pull your new memories via SessionStart and the loop continues +The Stop hook in full, since it is where all the policy lives: + +```mermaid +flowchart TD + A([Claude finishes a turn]) --> B{"anything uncommitted
or unpushed?"} + B -->|no| Z([exit 0]) + B -->|yes| C{"every dirty file matches
memories/learning_*.md
or memories/decision_*.md?"} + C -->|no| D["list the offenders,
push nothing"] --> Z + C -->|yes| E{MEMORIES_AUTOPUSH_MODE} + E -->|auto| F["stage adds and mods,
park deletions for review"] + E -->|full| G["stage everything,
deletions included"] + E -->|review| H["print the per-file report,
commit nothing"] + F --> I["commit ยท pull --rebase --autostash ยท push
retry with jitter if rejected"] + G --> I + H --> Z + I --> Z +``` + --- -## What's Included +## ๐Ÿ“ฆ What's Included ### Session Hooks | Hook | Event | What It Does | |------|-------|-------------| -| **memories_pull.sh** | `SessionStart` | Fast-forwards the shared memories checkout; emits a mode-aware warning if previous state is stuck (or, in `review` mode, summarises pending review) | -| **memories_autopush.sh** | `Stop` (async) | Dispatches by `MEMORIES_AUTOPUSH_MODE` mode (`auto` / `full` / `review`); filename guardrail applies in every mode | -| **memories_announce.sh** | `PostToolUse` (Write/Edit/MultiEdit) | In `review` mode only, surfaces the just-written memory to Claude's context so it mentions pending review in chat. Silent in `auto` and `full` | +| **pull.mts** | `SessionStart` | Fast-forwards the shared memories checkout; emits a mode-aware warning if previous state is stuck (or, in `review` mode, summarises pending review) | +| **autopush.mts** | `Stop` (async) | Dispatches by `MEMORIES_AUTOPUSH_MODE` mode (`auto` / `full` / `review`); filename guardrail applies in every mode | +| **announce.mts** | `PostToolUse` (Write/Edit/MultiEdit) | In `review` mode only, surfaces the just-written memory to Claude's context so it mentions pending review in chat. Silent in `auto` and `full` | + +Each runs as `node --experimental-strip-types --disable-warning=ExperimentalWarning `: mcs prefixes the interpreter the hook declares in `hookInterpreter`, and never looks at the shebang. They install to `.claude/hooks/shared-memories/`, with the library they import beside them in `lib/`. ### Slash Commands @@ -112,7 +123,7 @@ Captures still come from [`mcs-cli/memory`](https://github.com/mcs-cli/memory). | Script | When | What It Does | |--------|------|-------------| -| **configure-memories.sh** | `mcs sync` | Sparse-clones the shared repo, sets up the symlink, migrates any pre-existing `.claude/memories/` into the shared folder | +| **configure-memories.ts** | `mcs sync` | Sparse-clones the shared repo, sets up the symlink, migrates any pre-existing `.claude/memories/` into the shared folder | ### CLAUDE.local.md Section @@ -131,15 +142,18 @@ Captures still come from [`mcs-cli/memory`](https://github.com/mcs-cli/memory). | Dep | Via | |-----|-----| -| **jq** | brew | +| **Node.js 22.6+** | brew | + +Node runs the TypeScript directly โ€” there is no build step, no `node_modules`, and no runtime dependencies. --- -## Installation +## ๐Ÿš€ Installation ### Prerequisites - macOS +- Node.js 22.6 or newer (where `--experimental-strip-types` landed) - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI - [mcs](https://github.com/mcs-cli/mcs) CLI - [`mcs-cli/memory`](https://github.com/mcs-cli/memory) (companion capture pack โ€” produces the `learning_*.md` / `decision_*.md` files this pack shares) @@ -169,7 +183,7 @@ During `mcs sync`, you'll be prompted for: |--------|-------------|---------| | **MEMORIES_REPO_URL** | Clone URL for the shared memories repo, e.g. `git@github.com:org/memories.git` | *(required)* | | **MEMORIES_BRANCH** | Branch that holds the memory files and this pack | `main` | -| **MEMORIES_AUTOPUSH_MODE** | Stop-hook behavior โ€” `auto` (writes auto-pushed, deletions parked), `full` (writes + deletions auto-pushed), or `review` (nothing auto, per-turn report). See [Auto-Push Modes](#auto-push-modes). | `auto` | +| **MEMORIES_AUTOPUSH_MODE** | Stop-hook behavior โ€” `auto` (writes auto-pushed, deletions parked), `full` (writes + deletions auto-pushed), or `review` (nothing auto, per-turn report). See [Auto-Push Modes](#-auto-push-modes). | `auto` | > **Install per-project, not globally.** Run `mcs sync` from each project's root (the directory that contains `.claude/`, not `.claude/` itself) โ€” do **not** install this pack into your user-level `~/.claude/` directory. > @@ -182,7 +196,7 @@ During `mcs sync`, you'll be prompted for: --- -## Directory Structure +## ๐Ÿ“ Directory Structure ``` shared-memories/ @@ -191,22 +205,33 @@ shared-memories/ โ”‚ โ””โ”€โ”€ approve-memories.md # Slash command for review-mode approval โ”œโ”€โ”€ config/ โ”‚ โ””โ”€โ”€ settings.json # Templated env block โ€” ships MEMORIES_AUTOPUSH_MODE -โ”œโ”€โ”€ hooks/ -โ”‚ โ”œโ”€โ”€ memories_pull.sh # SessionStart: pull + stuck-state warning -โ”‚ โ”œโ”€โ”€ memories_autopush.sh # Stop: auto-commit + push (async) -โ”‚ โ””โ”€โ”€ memories_announce.sh # PostToolUse: review-mode nudge to Claude (sync) -โ”œโ”€โ”€ scripts/ -โ”‚ โ”œโ”€โ”€ configure-memories.sh # Sparse clone + symlink + migration -โ”‚ โ”œโ”€โ”€ doctor-memories.sh # Setup health check -โ”‚ โ””โ”€โ”€ doctor-memories-remote.sh # Remote-access health check -โ””โ”€โ”€ templates/ - โ””โ”€โ”€ instructions.md # CLAUDE.local.md section โ€” what this dir is +โ”œโ”€โ”€ runtime/ # Installed to .claude/hooks/shared-memories/ +โ”‚ โ”œโ”€โ”€ pull.mts # SessionStart: pull + stuck-state warning +โ”‚ โ”œโ”€โ”€ autopush.mts # Stop: auto-commit + push (async) +โ”‚ โ”œโ”€โ”€ announce.mts # PostToolUse: review-mode nudge to Claude (sync) +โ”‚ โ””โ”€โ”€ lib/ # git, paths, naming, mode, pending, report, push +โ”œโ”€โ”€ scripts/ # Run in place from the pack checkout +โ”‚ โ”œโ”€โ”€ configure-memories.ts # Sparse clone + symlink + migration +โ”‚ โ”œโ”€โ”€ doctor-memories.ts # Setup health check +โ”‚ โ””โ”€โ”€ doctor-memories-remote.ts # Remote-access health check +โ”œโ”€โ”€ tests/ # node:test โ€” unit, contract and behaviour +โ”‚ โ””โ”€โ”€ golden/ # Behaviour recordings the suite checks against +โ”œโ”€โ”€ templates/ +โ”‚ โ””โ”€โ”€ instructions.md # CLAUDE.local.md section โ€” what this dir is +โ”œโ”€โ”€ .github/workflows/ci.yml # macOS ร— Node 22.6/22/24 +โ”œโ”€โ”€ package.json # No dependencies; test + typecheck scripts +โ””โ”€โ”€ tsconfig.json # Strict, erasable-syntax-only, no emit ``` On engineer disks, the pack materializes as: ``` /.claude/ +โ”œโ”€โ”€ hooks/shared-memories/ # run by mcs via each hook's declared hookInterpreter +โ”‚ โ”œโ”€โ”€ pull.mts +โ”‚ โ”œโ”€โ”€ autopush.mts +โ”‚ โ”œโ”€โ”€ announce.mts +โ”‚ โ””โ”€โ”€ lib/ # imported as ./lib/โ€ฆ by the entries beside it โ”œโ”€โ”€ .memories-repo/ # sparse clone of MEMORIES_BRANCH โ”‚ โ”œโ”€โ”€ README.md, LICENSE, etc. # any root-level files your repo ships โ”‚ โ””โ”€โ”€ memories/ @@ -219,7 +244,7 @@ The clone uses `--sparse --filter=blob:none --single-branch` so only the `memori --- -## Migration From an Existing Local Memories Folder +## ๐Ÿšš Migration From an Existing Local Memories Folder Engineers who already have `.claude/memories/` populated (from `mcs-cli/memory`, Claude Code's native memory, or manual use) are handled automatically on first `mcs sync`: @@ -230,7 +255,21 @@ Engineers who already have `.claude/memories/` populated (from `mcs-cli/memory`, 5. Well-named migrated files are auto-committed and pushed so they immediately become team knowledge 6. If nothing is left in the backup dir, it's cleaned up automatically -If any step fails partway, an `ERR` trap restores the original folder from the backup โ€” you're never left with a broken setup and no memories. +If any step fails partway, the failure path restores the original folder from the backup โ€” you're never left with a broken setup and no memories. + +Each file in the backup takes one of four paths: + +```mermaid +flowchart TD + A["a file in the migration backup"] --> B{"a file of that name
already in the shared repo?"} + B -->|yes| C["skip โ€” the shared copy wins,
yours stays in the backup"] + B -->|no| D{"deleted somewhere in
the branch's history?"} + D -->|yes| E["hold back, naming the
commit that removed it"] + D -->|no| F["import into memories/"] + F --> G{"matches the naming rule?"} + G -->|yes| H["auto-commit and push"] + G -->|no| I["leave untracked,
with a rename nudge"] +``` ### Why previously-deleted files are held back @@ -261,7 +300,7 @@ A separate gap worth knowing about when you audit: filenames alone under-count d --- -## Optional: Push to a Side Branch +## ๐ŸŒฟ Optional: Push to a Side Branch If your org enforces PR + ticket + approval on the default branch of your memories repo, every Claude Stop auto-pushing to it would turn each memory into a PR. That kills adoption. @@ -280,7 +319,7 @@ Normal commits (including ones that delete files via `memory-audit`) are unaffec --- -## Auto-Push Modes +## ๐Ÿšฆ Auto-Push Modes The Stop hook's behavior is set during `mcs sync` via the `MEMORIES_AUTOPUSH_MODE` prompt. The chosen value is written to `.claude/settings.local.json`'s `env` block (per-user / project-local). To change modes later, re-run `mcs sync` and pick a different value, or edit `.claude/settings.local.json` directly. @@ -311,13 +350,13 @@ Discard local changes: โ€ฆ The same pending set is reported once per session โ€” repeated turns within the session stay silent so the report doesn't spam every prompt. SessionStart resets the dedupe so unresolved changes re-surface in the next session instead of being buried forever. -In `review` mode, Claude is also told about each memory write through a separate PostToolUse hook (`memories_announce.sh`), so it can proactively mention pending review in the same turn and invoke `/approve-memories` when you confirm โ€” without needing to wait for the terminal report. The terminal report and the in-conversation nudge are independent channels: the report goes to your terminal, the nudge goes to Claude's context. `auto` and `full` modes keep both channels silent. +In `review` mode, Claude is also told about each memory write through a separate PostToolUse hook (`announce.mts`), so it can proactively mention pending review in the same turn and invoke `/approve-memories` when you confirm โ€” without needing to wait for the terminal report. The terminal report and the in-conversation nudge are independent channels: the report goes to your terminal, the nudge goes to Claude's context. `auto` and `full` modes keep both channels silent. Pull is always automatic regardless of mode โ€” incoming team memories arrive at session start. --- -## Intentional Deletion Workflow +## ๐Ÿงน Intentional Deletion Workflow When you legitimately want to remove stale memories (typically after running the `memory-audit` skill from `mcs-cli/memory`), invoke the slash command: @@ -333,7 +372,7 @@ If your workflow makes the friction unnecessary, set `MEMORIES_AUTOPUSH_MODE=ful --- -## Troubleshooting +## ๐Ÿ”ง Troubleshooting ```bash mcs pack validate . # verify techpack.yaml + file refs @@ -356,7 +395,24 @@ Anything not matching `memories/(learning|decision)_*.md` needs renaming. --- -## Links +## ๐Ÿงช Development + +```bash +npm test # unit, contract and behaviour suites +npm run typecheck # tsc --noEmit (deps install ad hoc in CI) +``` + +TypeScript run directly by Node: no build step, no runtime dependencies, no lockfile. `tsconfig.json` sets `erasableSyntaxOnly`, so the syntax stays strippable โ€” no `enum`, no `namespace`, no constructor parameter properties. + +**`tests/golden/` is the behaviour contract.** Each file pins what the pack produces for one fixture: stdout, stderr, exit code, and the resulting repository state. The suite builds a throwaway project with a real git remote, installs the hooks, runs them through their shebang (pinned by `tests/manifest.test.ts` to the same command the manifest declares), and compares. A diff therefore means the pack's behaviour changed, not that a test went stale. + +Fixtures carry an `expect` pattern asserted against the recording, so a fixture where nothing happens fails rather than passing vacuously. Machine- and day-dependent values โ€” temp paths, hostname, dates, git's relative timestamps โ€” are normalised; everything else is byte-exact. + +CI runs on macOS across Node 22 and 24. Besides the typecheck and the suite it guards three things: the pack contains no shell at all, `hostname -s` still matches `os.hostname().split(".")[0]` (the commit subjects depend on it), and the suite leaves the working tree clean. + +--- + +## ๐Ÿ”— Links - [MCS](https://github.com/mcs-cli/mcs) โ€” the configuration engine - [Creating Tech Packs](https://github.com/mcs-cli/mcs/blob/main/docs/creating-tech-packs.md) โ€” guide for building your own @@ -367,6 +423,6 @@ Anything not matching `memories/(learning|decision)_*.md` needs renaming. --- -## License +## ๐Ÿ“„ License MIT diff --git a/commands/approve-memories.md b/commands/approve-memories.md index d03f1ad..083ddbf 100644 --- a/commands/approve-memories.md +++ b/commands/approve-memories.md @@ -14,7 +14,7 @@ Run `git -C .claude/.memories-repo status --porcelain -- memories/` and `git -C ## 2. Filename guardrail -Every dirty file under `memories/` must match `^memories/(learning|decision)_[a-zA-Z0-9_-]+\.md$` โ€” the same pattern the Stop hook enforces (keep in sync with `hooks/memories_autopush.sh` `allowed_pattern`, `hooks/memories_announce.sh` regex, and `scripts/configure-memories.sh` `allowed_pattern`). If any file fails, list the offenders, instruct the user to rename them to `memories/learning__.md` or `memories/decision__.md`, and stop without committing. +Every dirty file under `memories/` must match `^memories/(learning|decision)_[a-zA-Z0-9_-]+\.md$` โ€” the same pattern the Stop hook enforces, defined once in `runtime/lib/naming.mts`. If any file fails, list the offenders, instruct the user to rename them to `memories/learning__.md` or `memories/decision__.md`, and stop without committing. ## 3. Stage, commit, push diff --git a/hooks/memories_announce.sh b/hooks/memories_announce.sh deleted file mode 100755 index 5476c56..0000000 --- a/hooks/memories_announce.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -euo pipefail -trap 'rc=$?; echo "memories_announce: aborted (rc=$rc) at line $LINENO: $BASH_COMMAND" >&2; exit 0' ERR - -# PostToolUse hook: surface review-mode memory writes to Claude's conversation. -# -# In review mode the Stop hook's pending-changes report goes to the terminal -# only โ€” Stop runs hookAsync: true, so its stdout never re-enters Claude's -# context. This hook fills that gap by injecting additionalContext after a -# memory file write, so Claude can proactively mention pending review. -# -# Silent in auto/full modes โ€” those auto-push and don't need a Claude-visible -# nudge. - -command -v jq >/dev/null 2>&1 || exit 0 - -input_data=$(cat) || exit 0 -file_path=$(echo "$input_data" | jq -r '.tool_input.file_path // empty' 2>/dev/null) || exit 0 - -# hookMatcher in techpack.yaml scopes us to Write/Edit/MultiEdit, but those -# tools touch many paths; restrict to the exact memory-file naming convention -# the autopush guardrail accepts. A loose glob would announce phantom pending -# state for files autopush will silently reject (e.g. "learning_foo bar.md"). -# keep in sync with hooks/memories_autopush.sh allowed_pattern, -# scripts/configure-memories.sh allowed_pattern, and commands/approve-memories.md guardrail -[[ "$file_path" =~ (^|.*/)\.claude/memories/(learning|decision)_[a-zA-Z0-9_-]+\.md$ ]] || exit 0 - -# Review mode only โ€” auto/full/unset/unknown all auto-push and need no nudge. -case "${MEMORIES_AUTOPUSH_MODE:-}" in - review) ;; - *) exit 0 ;; -esac - -msg="Memory file saved at $file_path (MEMORIES_AUTOPUSH_MODE=review). This memory will not auto-push. Mention the pending memory to the user before ending your turn so they can decide whether to approve or discard. If they approve, run /approve-memories." - -jq -n --arg ctx "$msg" \ - '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $ctx}}' diff --git a/hooks/memories_autopush.sh b/hooks/memories_autopush.sh deleted file mode 100755 index 89890f2..0000000 --- a/hooks/memories_autopush.sh +++ /dev/null @@ -1,386 +0,0 @@ -#!/bin/bash -set -euo pipefail -# Hooks must never fail-fast onto Claude Code, but silent aborts are -# undebuggable. Log the failing line/command to stderr before exiting 0. -trap 'rc=$?; echo "memories_autopush: aborted (rc=$rc) at line $LINENO: $BASH_COMMAND" >&2; exit 0' ERR - -# Stop hook: handle shared-memory file changes per MEMORIES_AUTOPUSH_MODE mode. -# -# Modes (read from .claude/settings.local.json's `env` block โ€” mcs writes the -# value from the techpack prompt during `mcs sync`): -# -# auto โ€” writes auto-pushed; deletions parked for manual review (default) -# full โ€” writes AND deletions auto-pushed -# review โ€” nothing auto; prints a per-turn pending-changes report -# -# In every mode, files that don't match memories/(learning_|decision_).md -# halt everything until renamed โ€” naming policy is orthogonal to push policy. -# Unset, empty, or unknown values fall through to `auto`. -# -# After memory-audit (auto mode only) intentionally removes stale files, the -# user approves the deletions with the pack's slash command: -# /approve-memories audit cleanup - -command -v git >/dev/null 2>&1 || { echo "memories_autopush: git not found; skipping" >&2; exit 0; } -command -v jq >/dev/null 2>&1 || { echo "memories_autopush: jq not found; skipping" >&2; exit 0; } - -input_data=$(cat) || exit 0 -echo "$input_data" | jq '.' >/dev/null 2>&1 || { echo "memories_autopush: stdin is not valid JSON; skipping" >&2; exit 0; } - -# Anchor on the script's own path, not stdin `cwd`. The hook ships at -# /.claude/hooks/shared-memories/