Skip to content

fix(mem): own the Claude project-key encoding in Go so the Windows twins can't drift - #739

Merged
mlorentedev merged 3 commits into
mainfrom
fix/windows-twin-memory-parity
Jul 14, 2026
Merged

fix(mem): own the Claude project-key encoding in Go so the Windows twins can't drift#739
mlorentedev merged 3 commits into
mainfrom
fix/windows-twin-memory-parity

Conversation

@mlorentedev

@mlorentedev mlorentedev commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Why

The Claude auto-memory junction key encoding drifted between the Go layer and its Windows PowerShell twins. memlink.ClaudeProjectKey (the #551 fix) maps /, \ and the drive : each to - (so C:\Users\me\p -> C--Users-me-p), but two PowerShell sites still mapped only \ and deleted the : (producing C-Users-me-p) -- a key Claude never reads:

  • setup-windows.ps1 built junctions at the wrong path and printed a misleading Linked auto-memory.
  • knowledge-crystallize.ps1 resolved a nonexistent path and warned No MEMORY.md found for every project.

Separately, vault-maintenance-weekly.ps1 invoked the crystallizer with the POSIX --all, which binds positionally to $ProjectDir and leaves [switch]$All false -- so the Sunday scheduled task has never processed a project.

Root cause of the encoding half: one datum duplicated across Go + 2 PowerShell sites, which drifted when #551 fixed only Go. Per ADR-020 (Go owns tooling logic) + SSOT, Go now owns the encoding and the twins call it.

What

  • dotf mem project-key <path> exposes memlink.ClaudeProjectKey as the single encoding source (TestMemProjectKey).
  • scripts/utils.ps1: Get-ClaudeProjectKey (dotf-first, corrected fallback) + Get-ClaudeProjectKeyEncoded (pure, for the decoder's hot scan -- no per-directory subprocess).
  • setup-windows.ps1 + knowledge-crystallize.ps1 call the helper; deleted the buggy local Get-EncodedPath; fixed Get-DecodedPath to the double-dash key (^([A-Za-z])--).
  • vault-maintenance-weekly.ps1: --all -> -All.
  • Anti-drift guard (tests/claude-project-key.Tests.ps1, 10/10): binds the PS encoder to Go's known-good keys; the dotf cross-check probes support at discovery time and skips (not fails) against a released dotf that predates the subcommand (CI: integration container tests the released dotf, not the PR's built binary #734).

Verification

  • go build/vet/test ./... green; TestMemProjectKey passes.
  • Pester on Windows: 10/10 (dotf cross-check green against the PR-built binary).
  • PSScriptAnalyzer clean on changed files; all changed .ps1 ASCII-only.

Notes / follow-ups (ticketed, not inlined)

Spec: specs/BUG-031-windows-project-key/.

Closes #689.

Summary by CodeRabbit

  • New Features

    • Added dotf mem project-key <path> to generate Claude project memory keys consistently.
    • Centralized project-key resolution for Windows scripts, with a fallback when the CLI is unavailable.
  • Bug Fixes

    • Corrected Windows project-key encoding and memory junction destinations.
    • Fixed weekly maintenance to process all projects correctly.
  • Tests

    • Added coverage validating PowerShell and CLI project keys match expected values.

…ins can't drift

Two PowerShell sites re-implemented the auto-memory junction key and deleted the
drive ':' instead of mapping it to '-', producing C-Users-... where Claude Code
and memlink.ClaudeProjectKey use C--Users-.... setup-windows.ps1 deposited
junctions Claude never reads; knowledge-crystallize.ps1 warned "No MEMORY.md
found" for every project. vault-maintenance-weekly.ps1 also passed the POSIX
--all, which bound positionally to $ProjectDir and left [switch]$All false, so the
weekly task never fanned out.

- add `dotf mem project-key <path>` exposing memlink.ClaudeProjectKey as the one
  encoding source
- setup-windows.ps1 + knowledge-crystallize.ps1 resolve the key via a shared
  utils.ps1 helper (dotf-first, corrected pure fallback); fix the decoder regex
- vault-maintenance-weekly.ps1: --all -> -All
- Pester guard binds the PS encoder to Go's known-good keys

Closes #689
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mlorentedev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b74f3359-f67d-4740-8d86-f9ac4806e0ea

📥 Commits

Reviewing files that changed from the base of the PR and between 766e5d9 and 16e76fb.

📒 Files selected for processing (2)
  • docs/lessons.md
  • tests/knowledge-crystallize-ps1.bats
📝 Walkthrough

Walkthrough

Adds dotf mem project-key, shared PowerShell project-key helpers, aligned Windows memory-path handling, a corrected -All maintenance invocation, Pester coverage, and BUG-031 specification and verification records.

Changes

Windows project-key alignment

Layer / File(s) Summary
Go project-key CLI contract
cli/internal/cmd/mem.go, cli/internal/cmd/mem_test.go, cli/.gitignore
Registers mem project-key <path>, prints the memlink-derived key, tests path formats, and ignores local CLI build output.
PowerShell key resolution and validation
scripts/utils.ps1, tests/claude-project-key.Tests.ps1
Adds pure and dotf-backed key helpers with fallback behavior, plus Pester comparisons against canonical and CLI-generated keys.
Memory workflow integration
scripts/knowledge-crystallize.ps1, setup-windows.ps1, scripts/vault-maintenance-weekly.ps1
Uses shared key resolution for memory paths and junctions, updates decoding for double-dash keys, and invokes crystallization with -All.
BUG-031 specification and verification
specs/BUG-031-windows-project-key/*
Documents the implementation tasks, acceptance criteria, test evidence, implementation decisions, and closure checklist.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The cli/.gitignore bin/ ignore rule is unrelated to #689 and appears to be an extra hygiene change outside the issue scope. Move the .gitignore tweak to a separate PR unless it is required for the linked fix.
Description check ⚠️ Warning The description is informative, but it does not follow the repository template and omits the required Summary, SDD checklist, and Test plan sections. Rewrite it to match the template: add Summary bullets, complete the SDD checklist or skip rationale, and include a Test plan with manual verification.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses #689 by fixing both Windows encoding sites, switching the weekly invocation to -All, and adding regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main change: moving Claude project-key encoding ownership to Go to prevent Windows drift.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-twin-memory-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
cli/internal/cmd/mem_test.go (1)

61-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Refactor to use a standard table-driven test with a slice of structs.

Using a map for test cases introduces non-deterministic execution order, and running them in the same scope makes it harder to isolate failures. Consider using a slice of structs with t.Run for better readability and standard Go testing practices.

♻️ Proposed refactor
 func TestMemProjectKey(t *testing.T) {
-	for in, want := range map[string]string{
-		`C:\Users\me\p`: "C--Users-me-p",
-		"/home/me/p":    "-home-me-p",
-	} {
-		cmd := newMemCmd()
-		var out bytes.Buffer
-		cmd.SetArgs([]string{"project-key", in})
-		cmd.SetOut(&out)
-		cmd.SetErr(io.Discard)
-		if err := cmd.Execute(); err != nil {
-			t.Fatalf("project-key %q: unexpected error %v", in, err)
-		}
-		if got := strings.TrimSpace(out.String()); got != want {
-			t.Errorf("project-key %q = %q, want %q", in, got, want)
-		}
-	}
+	tests := []struct {
+		name string
+		in   string
+		want string
+	}{
+		{"windows_path", `C:\Users\me\p`, "C--Users-me-p"},
+		{"unix_path", "/home/me/p", "-home-me-p"},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			cmd := newMemCmd()
+			var out bytes.Buffer
+			cmd.SetArgs([]string{"project-key", tc.in})
+			cmd.SetOut(&out)
+			cmd.SetErr(io.Discard)
+			if err := cmd.Execute(); err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if got := strings.TrimSpace(out.String()); got != tc.want {
+				t.Errorf("got %q, want %q", got, tc.want)
+			}
+		})
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/internal/cmd/mem_test.go` around lines 61 - 78, Refactor
TestMemProjectKey to define cases as a slice of structs containing input and
expected output, then iterate with t.Run using a descriptive case name. Create
and configure a fresh command and output buffer inside each subtest so failures
remain isolated while preserving the existing assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specs/BUG-031-windows-project-key/proposal.md`:
- Around line 49-56: The fallback encoding described in the proposal omits the
slash replacement used by ClaudeProjectKey. Update the fallback example to
replace /, \, and : consistently, or reference Get-ClaudeProjectKeyEncoded as
the single source of truth; keep the dotf-based path unchanged.

In `@specs/BUG-031-windows-project-key/tasks.md`:
- Around line 40-46: Resolve the contradiction in the “Machine-readable
features” section: either add the omitted features.json artifact or update the
“See sibling features.json” reference to its actual canonical location. Keep the
surrounding statement consistent with the chosen artifact location and omission
policy.

In `@specs/BUG-031-windows-project-key/verification.md`:
- Around line 26-39: Update the PSScriptAnalyzer scope statement in the
verification checklist to match the five files listed in the test status, or
explicitly distinguish the two CI-linted files from the three additionally
analyzed files. Ensure the verification record is unambiguous without changing
the reported results.

---

Nitpick comments:
In `@cli/internal/cmd/mem_test.go`:
- Around line 61-78: Refactor TestMemProjectKey to define cases as a slice of
structs containing input and expected output, then iterate with t.Run using a
descriptive case name. Create and configure a fresh command and output buffer
inside each subtest so failures remain isolated while preserving the existing
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba5666a6-78e1-45ce-bd5d-811a8e788772

📥 Commits

Reviewing files that changed from the base of the PR and between 1d4e9ea and 766e5d9.

📒 Files selected for processing (11)
  • cli/.gitignore
  • cli/internal/cmd/mem.go
  • cli/internal/cmd/mem_test.go
  • scripts/knowledge-crystallize.ps1
  • scripts/utils.ps1
  • scripts/vault-maintenance-weekly.ps1
  • setup-windows.ps1
  • specs/BUG-031-windows-project-key/proposal.md
  • specs/BUG-031-windows-project-key/tasks.md
  • specs/BUG-031-windows-project-key/verification.md
  • tests/claude-project-key.Tests.ps1

Comment on lines +49 to +56
- **Go owns the datum.** New `dotf mem project-key <path>` subcommand prints
`memlink.ClaudeProjectKey(path)`. The encoding now has one implementation.
- **PowerShell calls Go, with a corrected fallback.** `setup-windows.ps1` and
`knowledge-crystallize.ps1` obtain the key via `dotf mem project-key`, guarded on
`dotf` being on PATH (the existing `if (Get-Command dotf ...)` pattern at
`setup-windows.ps1:605/619/645`). When `dotf` is absent the inline fallback uses
the **corrected** encoding `.Replace('\','-').Replace(':','-')` (matches Go), so
bootstrap never hard-depends on the CLI yet never re-emits the buggy key.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== proposal excerpt ==\n'
sed -n '40,70p' specs/BUG-031-windows-project-key/proposal.md

printf '\n== find ClaudeProjectKey ==\n'
rg -n "ClaudeProjectKey|project-key|Replace\\(" -S .

Repository: mlorentedev/dotfiles

Length of output: 18247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the encoder and the Windows scripts mentioned in the proposal.
fd -a "ClaudeProjectKey" .
fd -a "setup-windows.ps1" .
fd -a "knowledge-crystallize.ps1" .
fd -a "proposal.md" specs/BUG-031-windows-project-key

printf '\n== encoder references ==\n'
rg -n "ClaudeProjectKey|Replace\\('/'|Replace\\('\\\\'|Replace\\(':'" -S . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

printf '\n== proposal lines ==\n'
sed -n '45,60p' specs/BUG-031-windows-project-key/proposal.md

Repository: mlorentedev/dotfiles

Length of output: 10442


Make the fallback example include / too. ClaudeProjectKey replaces /, \, and :; the proposal’s fallback snippet only shows \ and :. Either add the / replacement or point this line at Get-ClaudeProjectKeyEncoded so it matches the single source of truth.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~54-~54: Possible missing comma found.
Context: ...ndows.ps1:605/619/645). When dotf` is absent the inline fallback uses the **correc...

(AI_HYDRA_LEO_MISSING_COMMA)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/BUG-031-windows-project-key/proposal.md` around lines 49 - 56, The
fallback encoding described in the proposal omits the slash replacement used by
ClaudeProjectKey. Update the fallback example to replace /, \, and :
consistently, or reference Get-ClaudeProjectKeyEncoded as the single source of
truth; keep the dotf-based path unchanged.

Comment on lines +40 to +46
> `features.json` omitted, matching the current precedent (BUG-029, BUG-030 ship
> proposal/tasks/verification only). Acceptance criteria map to Go/Pester tests
> named in `verification.md`.

## Machine-readable features

See sibling `features.json`. Pass-state is harness-gated (agent may not set `passing`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove or correct the stale features.json reference.

This section says features.json is intentionally omitted, then instructs readers to “See sibling features.json.” Either include the artifact or replace the reference with the canonical location; otherwise the spec is internally contradictory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/BUG-031-windows-project-key/tasks.md` around lines 40 - 46, Resolve the
contradiction in the “Machine-readable features” section: either add the omitted
features.json artifact or update the “See sibling features.json” reference to
its actual canonical location. Keep the surrounding statement consistent with
the chosen artifact location and omission policy.

Comment on lines +26 to +39
- [x] `go build`/`vet`/`test ./...` clean; PSScriptAnalyzer clean on the two
CI-linted changed files; every changed `.ps1` ASCII-only.

## Test status

- Go: `go build ./... && go vet ./... && go test ./...` -> all packages `ok`
(cli/internal/cmd, memlink, doctor, env, mem, ... green). `TestMemProjectKey`
passes.
- Pester (Windows, this box): `Invoke-Pester tests/claude-project-key.Tests.ps1`
-> `Tests Passed: 10, Failed: 0`. The dotf cross-check ran green against a
PR-built `dotf` on PATH.
- PSScriptAnalyzer (repo settings, Error+Warning): PASS on `setup-windows.ps1`,
`scripts/knowledge-crystallize.ps1`, `scripts/utils.ps1`,
`scripts/vault-maintenance-weekly.ps1`, `tests/claude-project-key.Tests.ps1`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the PSScriptAnalyzer scope consistent.

Lines 26-27 claim only two CI-linted changed files, but lines 37-39 list five analyzed files. Replace “two” with the actual scope or explain the distinction so the verification record is unambiguous.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/BUG-031-windows-project-key/verification.md` around lines 26 - 39,
Update the PSScriptAnalyzer scope statement in the verification checklist to
match the five files listed in the test status, or explicitly distinguish the
two CI-linted files from the three additionally analyzed files. Ensure the
verification record is unambiguous without changing the reported results.

…oding

Two characterization tests in knowledge-crystallize-ps1.bats asserted the buggy
behavior this PR removes: one required a local `Get-EncodedPath` function (deleted
in favor of the shared utils.ps1 helper), the other grepped for the colon-deleting
`Replace ':' ''` pattern that produced the wrong single-dash key. They locked the
#689 bug in place, so the fix flipped them red.

Re-point both at the corrected reality: the script sources utils.ps1 and resolves
the key via Get-ClaudeProjectKey (no local encoder), and the drive colon is mapped
to '-' (double-dash key), never deleted.
BUG-031's correct fix flipped two bats characterization tests red because they
asserted the old buggy behavior (a Get-EncodedPath function + the colon-deleting
Replace pattern). A *.ps1-only grep missed them. Record the rule: grep every test
extension for a removed symbol/pattern, and invert any test that fingerprints a
bug when you kill it.
@mlorentedev
mlorentedev merged commit c4f1a7c into main Jul 14, 2026
13 checks passed
@mlorentedev
mlorentedev deleted the fix/windows-twin-memory-parity branch July 14, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Windows twins use the wrong Claude project-key encoding + broken --all binding

1 participant