diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fabc707..eb73ccc 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -22,7 +22,7 @@ { "name": "project-memory", "source": "./plugins/project-memory", - "description": "In-repo project memory: committed Team memory and git-ignored per-user Private memory, with grooming (memory-review-session) and migration (migrate-memory) skills, shipped as a Rules payload" + "description": "In-repo project memory: committed Team memory and git-ignored per-user Private memory, with grooming (memory-review-session), migration (migrate-memory), and the Auto-memory redirect (redirect-memory) skills, shipped as a Rules payload" } ] } diff --git a/.claude/rules/elements-of-style.md b/.claude/rules/elements-of-style.md new file mode 100644 index 0000000..b8a701c --- /dev/null +++ b/.claude/rules/elements-of-style.md @@ -0,0 +1,17 @@ +# Elements-of-style pass on prose + +Every committed Markdown file in this repo is prose for humans — `docs/` +artifacts (specs, plans, ADRs, the glossary), plugin content (rules, +skills, READMEs), and the repo's own README and rules — and gets an +elements-of-style pass: + +- **Writing a new document**: when the + `elements-of-style:writing-clearly-and-concisely` skill is available, + invoke it BEFORE drafting — never write "from memory of the rules". +- **Editing an existing document**: run an explicit editing pass with + that skill's full rules over the changed prose. +- Without the plugin the rule stays silent: no substitute pass, and the + edit proceeds normally. + +The pass binds wording, never decisions — it changes how sentences carry +a document's content, not what the document decides. diff --git a/README.md b/README.md index 7c9a393..85de10f 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ Marketplace of [Claude Code](https://code.claude.com) plugins by Missing Bits. ## Plugins -| Plugin | Description | -|--------|-------------| -| `working-process` | Spec-driven working process: grilling-session, architect-session, system-designer-session and sync-rules skills, architect and plan-adversary review agents, two verdict-free consultation agents, distributed process rules | -| `python-standards` | Python coding standards for uv + ruff + pytest + pyright: area skills, code-review stack, plan-review checklist, distributed toolchain rule | -| `salesforce-standards` | Salesforce coding standards for the sf CLI toolchain: area skills (Apex, LWC, Flow, data/security model, legacy UI), code-review stack, plan-review checklist, distributed toolchain rule | -| `project-memory` | In-repo project memory: committed Team memory (`docs/memory/`) and per-user Private memory (`.claude/memory/`), with `memory-review-session` and `migrate-memory` skills, distributed as a Rules payload | +| Plugin | Docs | Description | +|--------|------|-------------| +| `working-process` | [README](plugins/working-process/README.md) | Spec-driven working process: grilling-session, architect-session, system-designer-session and sync-rules skills, architect and plan-adversary review agents, two verdict-free consultation agents, distributed process rules | +| `python-standards` | [README](plugins/python-standards/README.md) | Python coding standards for uv + ruff + pytest + pyright: area skills, code-review stack, plan-review checklist, distributed toolchain rule | +| `salesforce-standards` | [README](plugins/salesforce-standards/README.md) | Salesforce coding standards for the sf CLI toolchain: area skills (Apex, LWC, Flow, data/security model, legacy UI), code-review stack, plan-review checklist, distributed toolchain rule | +| `project-memory` | [README](plugins/project-memory/README.md) | In-repo project memory: committed Team memory (`docs/memory/`) and per-user Private memory (`.claude/memory/`), with `memory-review-session`, `migrate-memory`, and `redirect-memory` skills, distributed as a Rules payload | ## License diff --git a/docs/domain/adr/0002-own-entry-format.md b/docs/domain/adr/0002-own-entry-format.md new file mode 100644 index 0000000..7e6563f --- /dev/null +++ b/docs/domain/adr/0002-own-entry-format.md @@ -0,0 +1,25 @@ +--- +ticket: none +--- + +# Project memory defines its own entry format + +Auto-memory keeps entries in a shape — `name`, `description`, +`metadata.type` with a four-value taxonomy — that the platform never +documents; the published contract for entry bodies is "any markdown", and the +surrounding surface moved four times inside one minor line. Project memory +therefore converges on that shape without adopting it: the plugin's rules +define every field on the plugin's own terms and adopt only fields that earn +their place there, so a field like `description` is kept because it gives the +index line a source, not because Auto-memory writes one. The asymmetry decides +it — the store holds durable, versioned, team-shared data in git, while the +format is a moving internal detail of a self-updating tool, and binding the +first to the second would pay for interoperability with other people's +repositories. + +The consequence is that compatibility is textual rather than contractual: a +tolerance clause keeps unknown frontmatter keys intact so both writers can +share a file, and nothing in the rules calls Auto-memory the source of the +format. Making the store double as the Auto-memory directory would reverse +this decision, so that later choice has to face it deliberately instead of +sliding past it. diff --git a/docs/domain/adr/0003-hybrid-store-adoption.md b/docs/domain/adr/0003-hybrid-store-adoption.md new file mode 100644 index 0000000..5a54855 --- /dev/null +++ b/docs/domain/adr/0003-hybrid-store-adoption.md @@ -0,0 +1,25 @@ +--- +ticket: none +--- + +# Private memory may double as the Auto-memory directory, opt-in + +ADR 0002 closed with: "Making the store double as the Auto-memory +directory would reverse this decision, so that later choice has to face +it deliberately instead of sliding past it." This record is that choice, +faced. The hybrid adopts exactly two platform-owned identities — the +directory (`autoMemoryDirectory` may point at `.claude/memory/`) and the +index filename (`MEMORY.md`, the only name Auto-memory loads, adopted +globally so the format has no mode variants) — because in a Hybrid store +both pass ADR 0002's own test: they earn their place as the only way the +harness reads the store at all. + +Everything else stands. The plugin defines its own entry format, +converges on Auto-memory's without adopting it, and keeps the tolerance +clause for keys the harness writes; an entry Auto-memory writes into a +Hybrid store is an ordinary note whose missing H1 is format debt. The +adoption is opt-in per developer and per Environment, carried by one key +in `.claude/settings.local.json` written and removed by the +redirect-memory skill; declining it leaves the store purely rule-driven. +ADR 0002 remains in force for the format — this record narrows it only +at the directory-and-name boundary. diff --git a/docs/domain/glossary.md b/docs/domain/glossary.md index bec4c70..8dace8d 100644 --- a/docs/domain/glossary.md +++ b/docs/domain/glossary.md @@ -45,6 +45,16 @@ wins, mirroring the Rules engine's project-over-user conflict rule. Part of the review-reports contract: the paths may not drift independently. _Avoid_: discovery, probe (unqualified) +**Store probe**: +The existence check a plugin other than project-memory runs to decide +whether the reviewed project keeps a Project-memory part: one test per +part, on the part's directory (`docs/memory/`, `.claude/memory/`) — never +on a file inside it. Three surfaces run it (the review-reports +Candidate-gap park offer and the two standards code-review restatements). +Distinct from the Contract probe, which finds the installed report +contract; the unqualified word stays banned there. +_Avoid_: index check, store check + **Review report**: The single persistent document one code-review run writes under `docs/code-review/` of the reviewed project — one run, one report, @@ -286,12 +296,41 @@ asked, and NOT a Process directory — a per-user store under the `.claude/` config namespace, governed by the Project-memory rule. _Avoid_: local memory +**Auto-memory**: +The harness's own memory mechanism — the notes Claude Code writes for itself, +their `MEMORY.md` index and its load budget, and the `autoMemoryDirectory` / +`autoMemoryEnabled` settings that place and disable it. The mechanism, never a +store: the store it manages by default is Home-dir memory. Project memory +borrows none of its machinery — even a Hybrid store only points the harness +at the plugin's directory. +_Avoid_: native memory, auto memory (unhyphenated) + **Home-dir memory**: -The built-in Claude Code memory at `~/.claude/…/memory/`, auto-loaded by the -harness. The store the Project-memory rule redirects project-scoped writes -away from; it keeps cross-project and personal facts. +The store Auto-memory manages when no redirect points it elsewhere — one +per checkout path and per Environment, kept by default under +`~/.claude/projects//memory/`; a default, not a definition, since +`autoMemoryDirectory` may place it anywhere. The store the Project-memory +rule redirects project-scoped writes away from; it keeps cross-project and +personal facts. _Avoid_: native memory +**Hybrid store**: +The Private memory store that is the current session's Auto-memory +directory — the opt-in redirect (`autoMemoryDirectory` pointing at +`.claude/memory/`) honored in that session and landing on the store. A property of the +session, never of the checkout: the same store may be hybrid in one +environment and plain in another. Team memory is never one — Auto-memory's +team mounts are server-backed, so a repo directory cannot join. +_Avoid_: hybrid memory, hybrid mode + +**Environment**: +One filesystem view a session runs in — its own path namespace, settings +home (`~/.claude`), and trust record: the host, a dev container, a WSL +distro. The unit the redirect activates per, and the unit each Home-dir +memory store belongs to; one checkout may be visible from several +environments through different absolute paths. +_Avoid_: machine (where the path namespace is what matters) + **Idea entry**: A Project-memory entry with the idea shape: an `idea-` filename prefix and lifecycle frontmatter (`status` parked → spec'd | dropped, a `spec:` pointer, @@ -303,18 +342,18 @@ _Avoid_: backlog item **Archive**: The closed-entry record of a Project-memory part — `ARCHIVE.md`, holding one line per closed entry in a **Done** or **Dropped** section, read on demand and -never at session start. Distinct from a live entry (listed in `INDEX.md`): a +never at session start. Distinct from a live entry (listed in `MEMORY.md`): a closed entry keeps no body, only its archive line. _Avoid_: archive folder, backlog **Live entry**: -A Project-memory entry currently listed in `INDEX.md` — an active note or a +A Project-memory entry currently listed in `MEMORY.md` — an active note or a `parked` idea. The only entries reachable at session start (via the index). Opposite of an archived (closed) entry. _Avoid_: active entry, open entry **Close (an entry)**: -Move an entry out of `INDEX.md` when it reaches a terminal state — Done (its +Move an entry out of `MEMORY.md` when it reaches a terminal state — Done (its content now lives in a spec, ADR, glossary, or another artifact) or Dropped (abandoned) — leaving a one-line Archive record. Deleting an obsolete entry is NOT a close: it leaves no Archive line. diff --git a/docs/plans/2026-08-10-memory-entry-format.md b/docs/plans/2026-08-10-memory-entry-format.md new file mode 100644 index 0000000..5a151d7 --- /dev/null +++ b/docs/plans/2026-08-10-memory-entry-format.md @@ -0,0 +1,503 @@ +--- +ticket: none +date: 2026-08-10 +status: implemented +adversary: blocking (resolved 2026-08-10) +branch: feature/memory-entry-format +base: develop +--- + +# Project-memory entry format Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Give every Project-memory entry a required `description`, make the whole `INDEX.md` line a projection of its entry, pin the plugin's own frontmatter fields to the top level, and tell the rules to leave other tools' frontmatter keys alone. + +**Architecture:** A rules-and-skills plugin gains edits to one rule file, two SKILL.md files and its README. No executable code — every deliverable is Markdown. "Tests" are `claude plugin validate`, hand review of rule and skill frontmatter, and greps that prove the intended text landed and the unintended text did not. + +**Tech Stack:** Claude Code Rules payloads (Markdown with optional `paths:`), skills (SKILL.md with YAML frontmatter), `claude` CLI. + +**Spec:** `docs/specs/2026-08-10-project-memory-entry-format-design.md` (grilled 2026-08-10; architect round 1 `concerns`, resolved; round 2 `LGTM` on the amended spec). + +## Global Constraints + +- Public repo: no machine-specific paths (`/home//…`), no company or client names, all committed text English. +- Commit messages: ONE line, conventional-commit subject (`type:`/`type(scope):`), no body, no trailers (no `Co-Authored-By`). +- **No version bump on this branch** unless the change is dogfooded — the release PR from `develop` to `master` mints the bump. Dogfooding sets `0.4.0-dev.memory-entry-format` (Task 5 decides): the ANTICIPATED next release, never the shipped `0.3.0` with a suffix bolted on, which semver reads as older than what is already installed. +- `claude plugin validate .` and `claude plugin validate plugins/project-memory` must pass before each commit touching the plugin. `validate` does NOT check `rules/` or `skills/` frontmatter — review those by hand; quote any YAML scalar containing `: `. +- The core rule (`project-memory.md`) ships with NO `paths:` (always-on); the conventions rule keeps `paths:` for `docs/memory/**`, `.claude/memory/**`. This plan does not change either frontmatter block. +- Mentions of other plugins stay conditional ("when the project keeps…", "when the working-process rules are installed"). Committed project rules load for people who do not have those plugins. +- Glossary terms bind: **Auto-memory** is the harness mechanism, **Home-dir memory** the store it manages by default. "Native memory" is `_Avoid_`-banned — never write it. +- **Out of scope, do not touch:** `working-process`, `python-standards`, `salesforce-standards`; the index filename (`INDEX.md` stays); `ARCHIVE.md`'s format; any `autoMemoryDirectory` redirect. +- Do NOT create `evals/trigger-evals.json` or any eval artifact for the edited skills. + +## Review rounds + +- **Plan-adversary**, 2026-08-10, **Sonnet 5** — the prescribed tier for a + small mechanical plan (one family below the most capable, never the + cheapest), not a fallback. Verdict **blocking**: two Important, one Minor, + all resolved inline. The round verified every anchor text in Tasks 2-5 + against the real files, and confirmed the plan's out-of-scope claims about + the three other plugins and the surviving `^ticket:` sweep. + 1. Important — the dogfooding version `0.3.0-dev.memory-entry-format` + reuses the already-released `0.3.0`, and a prerelease sorts below its own + release, so `sync-rules` would read the update as a downgrade against an + installed manifest recording `0.3.0` and trip its Direction gate. + Resolved: the number is now the anticipated next release, + `0.4.0-dev.memory-entry-format`, with the reason recorded in Task 5 + Step 2 and in Global Constraints. + 2. Important — Task 3 Step 3 verified both of that task's edits with one + grep whose `description` alternative matches the skill's own YAML + frontmatter, so it passed whether or not the step-4 rewrite landed. + Resolved: one grep per edit, plus a negative check that the old step 4 + is gone rather than duplicated. + 3. Minor — Task 1 staged the glossary but its commit subject named only the + spec, plan and ADR. Resolved: split into two commits, each naming what + it carries. +- **Plan-adversary round 2**, 2026-08-10, **Opus 5** — above the prescribed + tier at the developer's request, so no fallback record. Verdict + **blocking**: six Important, five Minor, all resolved inline. The round + re-derived round 1's fixes and confirmed them, then found what round 1 had + missed. Three findings were spec-level and are recorded as amendments in the + spec itself. + 1. Important — the projection's left half had no source: nothing in the + plugin required an entry to have an H1 (`rg -n 'H1' plugins/project-memory/` + returned nothing). Resolved in the spec and in Task 2 Step 1. + 2. Important — migration would have destroyed the title: home-dir entries + carry it only in `name`, which the plan dropped, while the next + (unchanged) bullet deletes the source file. Resolved: `name` becomes the + new entry's H1, its slug the filename. + 3. Important — the rule and the audit shipped contradictory instructions, + one banning direct edits of an index line and the other prescribing + exactly that. Resolved: one vocabulary — a line is re-projected from the + entry, never authored. + 4. Important — Task 5's marketplace check asserted that `plugin.json` and + the catalog carry the same description. They deliberately differ, and + the grep never printed the catalog's line. Resolved: the check is now + name-identity plus a `git diff --name-only` proving both files untouched. + 5. Important — two shipped surfaces still advertised "sharpening index + lines": the skill's own `description:` frontmatter and README line 52. + Resolved: both updated, with the trigger phrases held verbatim. + 6. Important — the "one grep per edit" standard from round 1 had been + applied only in Task 3; Tasks 2 and 4 still verified several edits with + one alternation. Resolved: split throughout, each with its own + expectation. + 7. Minor — the `type` hint named a top-level key; the harness nests it under + `metadata:`. Resolved. + 8. Minor — the dogfooded prerelease obliges the release to mint at + `0.4.0` or higher, or the gate trips in reverse. Resolved: recorded in + Task 5 Step 2. + 9. Minor — the sweep's `INDEX.md` grep over the other three plugins printed + the same matches whether or not they were edited. Resolved: replaced + with `git diff --name-only`, expecting empty. + 10. Minor — no task closed the lifecycle. Resolved: Task 5 Step 6. + 11. Minor — the README's `## Rules` enumeration of the conventions rule went + stale. Resolved: extended in Task 5 Step 1. + +--- + +### Task 1: Commit the process documents + +**Files:** +- Commit (already written): `docs/specs/2026-08-10-project-memory-entry-format-design.md` +- Commit (already written): `docs/domain/adr/0002-own-entry-format.md` +- Commit (already modified): `docs/domain/glossary.md` +- Commit (already written): `docs/plans/2026-08-10-memory-entry-format.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: a clean baseline, so later task diffs contain only implementation. + +- [ ] **Step 1: Verify the working tree holds only these four as changes** + +Run: `git status --short` +Expected: `M docs/domain/glossary.md`, `?? docs/domain/adr/0002-own-entry-format.md`, `?? docs/specs/2026-08-10-project-memory-entry-format-design.md`, `?? docs/plans/2026-08-10-memory-entry-format.md` (order may vary). Anything else → stop and reconcile. + +- [ ] **Step 2: Commit** + +Two commits: the work's own documents, then the glossary the grilling session amended — a subject names everything it carries. + +```bash +git add docs/specs/2026-08-10-project-memory-entry-format-design.md docs/plans/2026-08-10-memory-entry-format.md docs/domain/adr/0002-own-entry-format.md +git commit -m "docs: spec, plan and ADR for the project-memory entry format" +git add docs/domain/glossary.md +git commit -m "docs: add the Auto-memory term and rewrite Home-dir memory" +``` + +--- + +### Task 2: Teach the conventions rule the entry format + +The whole format change lands in one rule file. The Entry shapes section gains a preamble (the `description` and the index projection) and a closing pair of paragraphs (field placement, tolerance). The existing note and idea bullets are untouched. + +**Files:** +- Modify: `plugins/project-memory/rules/project-memory-conventions.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: the `description` requirement, the index-projection rule with its drift authority, the top-level field placement, and the tolerance clause — all three later tasks apply them. + +- [ ] **Step 1: Insert the `description` and projection paragraphs** + +In `## Entry shapes`, directly after the paragraph beginning "Shape, not topic, is the criterion", insert: + +``` +Every entry opens with an H1 carrying its title and carries a `description:` +— one line, the summary that makes it findable. Its `INDEX.md` line is a +projection of the entry, both halves of it: the link text from the H1, the +text after the dash from `description`. + + - [H1 of the entry](file.md) — + +Quote the `description` scalar whenever it contains `: `, as index summaries +routinely do. Nothing parses entry frontmatter today, but an unquoted colon +makes the block invalid YAML the moment something does. + +On drift the entry wins — the file is the entry, the index a view of it — so +an index line is never authored directly, only re-projected: correct the H1 or +the `description` first, then re-derive the line from them. `INDEX.md` and +`ARCHIVE.md` are registry files, not entries: no frontmatter, no +`description`, no H1 requirement. +``` + +- [ ] **Step 2: Append the field-placement and tolerance paragraphs** + +At the end of `## Entry shapes`, after the **idea** bullet, add: + +``` +Every field this rule defines — `description`, `status`, `spec`, `ticket`, +`adr-candidate` — sits at the top level of the frontmatter, never nested +under a `metadata:` block. Nesting would break the anchored `^ticket:` sweep +the project's ticket convention publishes (when it keeps one), across the +whole project rather than only in the store. + +Entries may carry frontmatter that other tools wrote. Leave unknown keys +alone: never remove them, never rewrite them, and never let one change an +entry's shape — the `idea-` prefix stays authoritative. +``` + +- [ ] **Step 3: Verify the text landed and the frontmatter did not move** + +One grep per inserted passage — never a shared alternation. The word `description` alone is worthless as a check here: it recurs throughout the rule. + +```bash +rg -n 'opens with an H1' plugins/project-memory/rules/project-memory-conventions.md +rg -n 'only re-projected' plugins/project-memory/rules/project-memory-conventions.md +rg -n 'sits at the top level' plugins/project-memory/rules/project-memory-conventions.md +rg -n 'Leave unknown keys' plugins/project-memory/rules/project-memory-conventions.md +head -5 plugins/project-memory/rules/project-memory-conventions.md +``` + +Expected: each of the four greps returns exactly one line; the file still opens with the `paths:` block listing `docs/memory/**` and `.claude/memory/**`. + +- [ ] **Step 4: Verify no banned term and no unconditional cross-plugin claim** + +```bash +rg -in 'native memory' plugins/project-memory/rules/project-memory-conventions.md +rg -n 'ticket convention' plugins/project-memory/rules/project-memory-conventions.md +``` + +Expected: first command finds nothing; second shows the sweep sentence carrying its "(when it keeps one)" hedge. + +- [ ] **Step 5: Validate and commit** + +```bash +claude plugin validate . +claude plugin validate plugins/project-memory +git add plugins/project-memory/rules/project-memory-conventions.md +git commit -m "feat(project-memory): require a description and derive the index line from the entry" +``` + +--- + +### Task 3: Give memory-review-session the mechanical checks + +**Files:** +- Modify: `plugins/project-memory/skills/memory-review-session/SKILL.md` + +**Interfaces:** +- Consumes: Task 2's H1, `description`, projection and tolerance rules. +- Produces: three audit defect checks, a format-debt clause, a narrowed entry-walk step 4, and a skill description that matches them. + +- [ ] **Step 1: Add the format checks to the opening audit** + +In `## Opening audit (mechanical)`, after the "empty or stub body files" bullet and before the `idea-*` bullet, insert: + +``` +- index lines whose text after the dash differs from the entry's + `description` — the entry wins, so the fix is to confirm the entry and + re-project the line, never to edit the line alone; +- index lines whose link text differs from the entry's H1 — same authority, + same fix; +- frontmatter keys this plugin does not define: report them and move on, + never remove them; +``` + +- [ ] **Step 2: Add the format-debt clause under the audit list** + +Directly after the audit's bullet list, before `## Entry walk`, add: + +``` +Entries written before these fields existed carry **format debt, not +defects**: an entry with no `description`, or with no H1 to project a title +from, is incomplete, not broken. Count the debt and list it apart from the +defects above — a dangling link is a fault in the store, a missing +`description` is work the store has not had yet — and offer to fill it in +during the walk. +``` + +- [ ] **Step 3: Narrow entry-walk step 4** + +Replace step 4 of `## Entry walk (ordered, existential first)`: + +``` +4. **Does the `INDEX.md` line summarize it well?** Recall depends on the + one-liner — the cheapest tidiness there is. +``` + +with: + +``` +4. **Is the `description` right?** Recall depends on it. The `INDEX.md` line + is only its projection, so judge the `description` itself and let the line + follow. +``` + +- [ ] **Step 4: Update the skill's own `description:` frontmatter** + +Line 3 still advertises the behavior this task narrows ("sharpening index lines"), which is now wrong: the session sharpens the entry, and the line follows. In `description:`, replace + +``` +splitting or merging, sharpening index lines. +``` + +with + +``` +splitting or merging, sharpening entry descriptions. +``` + +Change nothing else on that line. The quoted trigger phrases — `"groom the store"`, `"memory review"`, `"przejrzyjmy memory"` — stay verbatim, and the sentence order stays as it is: `description:` is what makes the skill trigger, so every other word is load-bearing. Do NOT create or update any eval file. + +- [ ] **Step 5: Verify each edit separately and the untouched boundaries** + +One grep per edit — never a shared one. The bare word `description` is worthless as a check here: the skill's own frontmatter contains it, so it matches whether or not the work landed. + +```bash +rg -n 're-project the line' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'link text differs' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'never remove them' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'format debt, not' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'Is the .description. right' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'sharpening entry descriptions' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'line summarize it well|sharpening index lines' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'never bulk-cleans|Never commits' plugins/project-memory/skills/memory-review-session/SKILL.md +rg -n 'przejrzyjmy memory' plugins/project-memory/skills/memory-review-session/SKILL.md +``` + +Expected: greps one to six each return exactly one line; the seventh returns NOTHING (both old strings replaced, not duplicated); the eighth shows the "recommends per entry, never commits" boundaries intact; the ninth confirms the trigger phrase survived the frontmatter edit. + +- [ ] **Step 6: Validate and commit** + +```bash +claude plugin validate plugins/project-memory +git add plugins/project-memory/skills/memory-review-session/SKILL.md +git commit -m "feat(project-memory): audit description, index projection and foreign frontmatter keys" +``` + +--- + +### Task 4: Simplify migrate-memory and teach it the index cap + +Three edits: the scan gains a weak ordering hint, the move mechanics stop translating frontmatter, and the migration trace learns its error path. + +**Files:** +- Modify: `plugins/project-memory/skills/migrate-memory/SKILL.md` + +**Interfaces:** +- Consumes: Task 2's field placement and tolerance rules. +- Produces: nothing later tasks depend on. + +- [ ] **Step 1: Add the ordering hint to the scan** + +At the end of `## Scan`, after "When a fact's scope is unclear, ask; never guess.", add: + +``` +A `type:` of `user` or `feedback` — nested under `metadata:`, where the +harness writes it — hints that the fact may be personal rather than scoped to +this repository, so consider those entries last. The hint never decides: a +per-user fact ABOUT this repo belongs in Private memory, and content stays the +criterion. +``` + +- [ ] **Step 2: Replace the shape-translation bullet in Move mechanics** + +Replace: + +``` +- Translate shape: a home-dir entry (`name`/`description`/`metadata` + frontmatter) becomes a **note** or an `idea-.md` per the conventions + rule — shape is the criterion, the `idea-` prefix authoritative. +``` + +with: + +``` +- Carry the shape across: a home-dir entry becomes a **note** or an + `idea-.md` per the conventions rule — shape is the criterion, the + `idea-` prefix authoritative. `description` moves over unchanged. `name` + and `metadata` do not survive as fields, but `name` carries the entry's + only title, so it becomes the new entry's H1 and its slug the filename — + dropped as a field, kept as information. Add the plugin's own fields per + the conventions rule. +``` + +- [ ] **Step 3: Add the cap to the migration trace** + +At the end of `## Migration trace`, add: + +``` +The harness loads that index and caps what it reads (200 lines or 25KB), and +a write past the cap comes back with an error telling you to shorten it. Keep +the trace to one line; on that error, report it and offer to shorten the +index rather than retrying the write. +``` + +- [ ] **Step 4: Verify the edits and that the direction is unchanged** + +One grep per edit — never a shared one. + +```bash +rg -n 'The hint never decides' plugins/project-memory/skills/migrate-memory/SKILL.md +rg -n 'becomes the new entry.s H1' plugins/project-memory/skills/migrate-memory/SKILL.md +rg -n 'shorten the index' plugins/project-memory/skills/migrate-memory/SKILL.md +rg -n 'Translate shape' plugins/project-memory/skills/migrate-memory/SKILL.md +rg -n 'One direction only|Never commits' plugins/project-memory/skills/migrate-memory/SKILL.md +``` + +Expected: the first three each return exactly one line (Steps 1, 2 and 3 respectively); the fourth returns NOTHING (the old bullet replaced, not duplicated); the fifth shows the direction and commit boundaries intact. + +- [ ] **Step 5: Validate and commit** + +```bash +claude plugin validate plugins/project-memory +git add plugins/project-memory/skills/migrate-memory/SKILL.md +git commit -m "feat(project-memory): move entries without translating frontmatter, and respect the home-dir index cap" +``` + +--- + +### Task 5: README, dogfooding decision, and the final sweep + +**Files:** +- Modify: `plugins/project-memory/README.md` +- Modify (only if dogfooding): `plugins/project-memory/.claude-plugin/plugin.json` + +**Interfaces:** +- Consumes: Tasks 2-4. +- Produces: the shipped state of the branch. + +- [ ] **Step 1: Update the plugin README** + +Three edits in this file. First, in `## Store layout`, after "A closed entry keeps no body — only its one-line archive record.", add: + +``` +Every entry opens with an H1 and carries a one-line `description:` in its +frontmatter, and its index line is a projection of the two — link text from +the H1, summary from `description` — so index lines are re-derived, never +authored by hand. Frontmatter written by other tools is left alone. +``` + +Second, in `## Rules`, the conventions-rule bullet enumerates what that rule holds and is now short by four items. Replace + +``` + directories: note/idea entry shapes, team-memory scope, the gotcha↔ADR +``` + +with + +``` + directories: note/idea entry shapes, the required H1 and `description` and + the index line projected from them, top-level placement of the plugin's own + frontmatter fields, the tolerance clause for keys other tools wrote, + team-memory scope, the gotcha↔ADR +``` + +Third, at `README.md:52`, the memory-review-session bullet still promises "sharpen index lines". Replace that phrase with `sharpen entry descriptions`. + +- [ ] **Step 2: Decide dogfooding** + +The plugin's rules are installed on this machine, so trying the new conventions means shipping them through the Rules engine, which keys on the version string. + +- **Dogfooding (default):** set `"version": "0.4.0-dev.memory-entry-format"` in `plugins/project-memory/.claude-plugin/plugin.json`, then run the `working-process:sync-rules` skill and pick the project-memory payload. The release PR strips the suffix. + + The number must be the anticipated NEXT release (`0.4.0`; the spec sizes this as a minor), not the currently shipped `0.3.0`. A prerelease sorts BELOW its own release in semver, so `0.3.0-dev.…` against an installed manifest recording `0.3.0` would trip the `sync-rules` Direction gate as a spurious downgrade — a warning-and-ask on a user-level target, a hard stop on a project-level Tracked one. + + The anticipation cuts both ways, and dogfooding creates an obligation because of it: the release PR must mint project-memory at **`0.4.0` or higher**. Mint it lower and the dogfooded machine now holds a manifest that outranks the released plugin, and the same gate trips in reverse for whoever dogfooded — recoverable only by rewriting the manifest by hand. +- **Not dogfooding:** leave `plugin.json` untouched. The rules on disk keep the old text until release. + +Ask the developer which, and do only what they choose. Do not run `sync-rules` without being told to. + +- [ ] **Step 3: Marketplace-sync check** + +The marketplace-sync rule ties the plugin **name** across three files — `plugin.json`, the `marketplace.json` catalog entry and the root `README.md` row — while descriptions may legitimately differ: the catalog and README rows are allowed to shorten `plugin.json`'s wording, never to contradict it. They are already different strings today, so "identical descriptions" is the wrong thing to check. + +This plan changes no name and no description, so the correct expectation is that all three are untouched: + +```bash +git diff --name-only develop...HEAD -- .claude-plugin/marketplace.json README.md +git diff develop...HEAD -- plugins/project-memory/.claude-plugin/plugin.json +``` + +Expected: the first prints nothing. The second prints nothing if Step 2 chose not to dogfood, or exactly one changed line — `version` — if it did. + +- [ ] **Step 4: Whole-branch sweep** + +```bash +claude plugin validate . +claude plugin validate plugins/project-memory +rg -in 'native memory' plugins/ +git diff --name-only develop...HEAD -- plugins/working-process plugins/python-standards plugins/salesforce-standards +git diff --stat develop...HEAD +``` + +Expected: both validates pass; no banned term; the third command prints NOTHING, which is what actually proves the other three plugins were left alone (grepping them for `INDEX.md` would print the same pre-existing matches either way); the diffstat lists only files under `plugins/project-memory/` and `docs/`. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/project-memory/README.md +git commit -m "docs(project-memory): document the entry description and the derived index line" +``` + +If Step 2 chose dogfooding, commit the version separately: + +```bash +git add plugins/project-memory/.claude-plugin/plugin.json +git commit -m "chore(project-memory): dogfood the entry format on a dev version" +``` + +- [ ] **Step 6: Close the lifecycle on both documents** + +`status` moves forward only, and nothing so far has moved it. Set the plan's `status` to `implemented` and the spec's to `implemented` — both in one commit, at the very end, after Step 4's sweep passed. + +```bash +rg -n '^status:' docs/specs/2026-08-10-project-memory-entry-format-design.md docs/plans/2026-08-10-memory-entry-format.md +``` + +Edit both frontmatter blocks with the Edit tool, then: + +```bash +git add docs/specs/2026-08-10-project-memory-entry-format-design.md docs/plans/2026-08-10-memory-entry-format.md +git commit -m "docs: mark the entry-format spec and plan implemented" +``` + +Expected afterwards: `rg -l '^status: implemented' docs/` lists both files. + +--- + +## Notes for the implementer + +- Every deliverable here is prose that a model reads at runtime. Wording is the implementation: when an edit's exact text is given above, use it verbatim rather than paraphrasing. +- The spec's own reasoning for each edit lives in `docs/specs/2026-08-10-project-memory-entry-format-design.md`; ADR 0002 records why the plugin defines its own format instead of adopting the harness's. Read both before changing any wording this plan specifies. +- Existing entries in any store are NOT migrated by this plan. They gain `description` opportunistically, when `memory-review-session` next walks them. Do not write a migration pass. diff --git a/docs/plans/2026-08-11-store-probe-decoupling.md b/docs/plans/2026-08-11-store-probe-decoupling.md new file mode 100644 index 0000000..c649274 --- /dev/null +++ b/docs/plans/2026-08-11-store-probe-decoupling.md @@ -0,0 +1,393 @@ +--- +ticket: none +date: 2026-08-11 +status: implemented +adversary: blocking (resolved 2026-08-11) +branch: feature/store-probe-decoupling +base: develop +--- + +# Store-probe decoupling Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove every mention of project-memory's index filename from the +three foreign plugins — store probes test the part directories, registry-file +enumerations delegate to the conventions rule. + +**Architecture:** Five wording edits across three plugins, nothing else. No +executable code — every deliverable is Markdown, so "tests" are anchored +greps proving the intended text landed (positive) and the index filename did +not survive (negative), plus a `git diff --name-only` proof that +project-memory stayed untouched. + +**Tech Stack:** Claude Code Rules payloads and skills (Markdown), `rg`, +`claude` CLI. + +**Spec:** `docs/specs/2026-08-11-store-probe-decoupling-design.md` +(grilled 2026-08-11; architect round skipped on the developer's call — +small mechanical change, twice consulted during the predecessor spec). + +## Global Constraints + +- Public repo: no machine-specific paths (`/home//…`), no company or + client names, all committed text English. +- Commit messages: ONE line, conventional-commit subject + (`type:`/`type(scope):`), no body, no trailers (no `Co-Authored-By`). +- **No version bump on this branch** — no dogfooding, so no `-dev.` suffix; + the release PR from `develop` to `master` mints the bumps (patch for each + of the three plugins, unless larger changes ride the same release). +- **Do not touch `plugins/project-memory/`** — no file of that plugin + changes; the index filename stays `INDEX.md`. +- Glossary binds: the check is a **store probe**, never a bare "probe" + (`_Avoid_: probe (unqualified)`); the probe tests the part's directory, + never a file inside it. +- The two standards restatements may not drift from each other or from the + review-reports contract: Task 2's two edits use ONE identical old/new + text pair. +- Edit artifact and rule files with the Edit tool, never `sed -i`. +- Do NOT create `evals/trigger-evals.json` or any eval artifact. +- Verification greps run against the TARGET files, never against this plan + (the plan's own line-wrapping differs from the files'). + +## Review rounds + +- **Plan-adversary**, 2026-08-11, **Opus 5** (developer's explicit choice, + above the Sonnet tier the heuristic prescribes for a small mechanical + plan — no fallback record applies). Verdict **blocking**: 3 Important, + 8 Minor. The round reproduced all five edits in a scratch tree and + sabotaged the checks; every Old block matched byte-for-byte and every + line citation held. Findings, all against the verification layer: + 1. (Important) Task 2's three checks stay green when the banned bare + "probe" replaces "store probe" — the new wrapping splits the term + across a line break, so no grep gates it. + 2. (Important) The Task 4 untouched-proof (`git diff develop...`) cannot + see an uncommitted stray edit to `plugins/project-memory/`; `|| true` + also swallows git errors. + 3. (Important) The lifecycle close flips `status:` without recording + this round's verdict and findings' disposition, which + spec-plan-lifecycle mandates for a non-LGTM verdict. + 4. (Minor) "Verified state" says five lines; the sweep returns six + lines across five edit sites. + 5. (Minor) Post-edit, `rg 'store probe'` finds one surface, not the + glossary's three — same wrap as finding 1. + 6. (Minor) Task 3 silently widens the `ticket` exemption to include + `ARCHIVE.md` (correct — working-process's list was stale since + project-memory 0.2.0 — but a semantic change, to be stated). + 7. (Minor) "both stores present" / "both-stores" survive next to the + new "per part" wording; the glossary says *part*. + 8. (Minor) Validation runs after the last commit and omits the + per-plugin form the plugin-authoring rule mandates before each. + 9. (Minor) Restatement-vs-contract consistency is asserted, never + checked — Task 2 only compares the two skills to each other. + 10. (Minor) The master negative check prints CLEAN when rg errors + (e.g. wrong working directory). + 11. (Minor) draft → implemented skips `approved` with no note that the + skip is deliberate. + + **Disposition (2026-08-11): all 11 accepted and folded into the tasks.** + Findings 1+5+7: Task 1 now edits the whole bullet ("both parts", one + edit) and Task 2's new text keeps "store probe" unbroken; both tasks + grep for the term. Finding 2: Task 4 adds `git status --porcelain` and + drops `|| true`. Finding 3: this section plus the frontmatter + annotation, confirmed by Task 4 before the flip. Finding 4: "six lines + across five edit sites". Finding 6: Task 3 states the `ARCHIVE.md` + widening. Finding 8: Tasks 1-3 validate (both forms) before each + commit. Finding 9: Task 4 Step 3 anchors all three surfaces. Finding + 10: Task 4 Step 1 pins the working directory and pairs a positive + control. Finding 11: Task 4 Step 4 records the deliberate skip. + +## Verified state (2026-08-11, branch `feature/store-probe-decoupling`) + +`rg -n 'INDEX\.md' plugins/ --glob '!plugins/project-memory/**'` returns +exactly six lines across five edit sites (the review-reports probe wraps +onto two lines) — three probe surfaces plus two enumerations. A repo-wide +sweep excluding project-memory, specs, plans, and Private memory finds no +sixth surface; the two review commands delegate ("candidate-gap offers +verbatim") rather than restate. + +--- + +### Task 1: Retarget the owning store probe (review-reports contract) + +**Files:** +- Modify: `plugins/working-process/rules/review-reports.md:197-204` + +**Interfaces:** +- Consumes: nothing. +- Produces: the contract wording the standards restatements (Task 2) mirror + — "store probe: the directories, never a file inside them; both parts + present means ask". + +- [ ] **Step 1: Apply the edit** + +One edit covering the whole Candidate-gap park bullet. It retargets the +probe AND fixes the adjacent terminology drift the glossary flags: the +store's halves are *parts* ("both stores" predates the term). Old (exact, +the full bullet): + +``` +- **Project-memory park** — only when the reviewed project keeps a + Project-memory store (probe `docs/memory/INDEX.md` and + `.claude/memory/INDEX.md`). The write is done by whoever accepts, + never by the review run. Store selection: explicit guidance wins + (project CLAUDE.md, the developer's own instructions, the store's + conventions); otherwise with both stores present the offer asks the + developer which one; with one store it names that one. No store — no + offer, and never an offer to create a store. +``` + +New (keep "store probe" AND "never a file inside it" unbroken on their +lines — the drift sweeps and Task 4's cross-surface anchor grep for both): + +``` +- **Project-memory park** — only when the reviewed project keeps a + Project-memory store (store probe: one existence test per part, + on `docs/memory/` and `.claude/memory/` — the directory, + never a file inside it). The write is done by whoever accepts, + never by the review run. Store selection: explicit guidance wins + (project CLAUDE.md, the developer's own instructions, the store's + conventions); otherwise with both parts present the offer asks the + developer which one; with one part it names that one. No store — no + offer, and never an offer to create a store. +``` + +- [ ] **Step 2: Verify** + +Run: `rg -c 'store probe' plugins/working-process/rules/review-reports.md` +Expected: `1` + +Run: `rg -c 'both parts present' plugins/working-process/rules/review-reports.md` +Expected: `1` + +Run: `rg -c 'INDEX\.md|both stores' plugins/working-process/rules/review-reports.md || echo ABSENT` +Expected: `ABSENT` (no INDEX.md left anywhere in the file — the probe was +its only mention — and no "both stores" either) + +- [ ] **Step 3: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/working-process` +Expected: both pass (the plugin-authoring rule requires both forms before +every commit touching a plugin). + +```bash +git add plugins/working-process/rules/review-reports.md +git commit -m "fix(working-process): point the store probe at the part directories" +``` + +--- + +### Task 2: Retarget both standards restatements (one text pair, two files) + +**Files:** +- Modify: `plugins/python-standards/skills/python-code-review/SKILL.md:75-79` +- Modify: `plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md:108-112` + +**Interfaces:** +- Consumes: the Task 1 contract wording — this restatement condenses it and + may not drift from it. +- Produces: nothing later tasks use. + +- [ ] **Step 1: Apply the SAME edit to both files** + +Both files carry this exact step-5 text (verbatim-identical in the two +skills; python at lines 75-79, salesforce at 108-112). Old: + +``` + then follow the review-reports contract's Candidate-gap offers + section: offer a Project-memory park when a store exists (probe + `docs/memory/INDEX.md` and `.claude/memory/INDEX.md`; explicit + guidance on store choice wins, both-stores means ask, never offer to + create one) and always offer a generalized upstream report (target +``` + +New (in both files, character-for-character the same; "store probe" +unbroken on its line, "both-stores" becomes the glossary's "both parts"): + +``` + then follow the review-reports contract's Candidate-gap offers + section: offer a Project-memory park when a store exists + (store probe: the `docs/memory/` and `.claude/memory/` directories, + never a file inside them; explicit guidance on store choice wins, + both parts means ask, never offer to create one) and always offer + a generalized upstream report (target +``` + +- [ ] **Step 2: Verify both files** + +Run: `rg -c 'store probe' plugins/python-standards/skills/python-code-review/SKILL.md plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md` +Expected: each file reports `1` + +Run: `rg -c 'INDEX\.md|both-stores' plugins/python-standards/skills/python-code-review/SKILL.md plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md || echo ABSENT` +Expected: `ABSENT` for both (the probe was each file's only INDEX.md +mention) + +Run (drift check — the two step-5 paragraphs stay identical): + +```bash +diff <(rg -A5 'offer a Project-memory park' plugins/python-standards/skills/python-code-review/SKILL.md) \ + <(rg -A5 'offer a Project-memory park' plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md) +``` + +Expected: no output (identical). + +- [ ] **Step 3: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/python-standards && claude plugin validate plugins/salesforce-standards` +Expected: all pass. + +```bash +git add plugins/python-standards/skills/python-code-review/SKILL.md plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md +git commit -m "fix: point both standards store-probe restatements at the part directories" +``` + +--- + +### Task 3: Delegate the two registry-file enumerations (working-process) + +**Files:** +- Modify: `plugins/working-process/rules/process-artifacts.md:63-67` +- Modify: `plugins/working-process/rules/ticket-frontmatter.md:22-26` + +**Interfaces:** +- Consumes: nothing from earlier tasks (independent of the probe edits). +- Produces: nothing later tasks use. + +Note one deliberate semantic widening: the old enumerations name only +`INDEX.md`, a list stale since project-memory 0.2.0 added `ARCHIVE.md` as +a second registry file. Delegation makes the exemption follow the owning +rule's list (`INDEX.md` and `ARCHIVE.md` today), which is the point of +the change, not an accident. + +- [ ] **Step 1: Edit `process-artifacts.md`** + +Old (exact): + +``` +- Per-work artifacts (review reports, ADRs, task briefs, progress + ledgers) carry a `ticket` frontmatter field; registry files that live + across tickets (the domain glossary, `.gitignore` files, and — when the + project-memory plugin's rules are installed — Project memory notes and + `INDEX.md`) are exempt. Project-memory idea entries DO carry `ticket` +``` + +New: + +``` +- Per-work artifacts (review reports, ADRs, task briefs, progress + ledgers) carry a `ticket` frontmatter field; registry files that live + across tickets (the domain glossary, `.gitignore` files, and — when the + project-memory plugin's rules are installed — Project memory notes and + the store's registry files, whose list that plugin's conventions rule + owns) are exempt. Project-memory idea entries DO carry `ticket` +``` + +- [ ] **Step 2: Edit `ticket-frontmatter.md`** + +Old (exact): + +``` +- Project memory (`docs/memory/`, `.claude/memory/` — when the + project-memory plugin's rules are installed): notes and `INDEX.md` + are `ticket`-exempt registry files (like the glossary); idea entries + (`idea-*.md`) carry `ticket`. See that plugin's + project-memory-conventions rule. +``` + +New: + +``` +- Project memory (`docs/memory/`, `.claude/memory/` — when the + project-memory plugin's rules are installed): notes and the store's + registry files are `ticket`-exempt (like the glossary; the + registry-file list belongs to that plugin's conventions rule); idea + entries (`idea-*.md`) carry `ticket`. See that plugin's + project-memory-conventions rule. +``` + +- [ ] **Step 3: Verify** + +Run: `rg -c "whose list that plugin's conventions rule" plugins/working-process/rules/process-artifacts.md` +Expected: `1` + +Run: `rg -c 'registry-file list belongs to' plugins/working-process/rules/ticket-frontmatter.md` +Expected: `1` + +Run: `rg -c 'INDEX\.md' plugins/working-process/rules/process-artifacts.md plugins/working-process/rules/ticket-frontmatter.md || echo ABSENT` +Expected: `ABSENT` for both files. + +- [ ] **Step 4: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/working-process` +Expected: both pass. + +```bash +git add plugins/working-process/rules/process-artifacts.md plugins/working-process/rules/ticket-frontmatter.md +git commit -m "fix(working-process): delegate the registry-file list to the project-memory conventions rule" +``` + +--- + +### Task 4: Whole-branch verification and lifecycle close + +**Files:** +- Modify: `docs/specs/2026-08-11-store-probe-decoupling-design.md` (frontmatter `status:`) +- Modify: `docs/plans/2026-08-11-store-probe-decoupling.md` (frontmatter `status:`) + +**Interfaces:** +- Consumes: all Task 1-3 edits committed. +- Produces: the branch ready for merge to `develop`. + +- [ ] **Step 1: Master check — the filename is gone, the term is on all three surfaces** + +```bash +cd "$(git rev-parse --show-toplevel)" +rg -l 'store probe' plugins/ | wc -l +rg -n 'INDEX\.md' plugins/ --glob '!plugins/project-memory/**' || echo CLEAN +``` + +Expected: `3` (the three probe surfaces — a positive control proving rg +ran against the right tree, and the glossary's "three surfaces" claim made +greppable), then `CLEAN`. A count other than 3, or any line before CLEAN, +fails the task. + +- [ ] **Step 2: Untouched-proof — project-memory saw no change on this branch** + +Run: `git status --porcelain -- plugins/project-memory/` +Expected: no output (catches uncommitted strays the range diff below +cannot see). + +Run: `git diff --name-only develop... -- plugins/project-memory/` +Expected: no output. No `|| true` — an erroring git call must fail loudly, +not read as success. + +Run: `git diff --name-only develop... -- 'plugins/*/.claude-plugin/plugin.json'` +Expected: no output (no version string moved on this branch). + +- [ ] **Step 3: Cross-surface consistency — restatements against the contract** + +Run: `rg -c 'never a file inside' plugins/working-process/rules/review-reports.md plugins/python-standards/skills/python-code-review/SKILL.md plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md` +Expected: `1` from each of the three files — the contract and both +restatements carry the same directory-not-file anchor. (Full semantic +consistency with the contract stays a human read; this anchors the part +a grep can hold.) + +- [ ] **Step 4: Flip lifecycle status** + +The adversary round (blocking, Opus 5, 2026-08-11) is recorded in the +Review rounds section with every finding's disposition, and the +frontmatter carries the resolution annotation — confirm both before the +flip. With the developer's confirmation, set `status: implemented` in the +spec's and this plan's frontmatter (Edit tool). The move from `draft` is +deliberate: the developer skipped the architect round and the `approved` +stage for this small mechanical change — recorded here so the spec's +missing approval reads as a decision, not an omission. Then commit both +documents together with any remaining `docs/` changes of this work +(glossary term, spec, plan): + +```bash +git add docs/specs/2026-08-11-store-probe-decoupling-design.md docs/plans/2026-08-11-store-probe-decoupling.md docs/domain/glossary.md +git commit -m "docs: spec, plan and glossary for the store-probe decoupling" +``` + +(Commit only with the developer's authorization, as always.) diff --git a/docs/plans/2026-08-12-memory-hybrid.md b/docs/plans/2026-08-12-memory-hybrid.md new file mode 100644 index 0000000..215c0fb --- /dev/null +++ b/docs/plans/2026-08-12-memory-hybrid.md @@ -0,0 +1,961 @@ +--- +ticket: none +date: 2026-08-12 +status: implemented +adversary: blocking (resolved 2026-08-12) +branch: feature/memory-hybrid +base: develop +--- + +# Hybrid Private memory Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rename every store index to `MEMORY.md`, teach the core rule to +deduplicate loading and watch for redirect divergence, ship the +`redirect-memory` opt-in skill, and record the ADR 0002 confrontation as +ADR 0003. + +**Architecture:** One plugin (`project-memory`) plus two repo artifacts +(ADR, glossary). Every deliverable is Markdown; "tests" are anchored greps +(positive and negative), `claude plugin validate` in both forms before each +commit, and hand review of skill/rule frontmatter. The rename is mechanical +(`replace_all` per file) followed by exact-text content edits per file. + +**Tech Stack:** Claude Code Rules payloads and skills (Markdown with YAML +frontmatter), `rg`, `claude` CLI. + +**Spec:** `docs/specs/2026-08-12-memory-hybrid-design.md` (grilled +2026-08-12; architect round 1 blocking — resolved; round 2 fresh, concerns +— resolved). + +## Global Constraints + +- Public repo: no machine-specific paths (`/home//…`), no company or + client names, all committed text English. One narrow exception: quoted + non-English trigger phrases inside a skill `description:`. +- Commit messages: ONE line, conventional-commit subject + (`type:`/`type(scope):`), no body, no trailers (no `Co-Authored-By`). +- `claude plugin validate .` AND `claude plugin validate + plugins/project-memory` pass before every commit touching the plugin. + `validate` does not check `rules/` or skill body text — review YAML by + hand; quote any scalar containing `: `. +- Glossary binds: **Auto-memory** (never "native memory"), **Hybrid + store** (never "hybrid memory" / "hybrid mode"), **Environment**, + **Home-dir memory**, **Store probe** (never bare "probe"). "Machine" is + avoided where the path namespace is what matters. +- After all tasks, `INDEX.md` survives in `plugins/project-memory/` in + EXACTLY TWO lines: the core rule's pre-rename detection and the + conventions rule's old-name rename offer. Zero mentions anywhere else + in the plugin (the bare word `INDEX` only additionally in the + pre-existing, untouched eval query file). +- Keep greppable phrases unbroken on one line where a task says so — + verification and future drift sweeps grep for them. +- Do NOT create `evals/trigger-evals.json` or any eval artifact. +- Do NOT touch other plugins; the store-probe decoupling already removed + every foreign coupling. +- Version: Task 8 (developer-gated) sets `0.4.0-dev.memory-hybrid`; no + other task touches `plugins/project-memory/.claude-plugin/plugin.json` + (currently `0.4.0-dev.memory-entry-format`). +- Verification greps run against the TARGET files, never against this + plan. + +## Review rounds + +- **Plan-adversary**, 2026-08-12, **Opus 5** (developer's explicit + choice). Verdict **blocking**: 1 Critical, 4 Important, 10 Minor. The + round reproduced every Old block, replace_all count, and grep + expectation (all held); the findings target design gaps the greps + cannot see: + 1. (Critical) Silent store blackout: the core rule looks only for + `MEMORY.md`, while the rename offer lives in the paths-gated + conventions rule that loads only when store files are touched — a + pre-rename store stops loading and the Adoption clause can create a + second index beside the orphan. Pre-rename detection must live in + the core rule's Loading clause. + 2. (Important) The plugin description enumerates its skills in + plugin.json, marketplace.json, and the root README table; shipping + redirect-memory makes all three stale, and marketplace-sync demands + one commit touching all three. + 3. (Important) The divergence check is unconditional in an always-on + rule — it fires in projects with no store at all, against the + rule's never-nags stance. Gate on `.claude/memory/` existing. + 4. (Important) The cap-error deferral points at a clause framed as + Hybrid-only and paths-scoped away from the Home-dir index that + migrate-memory actually writes. Word the budget unconditionally and + keep the actionable recovery in the skill. + 5. (Important) The "enable hybrid memory" trigger instantiates the + glossary ban (repo-hygiene's trigger exception covers language, + never bans); the Task 9 sweep was shaped to miss it. + 6-15. (Minor) Bare `INDEX` word survives in two files unseen by the + `INDEX\.md` greps; `an MEMORY.md` article break in README; Task 4's + partial-line Old block; a by-eye check of a frontmatter block the + core rule does not have; the glossary-commit contradiction between + Tasks 7 and 9; Status reads only settings.local.json though other + settings files are honored; "Edit tool… create the file" (Edit + cannot create — Write when absent); the parked idea this spec + graduates from is not closed at dogfood; only one payload re-synced + (installed working-process copy is pre-decoupling); plan status + should pass through `approved` at the adversary stamp. + + **Disposition (2026-08-12): all 15 accepted and applied.** Critical: + the core rule's Loading clause gained the pre-rename detection (reads + the old-named index meanwhile, Adoption keeps its hands off) — the + plugin's surviving `INDEX.md` count is now two and every affected grep + expectation was updated. Important 2: Task 6 grew the three identity + surfaces with exact texts, one commit. Important 3: the divergence + check is gated on `.claude/memory/` existing. Important 4: the budget + clause worded unconditionally in the conventions rule; migrate-memory + keeps the numbers and the recovery inline. Important 5: the trigger + phrase is "enable the hybrid store" (canonical term) and the ban sweep + now covers "hybrid memory". Minors: bare-INDEX edits in Tasks 1-2 with + a whitelisted three-line sweep in Task 9; the README article fix; the + full-line Old block in Task 4; the frontmatter by-eye check reworded + (the core rule has none); the glossary-commit contradiction removed + (Task 7 owns it); Status checks every honored settings file; Edit + vs Write on a fresh settings file; Task 8 closes the graduated idea + and syncs both payloads; plan status set to `approved` at this stamp. + +## Verified state (2026-08-12, branch `feature/memory-hybrid`) + +`rg -c 'INDEX\.md' plugins/project-memory/ -g '*'`: README.md 6, +project-memory-conventions.md 7, project-memory.md 4, +memory-review-session/SKILL.md 5, migrate-memory/SKILL.md 1 — 23 total. +The working tree already carries uncommitted, deliberate edits to +`docs/domain/glossary.md` (grilling outcomes) — they ride Task 7's commit, +which stages the whole file. The spec and this plan are untracked until +Task 9's commit. No other plugin mentions the filename (decoupling shipped +in `develop`). + +--- + +### Task 1: Core rule — rename, loading dedup, divergence watch + +**Files:** +- Modify: `plugins/project-memory/rules/project-memory.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: the loading-section wording Tasks 2-6 stay consistent with — + "unless the session context already carries that index" (dedup) and the + divergence message contract ("say so plainly"). + +- [ ] **Step 1: Mechanical rename** + +Edit tool, `replace_all: true`, old `INDEX.md` → new `MEMORY.md` (4 +occurrences). + +- [ ] **Step 2: Replace the Loading section** + +Old (exact, post-rename): + +``` +## Loading + +When a part's `MEMORY.md` exists, read it at session start and pull a topic +file only when its index line is relevant. If neither `MEMORY.md` exists this +rule is a no-op — it never scans, creates, or nags. +``` + +New: + +``` +## Loading + +When a part's `MEMORY.md` exists, read it at session start and pull a topic +file only when its index line is relevant — unless the session context +already carries that index: in a Hybrid store, Auto-memory loads Private +memory's `MEMORY.md` itself, and reading it twice buys nothing. If neither +`MEMORY.md` exists this rule is a no-op — it never scans, creates, or nags. + +One exception to the no-op: a part directory holding an `INDEX.md` and no +`MEMORY.md` is a store from before the rename, never "no store". Say so at +session start, point at the conventions rule's rename offer, and read the +old-named index as the part's index meanwhile — the Adoption clause must +never create a second index beside it. + +When Private memory exists (`.claude/memory/` present) and +`.claude/settings.local.json` carries `autoMemoryDirectory`, compare its +value with the realpath of `/.claude/memory`. On a mismatch, say +so plainly: the redirect points elsewhere in this session's filesystem +namespace, so Auto-memory is writing a stray store or none at all — the +platform gives no other visible signal. Never edit settings from this rule; +that is the redirect-memory skill's job, when it is available. +``` + +- [ ] **Step 2b: Fix the bare word left by the rename** + +Old (exact, post-rename): `project-memory-conventions rule for the INDEX +sections and entry shapes (no` → New: `project-memory-conventions rule for +the index sections and entry shapes (no` (the generic word — no filename +survives here). + +- [ ] **Step 3: Verify** + +Run: `rg -c 'MEMORY\.md' plugins/project-memory/rules/project-memory.md` +Expected: `6` (4 renamed, minus the replaced Loading section's 2, plus the +new Loading text's 4) + +Run: `rg -n 'INDEX\.md' plugins/project-memory/rules/project-memory.md` +Expected: exactly ONE line — the pre-rename detection ("holding an +`INDEX.md` and no `MEMORY.md`"). Zero or more than one fails. + +Run: `rg -n '\bINDEX\b' plugins/project-memory/rules/project-memory.md` +Expected: the same single line (the bare word survives nowhere else — +Step 2b removed it). + +Run: `rg -c 'Auto-memory is writing a stray store' plugins/project-memory/rules/project-memory.md` +Expected: `1` (the divergence message contract, unbroken on one line) + +- [ ] **Step 4: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass. Confirm the file still opens directly with +`# Project memory` and carries no frontmatter block — always-on by +design. + +```bash +git add plugins/project-memory/rules/project-memory.md +git commit -m "feat(project-memory): rename the index to MEMORY.md and watch the redirect in the core rule" +``` + +--- + +### Task 2: Conventions rule — rename, budget, old-name offer, second writer + +**Files:** +- Modify: `plugins/project-memory/rules/project-memory-conventions.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: the old-name offer wording (one of the plugin's exactly TWO + surviving `INDEX.md` mentions, beside the core rule's detection — + Task 9's master grep counts on both) and the section-blind sentence + Task 3 mirrors. + +- [ ] **Step 1: Mechanical rename** + +Edit tool, `replace_all: true`, old `INDEX.md` → new `MEMORY.md` (7 +occurrences). + +- [ ] **Step 2: Add the budget clause** + +Old (exact, post-rename — the paragraph closing the Locations section): + +``` +they close (see Lifecycle). `MEMORY.md` is the only file read at session start, +so it never carries history. +``` + +New: + +``` +they close (see Lifecycle). `MEMORY.md` is the only file read at session +start, so it never carries history. The harness caps any index it loads — +200 lines / 25 KB; in a Hybrid store that is this file. Keep lines thin, +and when a write bounces off the cap, shorten the index rather than +retrying. +``` + +Then fix the bare word the rename leaves two paragraphs below, in Entry +shapes. Old (exact): `the INDEX section and frontmatter follow it` → New: +`the index section and frontmatter follow it`. + +- [ ] **Step 3: Add the old-name offer (new subsection, directly after the paragraph edited in Step 2)** + +Insert: + +``` +### Stores that predate the rename + +A store whose live index is still named `INDEX.md` predates this +convention. Offer once to rename the file to `MEMORY.md` (`git mv` in a +tracked part, a plain rename in an ignored one) — content untouched, and +nothing to re-point: no plugin outside project-memory names the index +file. Declined, the offer rests for the session; the core rule keeps +reading the old-named index meanwhile, so nothing goes dark. +``` + +- [ ] **Step 4: Add the second-writer sentence to Entry shapes** + +Old (exact — the tolerance-clause paragraph): + +``` +Entries may carry frontmatter that other tools wrote. Leave unknown keys +alone: never remove them, never rewrite them, and never let one change an +entry's shape — the `idea-` prefix stays authoritative. +``` + +New: + +``` +Entries may carry frontmatter that other tools wrote. Leave unknown keys +alone: never remove them, never rewrite them, and never let one change an +entry's shape — the `idea-` prefix stays authoritative. In a Hybrid store +Auto-memory is a second writer: an entry it writes is an ordinary note +whose missing H1 is format debt, and an index line it appends lands +section-blind — re-sectioning belongs to the grooming walk +(memory-review-session, when available), never to a routine write. +``` + +- [ ] **Step 5: Verify** + +Run: `rg -c 'INDEX\.md' plugins/project-memory/rules/project-memory-conventions.md` +Expected: `1` (only the old-name offer) + +Run: `rg -c 'shorten the index rather than retrying' plugins/project-memory/rules/project-memory-conventions.md` +Expected: `1` + +Run: `rg -c 'section-blind' plugins/project-memory/rules/project-memory-conventions.md` +Expected: `1` + +Run: `rg -c 'Stores that predate the rename' plugins/project-memory/rules/project-memory-conventions.md` +Expected: `1` + +Run: `rg -c '\bINDEX\b' plugins/project-memory/rules/project-memory-conventions.md` +Expected: `1` (only the old-name offer's `INDEX.md`; the bare-word site is +gone) + +- [ ] **Step 6: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass. The `paths:` frontmatter block stays unchanged — +confirm by eye. + +```bash +git add plugins/project-memory/rules/project-memory-conventions.md +git commit -m "feat(project-memory): MEMORY.md conventions - budget, old-name offer, second writer" +``` + +--- + +### Task 3: memory-review-session — rename, re-sectioning, Auto-memory debt + +**Files:** +- Modify: `plugins/project-memory/skills/memory-review-session/SKILL.md` + +**Interfaces:** +- Consumes: Task 2's section-blind sentence (this skill implements the + "grooming walk" duty it names). +- Produces: nothing later tasks use. + +- [ ] **Step 1: Mechanical rename** + +Edit tool, `replace_all: true`, old `INDEX.md` → new `MEMORY.md` (5 +occurrences, all in the body). Then one separate edit in the frontmatter +`description:` — it names the files without extensions: old +`audits INDEX/ARCHIVE consistency` → new `audits MEMORY/ARCHIVE +consistency`; confirm the description's quoting survives intact. + +- [ ] **Step 2: Add the wrong-section audit bullet** + +Old (exact — the sweep bullet, post-rename): + +``` +- **sweep**: any closed line still in `MEMORY.md` (legacy, or a botched close) + moves to `ARCHIVE.md`. Under the invariant this should be empty; the sweep + is the safety net. +``` + +New: + +``` +- index lines sitting in the wrong section (a section-blind writer — + Auto-memory in a Hybrid store — appended them): move the line to its + part's proper section; the entry body is untouched; +- **sweep**: any closed line still in `MEMORY.md` (legacy, or a botched close) + moves to `ARCHIVE.md`. Under the invariant this should be empty; the sweep + is the safety net. +``` + +- [ ] **Step 3: Extend the format-debt sentence** + +Old (exact): + +``` +Entries written before these fields existed carry **format debt, not +defects**: an entry with no `description`, or with no H1 to project a title +from, is incomplete, not broken. +``` + +New: + +``` +Entries written before these fields existed — or written by Auto-memory, +which knows nothing of them — carry **format debt, not defects**: an entry +with no `description`, or with no H1 to project a title from, is +incomplete, not broken. +``` + +- [ ] **Step 4: Verify** + +Run: `rg -c 'INDEX\.md' plugins/project-memory/skills/memory-review-session/SKILL.md || echo ABSENT` +Expected: `ABSENT` + +Run: `rg -c 'section-blind writer' plugins/project-memory/skills/memory-review-session/SKILL.md` +Expected: `1` + +Run: `rg -c 'or written by Auto-memory' plugins/project-memory/skills/memory-review-session/SKILL.md` +Expected: `1` + +- [ ] **Step 5: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass (validate checks skill frontmatter — the renamed +description must still parse). + +```bash +git add plugins/project-memory/skills/memory-review-session/SKILL.md +git commit -m "feat(project-memory): review-session audits MEMORY.md and re-sections harness-appended lines" +``` + +--- + +### Task 4: migrate-memory — path-qualified stores, hybrid no-op + +**Files:** +- Modify: `plugins/project-memory/skills/migrate-memory/SKILL.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: nothing later tasks use. + +- [ ] **Step 1: Qualify the scan source** + +Old (exact — the full opening of the Scan paragraph, so the sentence that +follows survives intact): + +``` +Read home-dir memory (`MEMORY.md` plus its entry files). Candidates are facts +``` + +New (the rename makes the bare filename ambiguous — every store name is +now qualified by path; re-wrap the paragraph after the edit): + +``` +Read home-dir memory — the Home-dir store's `MEMORY.md` and entry files, +under `~/.claude/projects//memory/` by default. Candidates are facts +``` + +- [ ] **Step 2: Add the hybrid no-op check (new paragraph directly under the `## Scan` heading, before the paragraph edited in Step 1)** + +Insert: + +``` +First check for identity: when the Auto-memory redirect is active in this +environment (`autoMemoryDirectory` in `.claude/settings.local.json` equals +the realpath of `/.claude/memory`), home-dir → Private is a no-op +— the two directories are one. Say so and skip the Private-memory +candidates; Team-memory moves are unaffected. Each environment keeps its +own Home-dir store, so run the migration where the notes live. +``` + +- [ ] **Step 3: Qualify the projection and deletion lines** + +Old (exact): + +``` +- Project the entry's line into the target part's `INDEX.md` — link text from + its H1, summary from its `description`, per the conventions rule. +- Delete the home-dir body file and its `MEMORY.md` line. +``` + +New: + +``` +- Project the entry's line into the target part's `MEMORY.md` — link text + from its H1, summary from its `description`, per the conventions rule. +- Delete the home-dir body file and its line in the Home-dir store's + `MEMORY.md`. +``` + +- [ ] **Step 4: Qualify the trace target** + +Old (exact): + +``` +After moving one or more facts, leave a single roll-up line in home-dir +`MEMORY.md` — "project-scoped notes for migrated to its Project +memory, " — not a per-entry pointer. +``` + +New: + +``` +After moving one or more facts, leave a single roll-up line in the Home-dir +store's `MEMORY.md` — "project-scoped notes for migrated to its +Project memory, " — not a per-entry pointer. +``` + +- [ ] **Step 5: Defer the cap error to the conventions rule** + +The general "shorten, don't retry" convention graduates to the +conventions rule (Task 2); this skill defers instead of restating. Old +(exact — the trace section's closing paragraph): + +``` +The harness loads that index and caps what it reads (200 lines or 25KB), +and a write past the cap comes back with an error telling you to shorten +it. Keep the trace to one line; on that error, report it and offer to +shorten the index rather than retrying the write. +``` + +New (the numbers and the recovery stay here — this skill is the only +surface loaded when the Home-dir trace is written; the conventions rule +owns the canonical statement): + +``` +The harness caps what it loads from that index — 200 lines / 25 KB, the +budget convention the conventions rule owns. Keep the trace to one line; +on a cap error shorten the index, never retry the write. +``` + +- [ ] **Step 6: Verify** + +Run: `rg -c 'INDEX\.md' plugins/project-memory/skills/migrate-memory/SKILL.md || echo ABSENT` +Expected: `ABSENT` + +Run: `rg -c 'no-op' plugins/project-memory/skills/migrate-memory/SKILL.md` +Expected: `1` + +Run: `rg -c "Home-dir store's" plugins/project-memory/skills/migrate-memory/SKILL.md` +Expected: `3` + +Run: `rg -c 'never retry the write' plugins/project-memory/skills/migrate-memory/SKILL.md` +Expected: `1` + +- [ ] **Step 7: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass. + +```bash +git add plugins/project-memory/skills/migrate-memory/SKILL.md +git commit -m "feat(project-memory): migrate-memory path-qualifies stores and detects the hybrid no-op" +``` + +--- + +### Task 5: redirect-memory — the new skill + +**Files:** +- Create: `plugins/project-memory/skills/redirect-memory/SKILL.md` + +**Interfaces:** +- Consumes: the core rule's divergence contract (Task 1) and + migrate-memory's no-op behavior (Task 4) — both referenced, neither + restated. +- Produces: the skill name `redirect-memory` Tasks 6-7 mention. + +- [ ] **Step 1: Write the skill (full content)** + +```markdown +--- +name: redirect-memory +description: "Enable, disable, or inspect the Auto-memory redirect that makes this project's Private memory a Hybrid store (autoMemoryDirectory in .claude/settings.local.json). Use ONLY when the developer explicitly asks to point Claude Code's own memory at the project store or back (\"enable the hybrid store\", \"redirect memory to the project\", \"włącz hybrydę\"). Not a routine memory read or write." +--- + +# redirect-memory + +Points Auto-memory at this project's Private memory store — or points it +back. One key, one realpath: the skill writes `autoMemoryDirectory` into +`.claude/settings.local.json` and nothing else. Explicit-ask only; never +commits; never touches store content. + +## Status (always first) + +Read `.claude/settings.local.json` (when present) and report: + +- no `autoMemoryDirectory` key in ANY honored settings file → redirect + off; sessions here use their default Home-dir store. Before saying + "off", also check `.claude/settings.json` (committed — honored despite + its own schema note) and the user-level settings, and name the file + that carries the key when one does; +- key equal to `realpath(/.claude/memory)` → active in THIS + environment; +- key differing → written from another environment (or the checkout + moved): name both paths and which filesystem view each resolves in. + +## Enable + +1. Compute `realpath(/.claude/memory)` — never accept a typed + path. The store need not exist yet: asking for the redirect is the + intent signal the core rule's adoption clause waits for, so offer to + create Private memory (always ignored, never asked tracked/ignored). +2. Warn before writing — always. The settings file is per-checkout; the + path is per-environment ("this path resolves only where this + filesystem view exists"). A session on the losing side gets Home-dir + memory only without trust or key; otherwise it manufactures a stray + store at the foreign path, or runs with no working Auto-memory at + all. A path-preserving bind mount (`-v "$PWD:$PWD"`-style) activates + both sides at once — two writers on one index. Environment detection + (`/.dockerenv`, `REMOTE_CONTAINERS` in env) only enriches this + wording ("you are writing the container-side path"); never refuse. +3. Write the key preserving every other key in the file — the Edit tool + when the file exists, the Write tool when it does not. +4. Offer a one-time `migrate-memory` pass (when that skill is available) + for THIS environment's Home-dir store — each environment keeps its + own, so migration runs where the notes live. +5. Verification, stated to the developer: settings are read at session + start, so the redirect takes effect in the NEXT session. Start one, + write a note, confirm the file lands in `.claude/memory/` — every + failure mode of the redirect is silent by platform design. + +## Disable + +Name the asymmetry, then let the developer choose: + +- **Remove the key** (the default): environment-neutral — every + environment returns to its own default Home-dir store. +- **`autoMemoryEnabled: false`**: switches Auto-memory off in EVERY + environment that trusts this checkout — the bigger hammer, written + only on an explicit request for it. + +Either way the store is untouched and the rule-driven loading continues. + +## Boundaries + +Writes exactly one settings file, never a committable one — the path has +no portable form. Never commits. Never creates or edits store content: +store creation is the core rule's offer, moving notes is +migrate-memory's, grooming is memory-review-session's. +``` + +- [ ] **Step 2: Verify** + +Run: `rg -c 'name: redirect-memory' plugins/project-memory/skills/redirect-memory/SKILL.md` +Expected: `1` + +Run: `rg -c 'INDEX\.md' plugins/project-memory/skills/redirect-memory/SKILL.md || echo ABSENT` +Expected: `ABSENT` + +Run: `rg -c 'never refuse' plugins/project-memory/skills/redirect-memory/SKILL.md` +Expected: `1` + +Run: `rg -ci 'hybrid mode|hybrid memory|native memor' plugins/project-memory/skills/redirect-memory/SKILL.md || echo CLEAN` +Expected: `CLEAN` (glossary bans — the trigger phrase deliberately says +"enable the hybrid store", the canonical term, so no exemption is +needed). + +- [ ] **Step 3: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass (the new skill's frontmatter parses; description +quoted — it contains `: `). + +```bash +git add plugins/project-memory/skills/redirect-memory/SKILL.md +git commit -m "feat(project-memory): redirect-memory skill - the Hybrid store opt-in switch" +``` + +--- + +### Task 6: README, hybrid section, and the plugin's identity surfaces + +**Files:** +- Modify: `plugins/project-memory/README.md` +- Modify: `plugins/project-memory/.claude-plugin/plugin.json` (description only) +- Modify: `.claude-plugin/marketplace.json` (project-memory entry description) +- Modify: `README.md` (repo root — the project-memory table row) + +**Interfaces:** +- Consumes: names settled in Tasks 1-5 (`redirect-memory`, the divergence + watch, the old-name offer). +- Produces: nothing later tasks use. + +- [ ] **Step 1: Mechanical rename** + +Edit tool, `replace_all: true`, old `INDEX.md` → new `MEMORY.md` (6 +occurrences). Then fix the article the replace breaks: old (exact) +`without an `MEMORY.md`` → new `without a `MEMORY.md``. + +- [ ] **Step 2: Extend the core-rule bullet** + +Old (exact, post-rename): + +``` +- `project-memory.md` — the always-on core: loads each part's `MEMORY.md` + at session start, routes project-scoped facts to the store (best-effort) + instead of home-dir memory, and owns Team memory's tracked/ignored + first-create question. Always-on deliberately: index loading has no file + path to scope on. +``` + +New: + +``` +- `project-memory.md` — the always-on core: loads each part's `MEMORY.md` + at session start (skipping an index Auto-memory already loaded), watches + `autoMemoryDirectory` for divergence from the store path, routes + project-scoped facts to the store (best-effort) instead of home-dir + memory, and owns Team memory's tracked/ignored first-create question. + Always-on deliberately: index loading has no file path to scope on. +``` + +- [ ] **Step 3: Add the redirect-memory skill bullet (after the migrate-memory bullet in `## Skills`)** + +Insert: + +``` +- **redirect-memory** — enables, disables, or inspects the Auto-memory + redirect that makes Private memory a Hybrid store: one + `autoMemoryDirectory` key in `.claude/settings.local.json`, written + with the current environment's realpath, warned about honestly, and + verified in the next session. +``` + +- [ ] **Step 4: Add the hybrid section (after `## Store layout`)** + +Insert: + +``` +## Hybrid store (opt-in) + +With the redirect active, `.claude/memory/` is also the session's +Auto-memory directory: Claude Code loads the store's `MEMORY.md` itself +(200-line / 25 KB budget) and writes its own notes there as ordinary +entries — a missing H1 is format debt the review session pays down, and +foreign frontmatter keys are left alone. The redirect is per environment +(host, dev container, WSL — each has its own path namespace and trust +record) and per checkout; the losing side of a switch keeps rule-driven +loading and a plainly-worded divergence message. Team memory never joins +the redirect — Auto-memory's team mounts are server-backed, and the +committed, reviewed, branch-following store is the point of Team memory, +not a limitation. +``` + +- [ ] **Step 5: Verify** + +Run: `rg -c 'INDEX\.md' plugins/project-memory/README.md || echo ABSENT` +Expected: `ABSENT` + +Run: `rg -c 'Hybrid store \(opt-in\)' plugins/project-memory/README.md` +Expected: `1` + +Run: `rg -c 'redirect-memory' plugins/project-memory/README.md` +Expected: `1` + +- [ ] **Step 6: Update the three identity surfaces (marketplace-sync rule: one commit)** + +The plugin description enumerates its skills, so shipping +`redirect-memory` changes it — and the marketplace-sync rule demands the +manifest, the catalog entry, and the repo README row move in the same +commit. + +In `plugins/project-memory/.claude-plugin/plugin.json`, old (exact): +`a Rules payload plus memory-review-session and migrate-memory skills` → +new: `a Rules payload plus memory-review-session, migrate-memory, and +redirect-memory skills` (keep the JSON string on one line). + +In `.claude-plugin/marketplace.json`, old (exact): `with grooming +(memory-review-session) and migration (migrate-memory) skills, shipped as +a Rules payload` → new: `with grooming (memory-review-session), migration +(migrate-memory), and the Auto-memory redirect (redirect-memory) skills, +shipped as a Rules payload` (one line). + +In the repo root `README.md` table row, old (exact): `with +`memory-review-session` and `migrate-memory` skills, distributed as a +Rules payload` → new: `with `memory-review-session`, `migrate-memory`, +and `redirect-memory` skills, distributed as a Rules payload` (one line). + +- [ ] **Step 7: Verify the surfaces agree** + +Run: `rg -c 'redirect-memory' plugins/project-memory/.claude-plugin/plugin.json .claude-plugin/marketplace.json README.md` +Expected: `1` from each of the three files. + +- [ ] **Step 8: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass. + +```bash +git add plugins/project-memory/README.md plugins/project-memory/.claude-plugin/plugin.json .claude-plugin/marketplace.json README.md +git commit -m "docs(project-memory): README, Hybrid store section, and identity surfaces for redirect-memory" +``` + +--- + +### Task 7: ADR 0003 and the glossary rename entries + +**Files:** +- Create: `docs/domain/adr/0003-hybrid-store-adoption.md` +- Modify: `docs/domain/glossary.md` (the three entries naming `INDEX.md`: + **Archive**, **Live entry**, **Close (an entry)**) + +**Interfaces:** +- Consumes: the settled terms (Hybrid store, Environment) and skill name. +- Produces: nothing later tasks use. + +- [ ] **Step 1: Write ADR 0003 (full content)** + +```markdown +--- +ticket: none +--- + +# Private memory may double as the Auto-memory directory, opt-in + +ADR 0002 closed with: "Making the store double as the Auto-memory +directory would reverse this decision, so that later choice has to face +it deliberately instead of sliding past it." This record is that choice, +faced. The hybrid adopts exactly two platform-owned identities — the +directory (`autoMemoryDirectory` may point at `.claude/memory/`) and the +index filename (`MEMORY.md`, the only name Auto-memory loads, adopted +globally so the format has no mode variants) — because in a Hybrid store +both pass ADR 0002's own test: they earn their place as the only way the +harness reads the store at all. + +Everything else stands. The plugin defines its own entry format, +converges on Auto-memory's without adopting it, and keeps the tolerance +clause for keys the harness writes; an entry Auto-memory writes into a +Hybrid store is an ordinary note whose missing H1 is format debt. The +adoption is opt-in per developer and per Environment, carried by one key +in `.claude/settings.local.json` written and removed by the +redirect-memory skill; declining it leaves the store purely rule-driven. +ADR 0002 remains in force for the format — this record narrows it only +at the directory-and-name boundary. +``` + +- [ ] **Step 2: Rename the three glossary entries** + +Three Edit calls in `docs/domain/glossary.md`, each `INDEX.md` → +`MEMORY.md`: + +1. **Archive** entry: `Distinct from a live entry (listed in \`INDEX.md\`)` → + `(listed in \`MEMORY.md\`)`. +2. **Live entry** entry: `currently listed in \`INDEX.md\`` → + `currently listed in \`MEMORY.md\``. +3. **Close (an entry)** entry: `Move an entry out of \`INDEX.md\`` → + `Move an entry out of \`MEMORY.md\``. + +- [ ] **Step 3: Verify** + +Run: `rg -c 'INDEX\.md' docs/domain/glossary.md || echo ABSENT` +Expected: `ABSENT` + +Run: `rg -c 'ADR 0002' docs/domain/adr/0003-hybrid-store-adoption.md` +Expected: `3` + +Run: `rg -c '^ticket: none' docs/domain/adr/0003-hybrid-store-adoption.md` +Expected: `1` + +- [ ] **Step 4: Commit** (docs only — no plugin validation needed, but it + is free: run `claude plugin validate .` anyway) + +```bash +git add docs/domain/adr/0003-hybrid-store-adoption.md docs/domain/glossary.md +git commit -m "docs: ADR 0003 and glossary for the Hybrid store" +``` + +Note: `docs/domain/glossary.md` also carries the earlier grilling edits +(Hybrid store, Environment, Auto-memory, Home-dir memory) — they belong to +this same commit; stage the whole file. + +--- + +### Task 8: Dogfood (developer-gated) + +**Files:** +- Modify: `plugins/project-memory/.claude-plugin/plugin.json` + +**Interfaces:** +- Consumes: all content tasks committed. +- Produces: the version string Task 9's checks expect. + +STOP and confirm with the developer before this task — it changes the +version and this machine's installed rules. + +- [ ] **Step 1: Mint the dogfood version** + +Edit `plugins/project-memory/.claude-plugin/plugin.json`: +`"version": "0.4.0-dev.memory-entry-format"` → +`"version": "0.4.0-dev.memory-hybrid"` (same anticipated release, this +topic's discriminator; the release PR strips it while minting the final +≥ 0.4.0). + +- [ ] **Step 2: Validate, then commit** + +Run: `claude plugin validate . && claude plugin validate plugins/project-memory` +Expected: both pass. + +```bash +git add plugins/project-memory/.claude-plugin/plugin.json +git commit -m "chore(project-memory): dogfood version for the memory-hybrid topic" +``` + +- [ ] **Step 3: Sync and adopt locally (outside git)** + +With the developer driving: run `working-process:sync-rules` with +explicit sources for BOTH payloads — `plugins/project-memory/rules` AND +`plugins/working-process/rules` (the marketplace serves GitHub master, so +discovery alone will not see this working tree, and the installed +working-process copy predates the store-probe decoupling — left stale, it +keeps enumerating the old index filename against the renamed store). Then +offer the store rename this repo's own Private memory is now eligible for +(`.claude/memory/INDEX.md` → `MEMORY.md`, a plain rename — the store is +ignored), and close the parked idea this spec graduated from +(`.claude/memory/idea-native-memory-hybrid.md`): delete the body, move its +index line to `ARCHIVE.md` **Done** as a redirect to +`docs/specs/2026-08-12-memory-hybrid-design.md`, per the conventions +lifecycle. Enabling the redirect itself stays a separate, explicit +`redirect-memory` ask. + +--- + +### Task 9: Whole-branch verification and lifecycle close + +**Files:** +- Modify: `docs/specs/2026-08-12-memory-hybrid-design.md` (frontmatter `status:`) +- Modify: `docs/plans/2026-08-12-memory-hybrid.md` (frontmatter `status:`) + +**Interfaces:** +- Consumes: everything committed. +- Produces: the branch ready for merge to `develop`. + +- [ ] **Step 1: Master rename check** + +```bash +cd "$(git rev-parse --show-toplevel)" +rg -c 'MEMORY\.md' plugins/project-memory/ -g '*' | sort +rg -n 'INDEX\.md' plugins/project-memory/ +rg -n '\bINDEX\b' plugins/project-memory/ +``` + +Expected: every plugin file that names the index names `MEMORY.md`; the +second command prints EXACTLY TWO lines — the core rule's pre-rename +detection ("holding an `INDEX.md` and no `MEMORY.md`") and the +conventions rule's old-name offer ("still named `INDEX.md`"). The third +command prints exactly THREE lines: those two plus the untouched +pre-existing eval query in +`skills/memory-review-session/evals/trigger-evals.json`. Any other line +fails the task. + +- [ ] **Step 2: Glossary-ban sweep over the shipped plugin content** + +Run: `git diff develop... -- plugins/ | rg -i 'native memor|hybrid mode|hybrid memory' || echo CLEAN` +Expected: `CLEAN`. The sweep deliberately covers only `plugins/` — the +spec, this plan, and the glossary's `_Avoid_` lists legitimately quote +the banned terms; the shipped plugin content may not use them. + +- [ ] **Step 3: Untouched-proof** + +Run: `git status --porcelain -- plugins/ | rg -v 'project-memory' || echo CLEAN` +Expected: `CLEAN` (no stray edits in other plugins, committed or not). + +Run: `git diff --name-only develop... -- plugins/ | rg -v 'project-memory' || echo CLEAN` +Expected: `CLEAN`. + +- [ ] **Step 4: Flip lifecycle status** + +The two architect rounds and their dispositions are recorded in the +spec's Review rounds section; the fresh-round verdict is annotated +resolved. With the developer's confirmation, set `status: implemented` in +the spec's and this plan's frontmatter (Edit tool), then commit both with +any remaining `docs/` changes of this work: + +```bash +git add docs/specs/2026-08-12-memory-hybrid-design.md docs/plans/2026-08-12-memory-hybrid.md +git commit -m "docs: spec and plan for the memory hybrid" +``` + +(Commit only with the developer's authorization, as always.) diff --git a/docs/specs/2026-08-10-project-memory-entry-format-design.md b/docs/specs/2026-08-10-project-memory-entry-format-design.md new file mode 100644 index 0000000..78776a5 --- /dev/null +++ b/docs/specs/2026-08-10-project-memory-entry-format-design.md @@ -0,0 +1,394 @@ +--- +ticket: none +date: 2026-08-10 +status: implemented +grilled: 2026-08-10 +architect: LGTM +branch: feature/memory-entry-format +base: develop +--- + +# Project-memory entry format — a canonical description and a tolerance clause + +## Problem + +The store's entry shape costs us in two places today. + +**The index line has no source.** A Project-memory entry carries no summary of +itself. The one-line summary that makes an entry findable lives only in its +`INDEX.md` line, written by hand. Nothing ties it to the entry it points at, +so it drifts as the entry grows, and `memory-review-session` has nothing to +check it against — it can only ask whether the line reads well. + +**Moving a fact into the store means translating it.** `migrate-memory` reads +home-dir memory, whose entries in practice carry `name` / `description` / +`metadata` frontmatter, and writes entries in an unrelated shape. The skill +restates each fact instead of moving it. + +Underneath both sits a third fact: Auto-memory now keeps a per-project store +of its own, with its own conventions, so a developer writing into both juggles +two mental models for one act. + +This spec fixes the first two. Both payoffs — a summary that cannot drift and +a migration that copies rather than translates — arrive with the change itself; +nothing here waits on a later decision. + +## Terminology + +This spec says **Auto-memory** for the harness mechanism and **Home-dir +memory** for the store it manages by default. Both are canonical; the grilling +session added the first and rewrote the second, which had been defined by a +path that `autoMemoryDirectory` can move. "Native memory" stays banned, and +"native format" would have inherited that ban. + +## What the platform actually guarantees + +Which parts of Auto-memory are contractual and which are merely observed +shapes every decision below. + +**Documented** (`code.claude.com/docs/en/memory`): Home-dir memory sits by +default at `~/.claude/projects//memory/`, one directory per +repository, shared across worktrees, machine-local. Its entrypoint is +`MEMORY.md`, of which the first 200 lines or 25 KB load each session. +`autoMemoryDirectory` redirects the directory and is read from any settings +scope, honored in project scope once the workspace trust dialog is accepted; +the value must be absolute or start with `~/`. The documentation describes +entry files as "plain markdown you can edit or delete at any time". The single +documented frontmatter field is `modified`, an ISO timestamp the harness +writes into any memory file that already has frontmatter (v2.1.214+). + +**Not documented**: the `name` / `description` / `metadata.type` shape and its +`user | feedback | project | reference` taxonomy. These are observable in the +shipped binary and in live sessions, but the published contract for entry +bodies is "any markdown". The surface has also moved repeatedly inside one +minor line — the 25 KB limit, the change in how the limit is counted, the +`modified` field, and the switch from silent truncation to an explicit error +all landed in separate releases. + +The conclusion that shapes this spec: **there is no documented entry format to +adopt.** What follows therefore converges on good field names without ever +declaring Auto-memory the source of the plugin's format — recorded as +[ADR 0002](../domain/adr/0002-own-entry-format.md), since the hybrid decision +will meet the same question again. + +## Decisions + +### `description` becomes the canonical summary + +Every entry carries a `description:` — one line, the summary that makes it +findable, and opens with an H1 carrying its title. The H1 was a habit before; +the projection makes it a requirement, because the whole `INDEX.md` line +projects from the entry, both halves of it: the link text from the H1, the +text after the dash from `description`. + +``` +- [H1 of the entry](file.md) — +``` + +On drift the entry wins: the file is the entry, the index is a view of it. +Without code the strings must live in two files — the index exists precisely +to spare the reader the bodies — but naming an authority turns a subjective +review question into a mechanical one. + +Both halves being derived has one consequence worth stating plainly: nobody +edits an index line directly any more. A wrong title is fixed in the entry's +H1, a stale summary in its `description`, and the line follows. + +Registry files carry no frontmatter and no `description`: `INDEX.md` and +`ARCHIVE.md` are registers, not entries. Only `INDEX.md` is a projection. +`ARCHIVE.md` is the opposite — a closed entry keeps no body, so its archive +line is the sole surviving record of it, derived from nothing and +re-derivable from nothing. + +### Plugin fields stay at the top level + +`status`, `spec`, `ticket` and `adr-candidate` remain top-level keys, never +nested under `metadata:`. Two reasons, and the first is decisive: the +`ticket-frontmatter` rule publishes an anchored sweep, +`rg -l --no-ignore '^ticket:.*ABC-123' docs/ .superpowers/`, which nesting +breaks across the whole repository, not merely in the store. Second, nesting +would mean *relying* on the observed behavior that the harness folds unknown +top-level keys into `metadata` — and the failure is asymmetric. If that folding +stops, top-level fields lose nothing, since a reader simply ignores them; +restructuring `metadata` would force a rewrite of every file. + +### Unknown frontmatter keys are preserved + +The conventions rule gains a tolerance clause: entries may carry frontmatter +written by other tools; the plugin's rules and skills leave unknown keys +alone; an unknown key never changes an entry's shape, and the `idea-` prefix +stays the authoritative discriminator. + +One sentence makes the store safe to share with any other writer, `modified` +included. + +### `name` is not adopted + +The filename already carries identity and the H1 carries the title. A third +encoding would need a tie-break rule for the case where they disagree. Finding +M4 of the 2026-07-20 round settled exactly that ambiguity — "three encodings +of shape … with no tie-break" — by naming the `idea-` prefix authoritative and +the rest derived. + +### `type` is read, never written + +The plugin does not write `metadata.type`. Its values do not partition the +plugin's space — a note and an idea are both "project" — and they answer a +question the store does not ask. + +It is useful in one direction only, and only weakly: when `migrate-memory` +reads home-dir entries, `type: user` and `type: feedback` are a hint that a +fact may be personal rather than project-scoped, so the skill considers those +entries last. The hint never decides. The plugin's routing turns on whether a +fact is scoped to this repository, not on the harness's taxonomy — a per-user +fact about this repo belongs in Private memory ("per-user → Private memory"), +and only personal, cross-project facts stay in Home-dir memory. Content +judgment remains the criterion and overrides the hint, so no candidate is +dropped before the developer sees it. + +This deliberately does **not** reverse finding I1 of the 2026-07-20 round +("`type`-keyed routing binds to a taxonomy the native memory model does not +expose … not a documented, stable interface — unsafe for a distributable +rule"). That finding stands, and re-verification confirms its premise holds: +the taxonomy is still undocumented and the surface around it has kept moving. +Reading a field to classify someone else's data is not the same as coding a +distributable rule against it. + +## Component changes + +**`project-memory-conventions` rule** — the Entry shapes section gains the +required `description`, the index-projection statement with its drift +authority, the top-level placement of plugin fields with its reason, so that +no one "tidies" it later, and the tolerance clause. + +**`migrate-memory`** — the "Translate shape" step shrinks to two acts: decide +the prefix, add the plugin's fields. `description` carries over unchanged, +which is the point of the convergence; `name` and `metadata.type` do not, +since the plugin writes neither. `name` still has to go somewhere, though: a +home-dir entry carries its title in that field and has no H1, so migration +turns `name` into the new entry's H1 and its slug into the filename. The field +is dropped, the information is not — and the step that deletes the source file +is irreversible, so this is not a detail to leave implied. The tolerance clause protects keys another +writer put in a file, not keys this skill chooses to import. Ordering by +`type` is the weak hint described above, never a filter. The skill keeps its +direction, its consent model, and its "never commits" boundary. + +One error path the skill does not know about today: its migration trace writes +a roll-up line into the Home-dir index, which Auto-memory loads and therefore +caps at 200 lines or 25 KB. Past the cap the write returns an error. The skill +keeps the trace to one line and, on that error, shortens rather than retries. + +**`memory-review-session`** — the opening audit gains three defect checks: +index lines whose text differs from their entry's `description`; index lines +whose link text differs from the entry's H1; and frontmatter keys the plugin +does not define, reported and never removed. The existing subjective step 4 of +the entry walk ("does the line summarize it well?") narrows to judging the +`description` itself, since the line is now derived from it. + +Alongside them the audit gains one non-defect category. A missing +`description` is **format debt**, not a fault: the field is new, so every +entry a store already holds lacks it — nineteen in this repo's own Private +memory. Debt is counted and listed apart from the defects the audit opens +with; a dangling link is broken, an entry written before the field existed is +not. Note that only this check fires store-wide: drift needs both halves +present to disagree, so on an entry with no `description` there is nothing for +the drift check to compare. Drift stays a defect precisely because both halves +are there. + +**README** — the entry-shape summary follows the rule. + +No other plugin changes. `working-process` does read entry frontmatter — its +`ticket` sweep scans `docs/` and so reaches Team memory — but the field it +reads keeps its position and meaning, which is the point of the decision +above. The two standards plugins touch the store only through their +store-existence probes, which look at `INDEX.md` and never open an entry. + +## Migration + +None required. Nothing breaks: an entry without `description` still parses, +still loads, still points somewhere. What the change leaves behind is not a +migration but format debt, and it is repaid two ways. The systematic pass is +`memory-review-session`, which lists the debt and offers to fill it while it +walks; the incidental one is any write that needs an index line re-projected, +since the line can only be derived from a `description` that exists — so a +touch on a pre-format entry backfills the field rather than hand-writing the +line. No compatibility window, no detection of half-migrated stores, no +coordination across installs. + +## Out of scope + +- **The index rename and the hybrid**, together as one later decision. + Renaming `INDEX.md` to `MEMORY.md` only pays if the store also becomes the + Auto-memory directory; on its own it is a breaking change across four + plugins for no operational gain, and it would hand one file two writers with + contradictory invariants (the documented 200-line budget against the store's + "live entries only, never carries history"). +- **Decoupling the foreign-plugin probes** from the index filename — parked + separately, non-breaking, and the prerequisite that would make that rename a + single-plugin change. +- **A budget on the Project-memory index** and **adopting `modified`**: both + belong to a store Auto-memory actually reads, which this one is not. The + budget still binds where the plugin writes into Home-dir memory, handled + under `migrate-memory` above. + +## Alternatives considered + +**Full compatibility with the Auto-memory entry shape** — also writing `name` +and `metadata.type` so entries are indistinguishable from Auto-memory's own. +Rejected: it reverses I1 without a new premise, and adds a fourth encoding of +an entry's shape beside the filename prefix, the index section, and the +frontmatter set. + +**Convergence plus `name`** — rejected above; a field with no consumer is not +worth the tie-break rule it needs. + +**Shrinking the plugin to Team memory only**, handing Private memory to the +harness and contributing conventions to it. Raised in consultation and worth +recording: Home-dir memory is per-project, private and machine-local, which is +what Private memory is, so the overlap is real. Rejected for now because it +costs the two-part symmetry, the `migrate-memory` story, and the location +model (`.claude/` private, `docs/` team) that was itself an argued decision — +and because it is a strategy question that should not ride inside a format +change. It belongs with the hybrid decision. + +## Design inputs + +Both working-process consultation personas ran on Opus 5 from one shared +briefing, each with its own focusing question, in the background, and neither +saw the other's contribution. A consultation returns no verdict, so nothing is +stamped. + +The architect supplied the distinction underlying this spec — convergence (the +plugin defines its own format and happens to pick the same field names) +against referential adoption (the rule declares the plugin's shape to be +Auto-memory's shape, surrendering semantics) — and the asymmetry that decides +it: the store holds durable, versioned, team-shared data, while the format +remains a moving internal detail of a self-updating tool. The architect also +found I1 and argued that the rename bundles two changes. + +The system designer supplied the delivery mechanics: skills ship with the +plugin version, rules reach a machine only through `sync-rules`, so "new +skills, old rules" is the default state right after an update. That is what +makes any rename of a rule-loaded filename unrecoverable from inside the +change, and why this spec contains no rename. The designer also caught that +after a rename both the source and the target index in `migrate-memory` would +be called `MEMORY.md` — in a skill whose whole job is moving lines between +them. + +The two disagreed on one fact: whether project settings honor +`autoMemoryDirectory`. The documentation settles it — they do, once the +developer accepts the workspace trust dialog. Neither position affects this +spec. + +## Glossary + +Applied during the grilling session: + +- **Auto-memory** added — the harness mechanism, explicitly never a store, so + the `_Avoid_: native memory` ban has a positive counterpart; +- **Home-dir memory** rewritten as the store Auto-memory manages, its path + demoted from definition to default. + +## Architect findings (round 1, 2026-08-10) + +Verdict: `concerns` (fable 5, 2026-08-10) — dispatched at the prescribed tier +(most capable available), so no fallback field. One Important, two Minor. The +round confirmed the claims the spec makes about the other four plugins, the +`^ticket:` sweep, the additive-migration property, and the versioning section, +and endorsed ADR 0002 against the ADR-format bar. + +**Important** + +1. **F1 — the `type` candidate filter misreads the plugin's own routing.** The + spec claims the routing rules "send `user` and `feedback` facts elsewhere", + but the core rule routes per-user *project-scoped* facts INTO the store + ("per-user → Private memory"); only personal, non-project-scoped facts stay + in Home-dir memory. A home-dir entry tagged `type: user` — "in this repo + the developer prefers X" — is exactly a Private-memory candidate, and the + filter drops it before the per-fact consent step, so the consent gate never + catches the omission. The skill's canonical criterion is content, and the + spec never says the filter is subordinate to it. + +**Minor** + +2. **F2 — migration's treatment of the source's Auto-memory keys is + unspecified.** "Decide the prefix, add the plugin's fields" implies carrying + the source frontmatter wholesale, including `name` and `metadata.type`, + which collides with the spec's refusal to write either. The tolerance + clause makes a lingering `name` harmless, so either choice works — but a + distributable skill needs the sentence. +3. **F3 — the spec's own frontmatter carries a banned term.** The `branch:` + value commits "native memory" — `_Avoid_` under both **Auto-memory** and + **Home-dir memory** — into the document whose Terminology section declares + that term banned. + +**Resolution (2026-08-10, no fresh round).** All three applied: + +- **F1** — the false claim about the plugin's routing is gone. `type` is now a + weak ordering hint that content judgment overrides, stated together with the + actual rule (a per-user fact about this repo belongs in Private memory), so + nothing is dropped before the developer sees it. +- **F2** — migration carries `description` and leaves `name` and + `metadata.type` behind, with the reason: the tolerance clause protects keys + another writer put in a file, not keys this skill imports. +- **F3** — the topic branch renamed to `feature/memory-entry-format` and + `branch:` updated. Nothing had been pushed, so the rename cost nothing. + +The architect's suggestion outside the findings — spelling out that a derived +index line is never edited directly — was applied too. + +## Architect findings (round 2, 2026-08-10) + +Verdict: `LGTM` (fable 5, 2026-08-10) — dispatched at the prescribed tier, so +no fallback field. Four Minor, all wording reconciliations, all applied: + +1. **M1** — the audit description still said "four mechanical checks" after + the amendment reclassified the first one, the store count was 21 (which + counted the two registry files this spec excludes from "entry"), and the + claim that two checks fire store-wide contradicted the spec's own reason + that drift needs both halves present. Applied: three defect checks plus one + debt category, nineteen entries, only the debt check fires store-wide. +2. **M2** — calling `ARCHIVE.md` a view invited treating archive lines as + regenerable; a closed entry keeps no body, so its line is the only record + there is. Applied. +3. **M3** — "backlog" is `_Avoid_`-banned twice in the glossary, and this text + ships into a grooming skill whose other subjects are parked ideas and + closed entries, exactly where the ban applies. Applied: **format debt**. +4. **M4** — backfill was attributed only to the review session, while the + shipped rule makes any re-projection require a `description` too. Applied + in the Migration section. + +The round also verified the platform-guarantees section against the live +documentation, both earlier amendments against the shipped rule and a live +Home-dir entry, and the cross-plugin claims. Two observations recorded outside +its findings, carried into the plan: index lines today carry status flavor +("parked (confirmed):") that will land inside `description` and can drift +against the `status` field with no mechanical check, and `description` values +routinely contain `: `, so the rule's example quotes the scalar. + +## Amendments (2026-08-10, after plan-adversary round 2) + +Writing the plan exposed three holes in this spec, all found by the second +plan-adversary round (Opus 5) and verified against the repo before being +applied here: + +- **The projection had no left source.** Nothing in the plugin required an + entry to have an H1, yet the index line's link text was defined as one. + The `description` section now requires it. +- **Migration would have lost the title.** Home-dir entries carry their title + only in `name`, which this spec declines to write, while the skill's next + step deletes the source file. The `migrate-memory` section now routes `name` + into the new entry's H1. +- **"Opportunistically" was never operationalized.** With the field new, the + audit would have reported every existing entry as a defect. The + `memory-review-session` section now separates format debt from defects. + +None of the three reverses a decision or an architect finding; each makes +explicit what the design already assumed. + +## Versioning + +No version bump on this branch. Per the plugin-versioning rule the release PR +from `develop` to `master` mints the bump once; a `-dev.` suffix appears here +only if we dogfood the change before then. The change is additive and breaks +nothing, so it sizes as a minor. diff --git a/docs/specs/2026-08-11-store-probe-decoupling-design.md b/docs/specs/2026-08-11-store-probe-decoupling-design.md new file mode 100644 index 0000000..5657007 --- /dev/null +++ b/docs/specs/2026-08-11-store-probe-decoupling-design.md @@ -0,0 +1,139 @@ +--- +ticket: none +date: 2026-08-11 +status: implemented +grilled: 2026-08-11 +branch: feature/store-probe-decoupling +base: develop +--- + +# Store probes — decoupling foreign plugins from the index filename + +## Problem + +Three plugins that are not project-memory hard-code the name of +project-memory's index file. Five surfaces carry the coupling: + +- `plugins/working-process/rules/review-reports.md:198` — the Candidate-gap + park offer checks `docs/memory/INDEX.md` and `.claude/memory/INDEX.md` to + decide whether the reviewed project keeps a store. This is the **owning** + statement of the check; +- `plugins/python-standards/skills/python-code-review/SKILL.md:77` and + `plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md:110` — + deliberate standalone restatements of the same check, so the skills work + without the working-process rules installed; +- `plugins/working-process/rules/process-artifacts.md:67` and + `plugins/working-process/rules/ticket-frontmatter.md:23` — enumerate + `INDEX.md` among the `ticket`-exempt registry files. + +The coupling means any change to the store's layout is a change to four +plugins. Concretely: the deferred rename of the index to `MEMORY.md` — one +half of the parked Auto-memory hybrid decision — is today a breaking release +across all four, with a compatibility window whose worst state (a renamed +store under an old check) reads as "no store" and silently swallows the park +offer. After this change it is a single-plugin release. + +The change also pays on its own: three plugins stop encoding another +plugin's internal layout — a defect whether or not the rename ever happens. + +## Terminology + +The check is a **store probe** — never a bare "probe", which the glossary +bans (`_Avoid_: probe (unqualified)` under **Contract probe**, a different +check that finds the installed report contract). The grilling session +(2026-08-11) added **Store probe** to the glossary. + +## Decisions + +### Store probes test the directory, not the index file + +The three probe surfaces check that the directories `docs/memory/` and +`.claude/memory/` exist, not the index files inside them. The directory is +a stable adoption signal: it comes into being exactly at adoption — the +opt-in first-create flow materializes it, and Private memory's directory +always carries a `*`-only `.gitignore` — and no layout change inside the +store can move it. The check stays as mechanical as before: one +existence test per part, no file opened, equally dependable when a +code-review skill runs as a subagent. + +The store-selection logic downstream of the probe is untouched: explicit +guidance wins; both parts present means ask; never offer to create a store. + +### Registry-file enumerations delegate to the owning rule + +The two working-process enumerations stop naming `INDEX.md` and instead +say what the exemption is for and who owns the list: the store's registry +files, per the project-memory conventions rule. The same principle let +`ARCHIVE.md` land in project-memory 0.2.0 without touching working-process; +extended to the index file, it keeps working-process out of every future +registry change. Idea entries keep carrying `ticket`; that +statement names a shape, not a file, and stays. + +### The restatements change in the same release + +The two standalone restatements in the standards skills are deliberate +copies of the review-reports contract and may not drift from it. All three +probe surfaces and both enumerations change in one release; the +marketplace-sync discipline (one bump per changed plugin, minted in the +release PR) carries the rest. + +## What this does not change + +- **project-memory itself.** No file of that plugin changes; the index + filename stays `INDEX.md`. The rename remains deferred, coupled to the + hybrid decision. After this change the filename survives outside + project-memory only in repo artifacts — three glossary entries + (**Archive**, **Live entry**, **Close (an entry)**) and the historical + specs and plans — so the rename's scope is one plugin plus those + documentation lines (architect consultation, 2026-08-11). +- **Probe semantics for adopted stores.** For any store the plugin created, + directory-exists and index-exists are the same signal today, so no + behavior changes for existing users. The one divergence is a directory + created by hand without an index: the old check said "no store", the new + one says "store". That reading is more honest — the directory is the + adoption signal the first-create flow materializes — and the offer it + gates is an offer, not a write. +- **The `ticket`-exemption semantics.** The same files are exempt; only + the list moves. + +## Alternatives considered + +**Relying on session context** — the core project-memory rule already loads +the store's index at session start, so a reviewer could simply notice +whether a store is in context. Rejected: the code-review skills can run as +subagents, and whether user-level rules load into a subagent context — and +survive compaction — is unverified. A file-level check is predictable in +both environments. + +**Deferring until the rename forces it** — rejected: bundling a +non-breaking cleanup into a breaking release maximizes the blast radius of +both. Sequenced first, the cleanup ships quietly and the rename shrinks to +one plugin. + +## Component changes + +**`working-process`** — `review-reports.md` (the owning probe statement), +`process-artifacts.md` and `ticket-frontmatter.md` (the two delegating +enumerations). + +**`python-standards`** — one line in `python-code-review/SKILL.md`. + +**`salesforce-standards`** — one line in `salesforce-code-review/SKILL.md`. + +Three plugins bumped in the next release; the change is behavior-compatible +for every store the plugin ever created, so each sizes as a patch unless it +rides a release with larger changes. + +## Versioning + +No version bump on this branch. Both standards plugins are disabled on this +machine and working-process's change is not worth dogfooding alone, so no +`-dev.` suffix is minted; the release PR sizes the bumps. + +## Glossary + +The grilling session (2026-08-11) added **Store probe**: the existence +check a plugin other than project-memory runs on a Project-memory part's +directory, distinct from the Contract probe. The definition describes the +post-spec state — the directory, never a file inside it — and this branch +carries the implementation that makes it true. diff --git a/docs/specs/2026-08-12-memory-hybrid-design.md b/docs/specs/2026-08-12-memory-hybrid-design.md new file mode 100644 index 0000000..22ae7d8 --- /dev/null +++ b/docs/specs/2026-08-12-memory-hybrid-design.md @@ -0,0 +1,378 @@ +--- +ticket: none +date: 2026-08-12 +status: implemented +grilled: 2026-08-12 +architect: concerns (resolved 2026-08-12) +branch: feature/memory-hybrid +base: develop +--- + +# Hybrid Private memory — opt-in redirect onto Auto-memory + +## Problem + +Claude Code ships its own memory mechanism, and the plugin's Private +memory duplicates it: two stores per project, two write paths, and the +harness's notes land in a directory the project never sees. The hybrid +makes `.claude/memory/` *be* the Auto-memory directory — opt-in per +developer and per environment — so Auto-memory itself reads the store's +index and writes its notes where the plugin's rules already govern. +Team memory (`docs/memory/`) cannot join the redirect (Auto-memory's +team mounts are server-backed; a repo directory cannot be a mount) and +does not need to: the core rule reads it in every mode, and living in git — reviewed, +branched with the work — is its differentiator, not a gap. + +## Platform facts (CC 2.1.220, verified 2026-08-10, probes re-run 2026-08-11) + +Everything below is empirical; none of it is documented contract. + +- `autoMemoryDirectory` (settings) redirects the Auto-memory directory. + The path must be absolute (`~/` expands); relative and `$VAR` paths are + silently dropped and the session falls back to the default Home-dir + directory. +- A *resolving* path is used blindly: a nonexistent absolute path is + created (`mkdir -p`) and used without a word; an uncreatable path + (permissions) is still named as the session's memory directory — no + fallback either way. The only fallback is the trust gate: in an + untrusted workspace, project and local settings are not consulted at + all. +- Local and project settings are honored when the workspace is trusted, + sandboxed, or the session is non-interactive. Committed + `.claude/settings.json` IS honored (its own schema description says + otherwise; the observed behavior wins), but the path's machine + specificity rules it out anyway. +- Auto-memory loads exactly one index file: `MEMORY.md`, up to 200 lines + / 25 KB. Other files in the directory — `INDEX.md`, `ARCHIVE.md` — + stay invisible to it. +- `autoMemoryEnabled: false` removes the mechanism entirely. +- Auto-memory's entry shape (frontmatter `name` / `description` / + `metadata.type`, `[[slug]]` links) is undocumented and has moved + repeatedly; ADR 0002 already governs the plugin's stance toward it. +- Auto-memory's write-side curation — whether it consolidates or prunes + index lines and entries on its own — is unprobed. The spec treats it + as residual risk with a named backstop, never as fact. + +## Decisions + +### The index renames to `MEMORY.md`, globally + +Every store's live index — both parts, hybrid or not — is `MEMORY.md`; +`ARCHIVE.md` stays. One name means no mode detection anywhere: rules, +skills, and docs speak with one voice, and in a hybrid store Auto-memory +loads exactly the file that is the index. The alternatives both pay +forever — a hybrid-only rename forks every sentence that names the index +into two variants, and keeping two indexes puts two writers behind two +catalogs of one directory, with drift built in and the session loading +both. The rename pays once, and the store-probe decoupling +(`docs/specs/2026-08-11-store-probe-decoupling-design.md`) already made +the installment small: no plugin other than project-memory names the +index file, so the scope is this plugin plus three glossary entries. + +The rename has one internal cost worth naming: `MEMORY.md` then names +the home-dir and project indexes alike, so migrate-memory loses the +filename as its store discriminator — mitigated by qualifying every +store name with its path in that skill's wording. + +Adopting the harness's entrypoint *name* is not adopting its format. +ADR 0002's test — adopt only what earns its place — is met: in a hybrid +store `MEMORY.md` is the only filename Auto-memory will load; elsewhere +the name is as arbitrary as `INDEX.md` was. Residual risk: if the +harness ever renames its entrypoint, non-hybrid stores carry a stale +name at zero functional cost. + +Existing stores migrate mechanically: a session governed by the +conventions rule, finding a store whose index still bears the old name, +offers the rename once — and re-points nothing, because nothing outside +the plugin points at the filename any more. + +### Opt-in is a dedicated skill + +A new explicit-ask skill, `redirect-memory` — enable, disable, and +status in one skill, named for what it does per the `sync-rules` +pattern (settled at the 2026-08-12 grilling) — owns the switch. Enable: + +- writes `realpath(/.claude/memory)` — computed, never typed by + the developer — as `autoMemoryDirectory` into the project's + `.claude/settings.local.json`, and names the activation environment in + its output: the path resolves only where this filesystem view exists. + That file is per-checkout, not per-machine — in a bind-mounted + dev container, host and container read the same file through different + path namespaces; +- warns, never refuses: environment detection (`/.dockerenv`, + `REMOTE_CONTAINERS` in env) only enriches the warning's wording. The + warning names the path-preserving-mount case (one key activating both + sides at once) and states what the losing side gets — Home-dir memory + only without trust or key; otherwise a manufactured stray store or no + working Auto-memory. Both environment kinds are equally correct places + to opt in; with disjoint namespaces the active side is simply the one + that wrote last; +- offers a one-time `migrate-memory` pass for the facts accumulated in + this environment's Home-dir store, stating the per-environment reach: + each environment keeps its own Home-dir store, and migration runs + where the notes live; +- ends with a verification step — write a note, confirm it lands in + `.claude/memory/` — because every failure mode of the redirect is + silent by platform design. + +Disable names the undo asymmetry before acting: removing the key is +environment-neutral (every environment returns to its default), while +`autoMemoryEnabled: false` in a per-checkout file switches Auto-memory +off in every environment that trusts the checkout. The store itself is +never touched by either. + +### Activation contract, stated once + +The redirect is in effect exactly when settings are honored — trusted +workspace, sandbox, or non-interactive session: properties of the +environment and the session, never of the repo. Whether it lands on the +store is a separate question: the stored path must name the store in the +session's filesystem namespace, and mere resolution proves nothing, +because the platform manufactures a missing path. One absolute path in +one file still yields a guarantee worth naming, with one stated premise: +at most one Auto-memory writer per store, provided the environments' +path namespaces are disjoint at the store path — the normal +dev-container layout. A path-preserving bind mount (`-v "$PWD:$PWD"`- +style, or a `workspaceMount` that keeps the host path) defeats the +premise: the one key resolves on both sides, both trusted environments +are active at once, and two harnesses share one `MEMORY.md`. The +`redirect-memory` warning names this case alongside its container +wording. + +What the losing side of last-write-wins gets follows from the platform +facts, not from a fallback story: a session with settings unconsulted +(untrusted) or no key in sight stays on its Home-dir store; a trusted +session whose stored path is creatable manufactures a stray store there +and writes to it; a trusted session whose stored path is uncreatable +runs with no working Auto-memory at all. The rule below makes the stray +and the broken cases visible. + +### The rule deduplicates loading and watches for divergence + +In a hybrid store the harness and the core rule would both load Private +memory's `MEMORY.md`. The rule defers: a part whose index the session +context already carries (the Auto-memory block) is not read again; +everywhere else — non-hybrid stores, untrusted sessions, subagents — +the rule loads it as today and remains the safety net. Additionally, +when Private memory exists and +`.claude/settings.local.json` carries `autoMemoryDirectory`, the +rule compares it with `realpath(/.claude/memory)` and says so +plainly on mismatch. This check is mandatory, not decorative: the +platform has no fallback for a resolving-but-wrong path, so the rule's +message is the only visible signal in exactly the environment where the +redirect silently misses. A worktree carries no +`settings.local.json`, so the check raises no false alarms there. + +The 200-line / 25 KB load budget becomes an explicit index convention: +lines stay thin, and the "index too long — shorten, don't retry" error +path graduates from migrate-memory to the conventions rule. + +### Entries Auto-memory writes are first-class notes + +An entry the harness writes is a note like any other. Missing H1 is +format debt — the existing memory-review-session machinery counts it +apart from defects and offers to pay it during the walk; `description` +it already writes; `metadata.type` and other foreign keys stay intact +under the conventions rule's tolerance clause. The index-line format is +already convergent (`- [title](file.md) — summary` on both sides), so a +line the harness appends is a legal projection — appended section-blind: +the harness knows nothing of the Notes/Ideas sectioning, its lines land +where it puts them, and re-sectioning belongs to the groom +(memory-review-session's existing audit and sweep). Auto-memory is +self-curating by design and its write-side behavior is unprobed: whether +it consolidates or prunes index lines is a stated residual risk, not a +verified fact, and the audit — dangling links, body files without lines, +the entry-wins drift rule — is the named backstop, absorbing exactly the +damage such curation could do. `ARCHIVE.md` stays invisible to it by +design; nothing instructs the harness to close entries, and a line its +curation removes is restored by the sweep. + +## What this does not change + +- **Team memory.** Rule-driven in every mode; the hybrid cannot and does + not touch it. +- **The entry format.** ADR 0002 stands: the plugin defines its own + format and converges without adopting. The hybrid adopts the directory + identity and the entrypoint name — the two things that earn their + place — and nothing else; ADR 0003 records that confrontation. +- **Store probes.** Foreign plugins test the part directories + (post-decoupling), so the rename and the redirect are both invisible + to them. +- **`ARCHIVE.md`** and the closed-entry lifecycle. + +## Skills impact + +- **`redirect-memory`** (new): as specified above. +- **`migrate-memory`**: with the hybrid active, home-dir → Private is a + no-op by identity (same directory); the skill detects it and says so. + Its wording qualifies store names by path — the rename removed the + filename discriminator. Team-memory migration is unchanged. +- **`memory-review-session`**: no mechanical change; harness-written + entries fall into the existing audit (missing H1/description = format + debt). +- **README**: a hybrid section. + +## ADR and glossary + +ADR 0002 closes with: "Making the store double as the Auto-memory +directory would reverse this decision, so that later choice has to face +it deliberately instead of sliding past it." This spec is that choice, +faced: **ADR 0003** cites the sentence and splits what survives (own +entry format, tolerance clause) from what changes (directory identity, +index name). ADR 0002 remains in force. + +Glossary: the three entries naming `INDEX.md` (**Archive**, **Live +entry**, **Close (an entry)**) update to `MEMORY.md`; **Auto-memory** +and **Home-dir memory** gained the redirect corrections at the grilling +session (2026-08-12), which also added two terms: **Hybrid store** — the +Private memory store that is the current session's Auto-memory +directory, a property of the session, never of the checkout — and +**Environment** — one filesystem view a session runs in, the unit the +redirect activates per. + +## Documented limitations (not solved, stated) + +- The redirect carries one absolute path with no portable form. With + disjoint path namespaces that means at most one side of a checkout + writes the store (last write wins); a path-preserving mount activates + every side whose namespace contains the path, and the one-writer + premise falls with it. +- The losing side of last-write-wins has no fallback story beyond the + trust gate: untrusted or keyless sessions stay on their Home-dir + store; trusted ones either manufacture a stray store at the foreign + path or run with no working Auto-memory — the divergence check is the + signal in both. Merging stray and Home-dir stores into the project + store stays manual (`migrate-memory`, run per environment). +- A git worktree carries no `.claude/settings.local.json` and no Private + store of its own: worktree sessions — including this repo's own + feature workflow — sit on the inactive side silently (the divergence + check stays quiet there by design: no key is present), and their + Auto-memory writes land in a fresh, path-keyed Home-dir store of the + worktree, stranded when the worktree is removed. +- Trust is per environment (and per volume where `~/.claude.json` lives + in one); an environment that does not persist that file loses the + activation on every rebuild. + +## Out of scope + +- Synchronizing or merging Home-dir stores with the project store. +- Automating the opt-in inside environment repos (a dev-container setup + may write the key with its own in-container path from its own scripts; + the plugin enables that by keeping the contract at "one key, one + realpath" — and does not own it). +- Coordinating simultaneous host and container sessions beyond the + degradation above. + +## Alternatives considered + +- **Two indexes in a hybrid store** (`INDEX.md` for the rule, + `MEMORY.md` for the harness) — rejected: two writers, two catalogs, + structural drift, double session load, and the harness's budget spent + on a file the rule does not control. +- **Hybrid-only rename** — rejected: forks the format into permanent + mode variants across every rule, skill, and doc, and every store visit + starts with mode detection. +- **Committed settings as the opt-in carrier** — rejected: the path is + environment-specific by requirement; committing it breaks every other + checkout. Env vars and SessionStart hooks are verified dead ends: the + harness reads its memory env vars from its own `process.env`, which a + hook cannot reach — `$CLAUDE_ENV_FILE` feeds only the Bash tool's + shell. +- **Refusing opt-in inside containers** — rejected: the container is the + designed place to work in such setups, so its inside is the correct + side to opt in from; the skill warns and names the environment + instead. + +## Review rounds + +- **Architect**, 2026-08-12, **Fable 5**. Verdict **blocking**: 2 + Important, 2 Minor. + 1. (Important) The "at most one Auto-memory writer" guarantee is not + structural: a path-preserving bind mount (`-v $PWD:$PWD`, + `workspaceMount` at `${localWorkspaceFolder}`) makes one key resolve + in both environments — two trusted sides simultaneously active, two + harness writers on one `MEMORY.md`. Qualify the guarantee (disjoint + namespaces at the store path), name the case in the skill's warning, + reword the limitation. + 2. (Important) Second-writer coverage stops at the index-line format: + the sectioned index (Notes/Ideas) meets a section-blind writer, and + "Auto-memory never closes an entry" is an unverified write-side + claim in a spec that pins every other fact to a probe. Decide where + harness appends land (the groom as re-sectioning owner), and probe + write-side curation or downgrade the claim to a stated residual risk + with the audit named as backstop. + 3. (Minor) Component changes predate the grilling: "hybrid-memory" + (instantiates a glossary ban), "all three existing skills" (the + plugin ships two), "the mode term" (vague; two terms landed). + 4. (Minor) Worktree sessions — this repo's own feature workflow — sit + on the inactive side with no settings file and no signal; the + limitations section is silent on them. + + **Disposition (2026-08-12): all four accepted and applied.** Finding + 1: the one-writer guarantee now carries its disjoint-namespaces + premise, the skill's warning and the limitations name the + path-preserving-mount case. Finding 2: harness appends declared + section-blind with the groom as re-sectioning owner; "never closes" + replaced by a residual-risk statement with the audit as named backstop + and a matching Platform-facts bullet (write-side curation unprobed — + the probe was declined as unreliable to provoke; risk stated instead). + Finding 3: Component changes re-read against the grilled body (two + skills, `redirect-memory`, glossary line rewritten). Finding 4: a + worktree limitation line added. + +- **Architect, fresh round**, 2026-08-12, **Fable 5**. Verdict + **concerns**: 1 Important, 4 Minor. Prior dispositions verified as + genuinely resolved. + 1. (Important) The "inactive side degrades to its own Home-dir memory" + narrative contradicts the spec's own platform facts: a *trusted* + session on the losing side never returns to Home-dir memory — a + creatable foreign path is manufactured (`mkdir -p`) and becomes a + stray active store; an uncreatable one leaves no working Auto-memory + at all. Home-dir degradation holds only for untrusted or keyless + sessions. Replace the claim, everywhere it appears and in the + skill's warning, with that trichotomy; "resolves" in the activation + contract does not discriminate the store from a stray. + 2. (Minor) Worktree limitation understates: Home-dir stores are + path-keyed, so each worktree gets a fresh store, stranded at + cleanup; the glossary's "per Environment" could tighten to "per + checkout path". + 3. (Minor) "Both conjuncts are properties of the environment" — only + trust is environmental; sandbox and non-interactivity are + per-session. Say "of the environment and the session". + 4. (Minor) The rename costs migrate-memory its filename discriminator + (`MEMORY.md` then names home-dir and project indexes alike); the + rename decision should name the cost, path-qualified store names the + mitigation. + 5. (Minor) ADR 0003 is promised but missing from Component changes. + + **Disposition (2026-08-12): all five accepted and applied.** Finding + 1: the degradation story replaced by the fact-derived trichotomy in + the activation contract, the limitations, and the skill's warning; the + contract now separates "in effect" (settings honored) from "lands on + the store", since resolution alone proves nothing. Finding 2: the + worktree limitation names the fresh path-keyed store stranded at + cleanup; the glossary's Home-dir memory tightened to "per checkout + path". Finding 3: "of the environment and the session, never of the + repo". Finding 4: the rename decision names the lost filename + discriminator with path-qualified store names as mitigation, mirrored + in Skills impact. Finding 5: ADR 0003 added to Component changes. + +## Component changes + +**`project-memory`** — the core rule (dedup + divergence check, rename), +the conventions rule (index name, budget convention, rename offer), both +existing skills' index-name mentions, the new `redirect-memory` skill, +README. Breaking rename → **minor** bump pre-1.0, minted in the +release PR; the release obligation ≥ 0.4.0 from the entry-format branch +still stands, so one shared final version covers both. Dogfooding on +this repo is the natural path (`0.4.0-dev.`). + +**Glossary** — the two terms and two corrections landed at the grilling +(2026-08-12); the three `INDEX.md` entries update with the rename at +implementation. + +**ADR 0003** — the confrontation record (`docs/domain/adr/`), written at +implementation per the ADR-and-glossary section. + +**No other plugin changes** — the decoupling already removed every +foreign coupling to the store's layout. diff --git a/plugins/project-memory/.claude-plugin/plugin.json b/plugins/project-memory/.claude-plugin/plugin.json index d8641e2..d7fb59c 100644 --- a/plugins/project-memory/.claude-plugin/plugin.json +++ b/plugins/project-memory/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "project-memory", - "description": "In-repo project memory for Claude Code sessions — committed Team memory (docs/memory/) and git-ignored per-user Private memory (.claude/memory/); a Rules payload plus memory-review-session and migrate-memory skills", - "version": "0.3.0", + "description": "In-repo project memory for Claude Code sessions — committed Team memory (docs/memory/) and git-ignored per-user Private memory (.claude/memory/); a Rules payload plus memory-review-session, migrate-memory, and redirect-memory skills", + "version": "0.4.0", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["memory", "notes", "ideas", "knowledge", "rules"] diff --git a/plugins/project-memory/README.md b/plugins/project-memory/README.md index 8455a65..3fdf912 100644 --- a/plugins/project-memory/README.md +++ b/plugins/project-memory/README.md @@ -7,7 +7,7 @@ home-dir memory but living in the project, in two parts: team-relevant parked ideas, cross-ticket state, and operational gotchas. - **Private memory** — `.claude/memory/`, per-user, always git-ignored. -Each part holds a thin `INDEX.md` of live entries (loaded at session start), +Each part holds a thin `MEMORY.md` of live entries (loaded at session start), an `ARCHIVE.md` of closed-entry lines (read on demand), and the live entry bodies (pulled only when relevant). @@ -15,17 +15,21 @@ bodies (pulled only when relevant). The plugin ships two rule files as a Rules payload: -- `project-memory.md` — the always-on core: loads each part's `INDEX.md` - at session start, routes project-scoped facts to the store (best-effort) - instead of home-dir memory, and owns Team memory's tracked/ignored - first-create question. Always-on deliberately: index loading has no file - path to scope on. +- `project-memory.md` — the always-on core: loads each part's `MEMORY.md` + at session start (skipping an index Auto-memory already loaded), watches + `autoMemoryDirectory` for divergence from the store path, routes + project-scoped facts to the store (best-effort) instead of home-dir + memory, and owns Team memory's tracked/ignored first-create question. + Always-on deliberately: index loading has no file path to scope on. - `project-memory-conventions.md` — paths-scoped to the two store - directories: note/idea entry shapes, team-memory scope, the gotcha↔ADR - promotion offer, the live-only `INDEX.md` / on-demand `ARCHIVE.md` layout, + directories: note/idea entry shapes, the required H1 and `description` and + the index line projected from them, top-level placement of the plugin's own + frontmatter fields, the tolerance clause for keys other tools wrote, + team-memory scope, the gotcha↔ADR + promotion offer, the live-only `MEMORY.md` / on-demand `ARCHIVE.md` layout, and the closure lifecycle. -Adoption is opt-in: without an `INDEX.md` the core rule is a no-op — it +Adoption is opt-in: without a `MEMORY.md` the core rule is a no-op — it never scans, creates, or nags. The store exists only once you create it. ## Installation @@ -37,6 +41,14 @@ by the working-process plugin's Rules engine: 2. `/plugin install working-process@missing-bits` (brings the engine) 3. Run the `working-process:sync-rules` skill and pick this payload. +Optional companion: the `elements-of-style` plugin. When its +`writing-clearly-and-concisely` skill is present, the conventions rule +routes entry prose through it; without it nothing changes. Not a +dependency — install it yourself: + + /plugin marketplace add obra/superpowers-marketplace + /plugin install elements-of-style@superpowers-marketplace + The engine dependency is operational only — it installs and updates the rule files. The rules' content does not require the working-process *rules*: without them there is simply no Process-directory ceremony beyond @@ -47,16 +59,40 @@ counts as a Process directory and the routing targets light up. ## Skills - **memory-review-session** — an explicit-ask grooming conversation: audits - `INDEX.md`/`ARCHIVE.md` consistency, then walks entries toward their correct + `MEMORY.md`/`ARCHIVE.md` consistency, then walks entries toward their correct lifecycle state (close finished notes, promote or drop ideas, split/merge, - sharpen index lines). Recommends per entry; never bulk-cleans; never commits. + sharpen entry descriptions). Recommends per entry; never bulk-cleans; never commits. - **migrate-memory** — moves project-scoped facts about the current repo out of home-dir memory into this store (one direction), routing each to Team or Private memory and offering store adoption when none exists yet. +- **redirect-memory** — enables, disables, or inspects the Auto-memory + redirect that makes Private memory a Hybrid store: one + `autoMemoryDirectory` key in `.claude/settings.local.json`, written + with the current environment's realpath, warned about honestly, and + verified in the next session. ## Store layout -Each part keeps a thin `INDEX.md` of **live** entries (the only file read at +Each part keeps a thin `MEMORY.md` of **live** entries (the only file read at session start), an `ARCHIVE.md` of closed-entry lines (Done / Dropped, read on demand), and the live entry bodies. A closed entry keeps no body — only its one-line archive record. + +Every entry opens with an H1 and carries a one-line `description:` in its +frontmatter, and its index line is a projection of the two — link text from +the H1, summary from `description` — so index lines are re-derived, never +authored by hand. Frontmatter written by other tools is left alone. + +## Hybrid store (opt-in) + +With the redirect active, `.claude/memory/` is also the session's +Auto-memory directory: Claude Code loads the store's `MEMORY.md` itself +(200-line / 25 KB budget) and writes its own notes there as ordinary +entries — a missing H1 is format debt the review session pays down, and +foreign frontmatter keys are left alone. The redirect is per environment +(host, dev container, WSL — each has its own path namespace and trust +record) and per checkout; the losing side of a switch keeps rule-driven +loading and a plainly-worded divergence message. Team memory never joins +the redirect — Auto-memory's team mounts are server-backed, and the +committed, reviewed, branch-following store is the point of Team memory, +not a limitation. diff --git a/plugins/project-memory/rules/project-memory-conventions.md b/plugins/project-memory/rules/project-memory-conventions.md index fd9cb6e..7ec0f55 100644 --- a/plugins/project-memory/rules/project-memory-conventions.md +++ b/plugins/project-memory/rules/project-memory-conventions.md @@ -21,19 +21,47 @@ paths: - **Archive** `ARCHIVE.md` (per part) — the closed-entry record: one line per closed entry, in a **Done** or **Dropped** section. Read on demand only (e.g. answering "did we already consider this?"), never at session start. A - `ticket`-exempt registry file like `INDEX.md`. + `ticket`-exempt registry file like `MEMORY.md`. -`INDEX.md` holds only **live** entries, sectioned per part: a **Notes** +`MEMORY.md` holds only **live** entries, sectioned per part: a **Notes** section (active notes, recall-on-demand) and an **Ideas** section (only `parked` ideas, each a link to its `idea-` file). Closed entries — promoted, finished, or dropped — do not live here; they move to `ARCHIVE.md` the moment -they close (see Lifecycle). `INDEX.md` is the only file read at session start, -so it never carries history. +they close (see Lifecycle). `MEMORY.md` is the only file read at session +start, so it never carries history. The harness caps any index it loads — +200 lines / 25 KB; in a Hybrid store that is this file. Keep lines thin, +and when a write bounces off the cap, shorten the index rather than retrying. + +### Stores that predate the rename + +A store whose live index is still named `INDEX.md` predates this +convention. Offer once to rename the file to `MEMORY.md` (`git mv` in a +tracked part, a plain rename in an ignored one) — content untouched, and +nothing to re-point: no plugin outside project-memory names the index +file. Declined, the offer rests for the session; the core rule keeps +reading the old-named index meanwhile, so nothing goes dark. ## Entry shapes Shape, not topic, is the criterion; the `idea-` prefix is authoritative (the -INDEX section and frontmatter follow it). +index section and frontmatter follow it). + +Every entry opens with an H1 carrying its title and carries a `description:` +— one line, the summary that makes it findable. Its `MEMORY.md` line is a +projection of the entry, both halves of it: the link text from the H1, the +text after the dash from `description`. + + - [H1 of the entry](file.md) — + +Quote the `description` scalar whenever it contains `: `, as index summaries +routinely do. Nothing parses entry frontmatter today, but an unquoted colon +makes the block invalid YAML the moment something does. + +On drift the entry wins — the file is the entry, the index a view of it — so +an index line is never authored directly, only re-projected: correct the H1 or +the `description` first, then re-derive the line from them. `MEMORY.md` and +`ARCHIVE.md` are registry files, not entries: no frontmatter, no +`description`, no H1 requirement. - **note** (no prefix) — a gotcha or cross-ticket state; a cross-ticket registry, exempt from any per-work `ticket` convention the project keeps. @@ -47,6 +75,27 @@ INDEX section and frontmatter follow it). drop the body closes per the Lifecycle section; the `spec:` pointer then lives on the `ARCHIVE.md` Done redirect line, not a live file. +Every field this rule defines — `description`, `status`, `spec`, `ticket`, +`adr-candidate` — sits at the top level of the frontmatter, never nested +under a `metadata:` block. Nesting would break the anchored `^ticket:` sweep +the project's ticket convention publishes (when it keeps one), across the +whole project rather than only in the store. + +Entries may carry frontmatter that other tools wrote. Leave unknown keys +alone: never remove them, never rewrite them, and never let one change an +entry's shape — the `idea-` prefix stays authoritative. In a Hybrid store +Auto-memory is a second writer: an entry it writes is an ordinary note +whose missing H1 is format debt, and an index line it appends lands +section-blind — re-sectioning belongs to the grooming walk +(memory-review-session, when available), never to a routine write. + +When the `elements-of-style:writing-clearly-and-concisely` skill is +available, entry prose gets its pass: invoke it before writing a new body +or `description`, and run an explicit editing pass when reshaping one. +The pass binds wording, never an entry's content or lifecycle; entries +Auto-memory writes on its own keep their own voice. Without the skill +there is no substitute pass. + ## Team-memory scope Team memory owns only parked ideas, cross-ticket initiative state, and @@ -77,7 +126,7 @@ keeps no body, only a one-line `ARCHIVE.md` record. The governing test: a body survives closure only when no other artifact carries its content. - **Promotion** (content moves to a spec, ADR, or the glossary): delete the - body; move the `INDEX.md` line to `ARCHIVE.md` **Done** as a redirect + body; move the `MEMORY.md` line to `ARCHIVE.md` **Done** as a redirect pointer to the new home. - **Closed note** (a work-state note after its release, an expired gotcha whose resolution now lives in code or docs): delete the body; write an @@ -94,5 +143,5 @@ body survives closure only when no other artifact carries its content. - **Obsolete** (no longer true, nothing worth pointing at): delete the body and its index line — no `ARCHIVE.md` record. -Never leave an empty or stub body file. `INDEX.md` and `ARCHIVE.md` are +Never leave an empty or stub body file. `MEMORY.md` and `ARCHIVE.md` are registry files, not bodies: an empty section header in either is fine. diff --git a/plugins/project-memory/rules/project-memory.md b/plugins/project-memory/rules/project-memory.md index 017ae10..4e3a9b9 100644 --- a/plugins/project-memory/rules/project-memory.md +++ b/plugins/project-memory/rules/project-memory.md @@ -6,15 +6,31 @@ in the project, in two parts: - **Team memory** — `docs/memory/`, committed, shared with the team. - **Private memory** — `.claude/memory/`, per-user, always git-ignored. -Each part holds a thin `INDEX.md` (one-line pointers to live entries) plus +Each part holds a thin `MEMORY.md` (one-line pointers to live entries) plus flat topic files, and an `ARCHIVE.md` of closed-entry lines read on demand — never at session start. ## Loading -When a part's `INDEX.md` exists, read it at session start and pull a topic -file only when its index line is relevant. If neither `INDEX.md` exists this -rule is a no-op — it never scans, creates, or nags. +When a part's `MEMORY.md` exists, read it at session start and pull a topic +file only when its index line is relevant — unless the session context +already carries that index: in a Hybrid store, Auto-memory loads Private +memory's `MEMORY.md` itself, and reading it twice buys nothing. If neither +`MEMORY.md` exists this rule is a no-op — it never scans, creates, or nags. + +One exception to the no-op: a part directory holding an `INDEX.md` and no +`MEMORY.md` is a store from before the rename, never "no store". Say so at +session start, point at the conventions rule's rename offer, and read the +old-named index as the part's index meanwhile — the Adoption clause must +never create a second index beside it. + +When Private memory exists (`.claude/memory/` present) and +`.claude/settings.local.json` carries `autoMemoryDirectory`, compare its +value with the realpath of `/.claude/memory`. On a mismatch, say +so plainly: the redirect points elsewhere in this session's filesystem +namespace, so Auto-memory is writing a stray store or none at all — the +platform gives no other visible signal. Never edit settings from this rule; +that is the redirect-memory skill's job, when it is available. ## Routing (best-effort) @@ -32,8 +48,8 @@ guarantee. On a miss a fact lands in home-dir memory and can be moved later. Adoption is opt-in: the store exists only when the developer creates it. Offer to create it only when the developer signals intent to record something project-scoped — never proactively. On first creation, point at the -project-memory-conventions rule for the INDEX sections and entry shapes (no -`INDEX.md` yet exists to trigger it). +project-memory-conventions rule for the index sections and entry shapes (no +`MEMORY.md` yet exists to trigger it). For Team memory (`docs/memory/`), ask the tracked/ignored question before writing anything — this rule owns that question: diff --git a/plugins/project-memory/skills/memory-review-session/SKILL.md b/plugins/project-memory/skills/memory-review-session/SKILL.md index 0c7bd7c..23f7172 100644 --- a/plugins/project-memory/skills/memory-review-session/SKILL.md +++ b/plugins/project-memory/skills/memory-review-session/SKILL.md @@ -1,6 +1,6 @@ --- name: memory-review-session -description: "Grooming session for a Project-memory store: audits INDEX/ARCHIVE consistency, then walks entries toward their correct lifecycle state — closing finished notes, promoting or dropping ideas, splitting or merging, sharpening index lines. Use ONLY when the developer explicitly asks to review or tidy Project memory (\"groom the store\", \"memory review\", \"przejrzyjmy memory\"). A routine memory read or write, or a question about an entry's content, is NOT a trigger." +description: "Grooming session for a Project-memory store: audits MEMORY/ARCHIVE consistency, then walks entries toward their correct lifecycle state — closing finished notes, promoting or dropping ideas, splitting or merging, sharpening entry descriptions. Use ONLY when the developer explicitly asks to review or tidy Project memory (\"groom the store\", \"memory review\", \"przejrzyjmy memory\"). A routine memory read or write, or a question about an entry's content, is NOT a trigger." --- # memory-review-session @@ -17,19 +17,49 @@ Only on a direct request to review or tidy the store. Routine memory reads/writes and questions about an entry's content do NOT start a session — mirroring the core rule's "never scans, creates, or nags" stance. +## Scope + +The developer may narrow a session to one section ("only the Ideas"), a +topic, or named entries. The narrowing binds the walk alone: the opening +audit and its sweep always cover the whole part — they are the invariant's +safety net and never depend on what the developer asked to see. Inside a +narrowed walk, per-entry recommendation and consent are unchanged. + ## Opening audit (mechanical) -From `INDEX.md` plus a directory listing, surface the worklist: +From `MEMORY.md`, a directory listing, and each entry's frontmatter and H1, +surface the worklist: -- dangling `INDEX.md` links (a line pointing at a missing file); -- body files with no `INDEX.md` line; +- dangling `MEMORY.md` links (a line pointing at a missing file); +- body files with no `MEMORY.md` line; - empty or stub body files (a lifecycle violation); +- index lines whose text after the dash differs from the entry's + `description` — the entry wins, so the fix is to confirm the entry and + re-project the line, never to edit the line alone; +- index lines whose link text differs from the entry's H1 — same authority, + same fix; +- frontmatter keys this plugin does not define: report them and move on, + never remove them; - `idea-*` files whose `status` is `spec'd` or `dropped` but that still sit as live bodies; -- **sweep**: any closed line still in `INDEX.md` (legacy, or a botched close) +- index lines sitting in the wrong section (a section-blind writer — + Auto-memory in a Hybrid store — appended them): move the line to its + part's proper section; the entry body is untouched; +- **sweep**: any closed line still in `MEMORY.md` (legacy, or a botched close) moves to `ARCHIVE.md`. Under the invariant this should be empty; the sweep is the safety net. +Entries written before these fields existed — or written by Auto-memory, +which knows nothing of them — carry **format debt, not defects**: an entry +with no `description`, or with no H1 to project a title from, is +incomplete, not broken. Title drift on such an entry is part of the +same debt — its index line predates the projection, so a link text disagreeing +with the H1 of an entry that has no `description` is re-projected when the +debt is paid, not reported as drift. Count the debt and list it apart from the +defects above — a dangling link is a fault in the store, a missing +`description` is work the store has not had yet — and offer to fill it in +during the walk. + ## Entry walk (ordered, existential first) One entry at a time, a recommendation with each: @@ -41,8 +71,9 @@ One entry at a time, a recommendation with each: 3. **Is it the right size and shape?** split an overgrown note, trim it, merge duplicates; a note that has grown into a mini-spec is a candidate for a real spec, not a longer note. -4. **Does the `INDEX.md` line summarize it well?** Recall depends on the - one-liner — the cheapest tidiness there is. +4. **Is the `description` right?** Recall depends on it. The `MEMORY.md` line + is only its projection, so judge the `description` itself and let the line + follow. ## adr-candidate flags diff --git a/plugins/project-memory/skills/migrate-memory/SKILL.md b/plugins/project-memory/skills/migrate-memory/SKILL.md index c8b5c93..58b5529 100644 --- a/plugins/project-memory/skills/migrate-memory/SKILL.md +++ b/plugins/project-memory/skills/migrate-memory/SKILL.md @@ -17,11 +17,25 @@ trigger. ## Scan -Read home-dir memory (`MEMORY.md` plus its entry files). Candidates are facts +First check for identity: when the Auto-memory redirect is active in this +environment (`autoMemoryDirectory` in `.claude/settings.local.json` equals +the realpath of `/.claude/memory`), home-dir → Private is a no-op +— the two directories are one. Say so and skip the Private-memory +candidates; Team-memory moves are unaffected. Each environment keeps its +own Home-dir store, so run the migration where the notes live. + +Read home-dir memory — the Home-dir store's `MEMORY.md` and entry files, +under `~/.claude/projects//memory/` by default. Candidates are facts **project-scoped to the current repo**, judged by content. Cross-project and personal facts stay in home-dir memory — do not propose them. When a fact's scope is unclear, ask; never guess. +A `type:` of `user` or `feedback` — nested under `metadata:`, where the +harness writes it — hints that the fact may be personal rather than scoped to +this repository, so consider those entries last. The hint never decides: a +per-user fact ABOUT this repo belongs in Private memory, and content stays the +criterion. + ## No store yet → offer adoption If the project has no store, running this skill IS the signal of intent the @@ -36,11 +50,17 @@ Team memory, per-user → Private memory. The developer decides each. ## Move mechanics -- Translate shape: a home-dir entry (`name`/`description`/`metadata` - frontmatter) becomes a **note** or an `idea-.md` per the conventions - rule — shape is the criterion, the `idea-` prefix authoritative. -- Add the entry's line to the target part's `INDEX.md`. -- Delete the home-dir body file and its `MEMORY.md` line. +- Carry the shape across: a home-dir entry becomes a **note** or an + `idea-.md` per the conventions rule — shape is the criterion, the + `idea-` prefix authoritative. `description` moves over unchanged. `name` + and `metadata` do not survive as fields, but `name` carries the entry's + only title, so it becomes the new entry's H1 and its slug the filename — + dropped as a field, kept as information. Add the plugin's own fields per + the conventions rule. +- Project the entry's line into the target part's `MEMORY.md` — link text + from its H1, summary from its `description`, per the conventions rule. +- Delete the home-dir body file and its line in the Home-dir store's + `MEMORY.md`. - `ticket` frontmatter on a migrated idea only when the project keeps that convention (e.g. via working-process). @@ -51,9 +71,13 @@ wins: offer to merge into the project entry and remove the home-dir copy. ## Migration trace -After moving one or more facts, leave a single roll-up line in home-dir -`MEMORY.md` — "project-scoped notes for migrated to its Project -memory, " — not a per-entry pointer. +After moving one or more facts, leave a single roll-up line in the Home-dir store's +`MEMORY.md` — "project-scoped notes for migrated to its Project memory, " +— not a per-entry pointer. + +The harness caps what it loads from that index — 200 lines / 25 KB, the +budget convention the conventions rule owns. Keep the trace to one line; +on a cap error shorten the index, never retry the write. ## Boundaries diff --git a/plugins/project-memory/skills/redirect-memory/SKILL.md b/plugins/project-memory/skills/redirect-memory/SKILL.md new file mode 100644 index 0000000..903b893 --- /dev/null +++ b/plugins/project-memory/skills/redirect-memory/SKILL.md @@ -0,0 +1,69 @@ +--- +name: redirect-memory +description: "Enable, disable, or inspect the Auto-memory redirect that makes this project's Private memory a Hybrid store (autoMemoryDirectory in .claude/settings.local.json). Use ONLY when the developer explicitly asks to point Claude Code's own memory at the project store or back (\"enable the hybrid store\", \"redirect memory to the project\", \"włącz hybrydę\"). Not a routine memory read or write." +--- + +# redirect-memory + +Points Auto-memory at this project's Private memory store — or points it +back. One key, one realpath: the skill writes `autoMemoryDirectory` into +`.claude/settings.local.json` and nothing else. Explicit-ask only; never +commits; never touches store content. + +## Status (always first) + +Read `.claude/settings.local.json` (when present) and report: + +- no `autoMemoryDirectory` key in ANY honored settings file → redirect + off; sessions here use their default Home-dir store. Before saying + "off", also check `.claude/settings.json` (committed — honored despite + its own schema note) and the user-level settings, and name the file + that carries the key when one does; +- key equal to `realpath(/.claude/memory)` → active in THIS + environment; +- key differing → written from another environment (or the checkout + moved): name both paths and which filesystem view each resolves in. + +## Enable + +1. Compute `realpath(/.claude/memory)` — never accept a typed + path. The store need not exist yet: asking for the redirect is the + intent signal the core rule's adoption clause waits for, so offer to + create Private memory (always ignored, never asked tracked/ignored). +2. Warn before writing — always. The settings file is per-checkout; the + path is per-environment ("this path resolves only where this + filesystem view exists"). A session on the losing side gets Home-dir + memory only without trust or key; otherwise it manufactures a stray + store at the foreign path, or runs with no working Auto-memory at + all. A path-preserving bind mount (`-v "$PWD:$PWD"`-style) activates + both sides at once — two writers on one index. Environment detection + (`/.dockerenv`, `REMOTE_CONTAINERS` in env) only enriches this + wording ("you are writing the container-side path"); never refuse. +3. Write the key preserving every other key in the file — the Edit tool + when the file exists, the Write tool when it does not. +4. Offer a one-time `migrate-memory` pass (when that skill is available) + for THIS environment's Home-dir store — each environment keeps its + own, so migration runs where the notes live. +5. Verification, stated to the developer: settings are read at session + start, so the redirect takes effect in the NEXT session. Start one, + write a note, confirm the file lands in `.claude/memory/` — every + failure mode of the redirect is silent by platform design. + +## Disable + +Name the asymmetry, then let the developer choose: + +- **Remove the key** (the default): environment-neutral — every + environment returns to its own default Home-dir store. +- **`autoMemoryEnabled: false`**: switches Auto-memory off in EVERY + environment that trusts this checkout — the bigger hammer, written + only on an explicit request for it. + +Either way the store is untouched and the rule-driven loading continues. + +## Boundaries + +Writes exactly one settings file, never a committable one — the path has +no portable form. Never commits. Never creates or edits store content: +store creation is the core rule's offer, moving notes is +migrate-memory's, grooming is memory-review-session's. diff --git a/plugins/python-standards/.claude-plugin/plugin.json b/plugins/python-standards/.claude-plugin/plugin.json index 1a326b5..915f0eb 100644 --- a/plugins/python-standards/.claude-plugin/plugin.json +++ b/plugins/python-standards/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "python-standards", "description": "Python coding standards for the uv + ruff + pytest + pyright toolchain: six area skills, a code-review stack (python-code-review skill, python-code-reviewer agent, /python-review command) writing review reports to docs/code-review/, a python-plan-review checklist for plan reviews, and a python-toolchain rule shipped as a Rules payload", - "version": "0.3.0", + "version": "0.3.1", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["python", "standards", "uv", "ruff", "pytest", "pyright", "typer", "fastapi", "review"] diff --git a/plugins/python-standards/skills/python-code-review/SKILL.md b/plugins/python-standards/skills/python-code-review/SKILL.md index 1a597b4..9ff1147 100644 --- a/plugins/python-standards/skills/python-code-review/SKILL.md +++ b/plugins/python-standards/skills/python-code-review/SKILL.md @@ -73,10 +73,11 @@ them in the report's Summary as out of scope. 5. List `rule: none` findings in the reply as candidate standards gaps (one line each: violation class, proposed rule id, graded severity), then follow the review-reports contract's Candidate-gap offers - section: offer a Project-memory park when a store exists (probe - `docs/memory/INDEX.md` and `.claude/memory/INDEX.md`; explicit - guidance on store choice wins, both-stores means ask, never offer to - create one) and always offer a generalized upstream report (target + section: offer a Project-memory park when a store exists + (store probe: the `docs/memory/` and `.claude/memory/` directories, + never a file inside them; explicit guidance on store choice wins, + both parts means ask, never offer to create one) and always offer + a generalized upstream report (target resolved from the installed marketplace's source; non-public source → target-less draft; show the full draft before anything is filed; never include the reviewed project's code, identifiers, or name). diff --git a/plugins/salesforce-standards/.claude-plugin/plugin.json b/plugins/salesforce-standards/.claude-plugin/plugin.json index 9ac7102..c7b394b 100644 --- a/plugins/salesforce-standards/.claude-plugin/plugin.json +++ b/plugins/salesforce-standards/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "salesforce-standards", "description": "Salesforce coding standards for the sf CLI + Code Analyzer + Prettier-Apex + sfdx-lwc-jest toolchain: eight area skills (Apex, Apex testing, LWC, Flow, data model, security model, maintenance-first Aura and Visualforce), a code-review stack (salesforce-code-review skill, salesforce-code-reviewer agent, /salesforce-review command) writing review reports to docs/code-review/, a salesforce-plan-review checklist for plan reviews, and a salesforce-toolchain rule shipped as a Rules payload", - "version": "0.3.0", + "version": "0.3.1", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["salesforce", "standards", "apex", "lwc", "flow", "aura", "visualforce", "sf-cli", "review"] diff --git a/plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md b/plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md index d7a3da6..8d410b5 100644 --- a/plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md +++ b/plugins/salesforce-standards/skills/salesforce-code-review/SKILL.md @@ -106,10 +106,11 @@ provision. 5. List `rule: none` findings in the reply as candidate standards gaps (one line each: violation class, proposed rule id, graded severity), then follow the review-reports contract's Candidate-gap offers - section: offer a Project-memory park when a store exists (probe - `docs/memory/INDEX.md` and `.claude/memory/INDEX.md`; explicit - guidance on store choice wins, both-stores means ask, never offer to - create one) and always offer a generalized upstream report (target + section: offer a Project-memory park when a store exists + (store probe: the `docs/memory/` and `.claude/memory/` directories, + never a file inside them; explicit guidance on store choice wins, + both parts means ask, never offer to create one) and always offer + a generalized upstream report (target resolved from the installed marketplace's source; non-public source → target-less draft; show the full draft before anything is filed; never include the reviewed project's code, identifiers, or name). diff --git a/plugins/working-process/.claude-plugin/plugin.json b/plugins/working-process/.claude-plugin/plugin.json index 71c69bc..b439e40 100644 --- a/plugins/working-process/.claude-plugin/plugin.json +++ b/plugins/working-process/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "working-process", "description": "Spec-driven working process on top of superpowers: grilling-session, architect-session, system-designer-session and sync-rules skills, architect and plan-adversary review agents, architect-consult and system-designer-consult consultation agents, and process rules distributed as a Rules payload; domain plugins hook in via *-plan-review checklist skills and their own rules/ payloads", - "version": "0.12.0", + "version": "0.13.0", "author": { "name": "Missing Bits (Jacek Nakonieczny)" }, "license": "MIT", "keywords": ["process", "workflow", "spec", "plan", "review", "glossary", "adr", "architecture"], diff --git a/plugins/working-process/README.md b/plugins/working-process/README.md index 425ac65..5c137ae 100644 --- a/plugins/working-process/README.md +++ b/plugins/working-process/README.md @@ -61,6 +61,13 @@ language from its first message. rules loading; the skills and agents alone work on ≥ 2.1.143). - The `superpowers` plugin — declared as a dependency and installed automatically alongside this plugin. +- Optional companion: the `elements-of-style` plugin. When its + `writing-clearly-and-concisely` skill is present, the process rules + route prose artifacts under `docs/` through it; without it nothing + changes. Not a dependency — install it yourself: + + /plugin marketplace add obra/superpowers-marketplace + /plugin install elements-of-style@superpowers-marketplace ## Extending with a domain checklist diff --git a/plugins/working-process/rules/process-artifacts.md b/plugins/working-process/rules/process-artifacts.md index cdd1068..420822e 100644 --- a/plugins/working-process/rules/process-artifacts.md +++ b/plugins/working-process/rules/process-artifacts.md @@ -64,7 +64,8 @@ conventions below. ledgers) carry a `ticket` frontmatter field; registry files that live across tickets (the domain glossary, `.gitignore` files, and — when the project-memory plugin's rules are installed — Project memory notes and - `INDEX.md`) are exempt. Project-memory idea entries DO carry `ticket` + the store's registry files, whose list that plugin's conventions rule + owns) are exempt. Project-memory idea entries DO carry `ticket` and are not exempt. Team memory (`docs/memory/`) is a Process directory; Private memory (`.claude/memory/`) is not — it is the per-user store defined by the project-memory plugin's rules. diff --git a/plugins/working-process/rules/review-reports.md b/plugins/working-process/rules/review-reports.md index 29b2776..8c6e21a 100644 --- a/plugins/working-process/rules/review-reports.md +++ b/plugins/working-process/rules/review-reports.md @@ -195,12 +195,13 @@ violation class, proposed rule id, graded severity (a plugin-level and then offers, never performs unprompted: - **Project-memory park** — only when the reviewed project keeps a - Project-memory store (probe `docs/memory/INDEX.md` and - `.claude/memory/INDEX.md`). The write is done by whoever accepts, + Project-memory store (store probe: one existence test per part, + on `docs/memory/` and `.claude/memory/` — the directory, + never a file inside it). The write is done by whoever accepts, never by the review run. Store selection: explicit guidance wins (project CLAUDE.md, the developer's own instructions, the store's - conventions); otherwise with both stores present the offer asks the - developer which one; with one store it names that one. No store — no + conventions); otherwise with both parts present the offer asks the + developer which one; with one part it names that one. No store — no offer, and never an offer to create a store. - **Upstream report** — always offered: a report to the standards plugin's source repository, resolved at offer time from the installed diff --git a/plugins/working-process/rules/spec-plan-lifecycle.md b/plugins/working-process/rules/spec-plan-lifecycle.md index 1b739ab..c982e9f 100644 --- a/plugins/working-process/rules/spec-plan-lifecycle.md +++ b/plugins/working-process/rules/spec-plan-lifecycle.md @@ -70,13 +70,22 @@ only when the tool is available: grill a fresh spec (grilling-session); architect-review a grilled spec (architect agent dispatch); adversary-review a plan before implementation (plan-adversary agent dispatch); offer the pending re-review of a fallback-recorded verdict at -its consumption gate (fresh round at the prescribed tier). After any +its consumption gate (fresh round at the prescribed tier); and when a +spec or plan moves to `implemented` and the memory-review-session skill +is available, offer a Project memory review — released work-state notes +close, resolved entries sweep to the archive. After any review round, stamp the verdict into the document's field. -When implementation is about to start, suggest committing the work's -documents under `docs/` — only paths git tracks or would track; -deliberately ignored documents are skipped silently, and committing -itself stays with the developer. +The process suggests committing the work's documents under `docs/` at +exactly one point — the implementation-ready gate: the developer has +approved the plan (the `status` flip to `approved`) and implementation +is about to start. During authoring — spec drafting, grilling, review +rounds, plan writing — it never makes that suggestion; the documents' +uncommitted state is deliberate, not dirt in the process-artifacts +sense, and the developer may commit sooner on their own call. The +suggestion covers only paths git tracks or would track; deliberately +ignored documents are skipped silently, and committing itself stays +with the developer. Ticket value format, sourcing order, and backfill live in the ticket-frontmatter rule. diff --git a/plugins/working-process/rules/ticket-frontmatter.md b/plugins/working-process/rules/ticket-frontmatter.md index 8d79d94..506ebc4 100644 --- a/plugins/working-process/rules/ticket-frontmatter.md +++ b/plugins/working-process/rules/ticket-frontmatter.md @@ -20,9 +20,10 @@ GitLab, or anything else; the field is always `ticket`. `ticket`-only set of `.superpowers/**` artifacts. - Every other document under `docs/`: `ticket` + `date`. - Project memory (`docs/memory/`, `.claude/memory/` — when the - project-memory plugin's rules are installed): notes and `INDEX.md` - are `ticket`-exempt registry files (like the glossary); idea entries - (`idea-*.md`) carry `ticket`. See that plugin's + project-memory plugin's rules are installed): notes and the store's + registry files are `ticket`-exempt (like the glossary; the + registry-file list belongs to that plugin's conventions rule); idea + entries (`idea-*.md`) carry `ticket`. See that plugin's project-memory-conventions rule. - Per-work process artifacts (`.superpowers/**`, ADRs): `ticket` only — see the process-artifacts rule. diff --git a/plugins/working-process/rules/workflow.md b/plugins/working-process/rules/workflow.md index 42ba13b..e2f5bd8 100644 --- a/plugins/working-process/rules/workflow.md +++ b/plugins/working-process/rules/workflow.md @@ -72,3 +72,10 @@ the transcript stays inspectable. When `docs/domain/glossary.md` exists in the project, its canonical terms and `_Avoid_` bans bind specs, plans, code identifiers, and reviews. + +When the `elements-of-style:writing-clearly-and-concisely` skill is +available, prose artifacts under `docs/` — specs, plans, ADRs, the +glossary — get its pass: invoke it before drafting a new document, and +run an explicit editing pass over the changed prose of an existing one. +The pass binds wording, never decisions. Without the skill there is no +substitute pass and no install nagging — the work proceeds normally.