fix(claude-memory): resolve memory_dir from topic-docs seam in orphan-rule-check#470
Conversation
…-rule-check
orphan-rule-check hardcoded the literal `.work/` exclusion instead of reading
`memory_dir` from `.claude/topic-docs.yaml`. A consumer that overrides memory_dir
had its real memory tier scanned by the reference search, so ephemeral files there
registered false references that could mask an orphan always-loaded rule.
Resolve `memory_dir` from `${repo_root}/.claude/topic-docs.yaml` using the same
sed-extract shape as docs-hygiene's noise-shapes.sh and exclude that path, falling
back to `.work/` only when the seam is unset. Comments and --help updated to match.
Tests add discriminating cases proving the excluded tier MOVES under an override
(the overridden dir is excluded and `.work/` stops being excluded) and that the
`.work/` fallback still holds when the seam is absent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 0s —— View job PR Review: fix(claude-memory): resolve memory_dir from topic-docs seam in orphan-rule-check
ReviewOverall verdict: Approve. The fix is correct, tightly scoped, and well-tested. The implementation faithfully mirrors the fleet precedent. Notes below are observations, not blockers. Core fix —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25689522b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ry_dir parse
The orphan-rule-check memory_dir extract stripped every whitespace
character with ${seam//[[:space:]]/}, collapsing interior spaces in a
valid quoted relative root: memory_dir: ".work artifacts" resolved to
.workartifacts, so the real memory tier was not excluded from the
reference search and a tracked artifact there could still count as a
reference and mask an orphan rule — the regression this change prevents.
Trim only trailing whitespace (${seam%"${seam##*[![:space:]]}"}), the
same minimal fix the sibling retro parse used (fix/420): sed already
removes leading whitespace after the colon and the closing quote shields
significant trailing space before quote-stripping. Interior spaces in
quoted and plain scalars are preserved; unquoted trailing whitespace,
inline comments, and trailing slashes still trim. Adds regression Case 8
(quoted ".scratch dir" tier) that fails under the collapsing strip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 8s —— View job PR Review: fix(claude-memory): resolve memory_dir + preserve interior whitespace in orphan-check
Overall verdict: Approve. The whitespace-strip fix in Core whitespace fix —
|
| Step | Expression | Result |
|---|---|---|
sed |
strips memory_dir:[[:space:]]* |
".scratch dir" |
%%#* |
strips inline comment | ".scratch dir" |
| trailing-trim | strips trailing whitespace | ".scratch dir" |
%/ |
strips trailing slash | ".scratch dir" |
#\" / %\" |
strips outer quotes | .scratch dir |
Result: memory_dir=".scratch dir" → pathspec :(exclude).scratch dir/ — correct.
Under the old collapsing strip step 2 would produce ".scratchdir" → pathspec :(exclude).scratchdir/ → real tier .scratch dir/ included → false reference masks orphan. ✓ fix prevents that.
Case 8 regression guard — orphan-rule-check.test.sh lines 127–145
The fixture is correctly constructed:
mkdir -p "$WS/.scratch dir"— creates a tracked directory whose name contains an interior space.printf 'see d.md\n' >"$WS/.scratch dir/refD.md"— places the only reference tod.mdinside that tier.assert_contains "... d.md orphan" "$OUT" "d.md"— under the OLD code the strip collapses the path, the tier is not excluded, the reference is found, andd.mdis not reported as an orphan → assertion fails. Under the fix the tier is excluded andd.mdis an orphan → assertion passes.
The test correctly fails under the bug and passes under the fix. Well-constructed.
Note: the test relies on git add -A tracking a path with an interior space inside $TEST_TMPDIR. This is standard git behaviour; the fixture is fine.
Minor observations (non-blocking, carry-over from first review)
1. CHANGELOG 0.2.1 entry is silent on the whitespace-preservation change.
The entry accurately describes the seam-resolution fix but doesn't mention that e4bf13c also corrected the interior-whitespace collapse. Since these are squash-merged as one version, a second bullet (e.g. "Memory tier paths containing interior whitespace are now preserved correctly") would improve completeness, but is not required.
2. memory_dir not escaped for git pathspec magic characters (carry-over observation 1).
Still present; still very low risk given the constraint that memory_dir must be a valid directory path. The noise-shapes.sh precedent escapes for regex context — the pathspec context is different and the exposure is negligible for realistic values. Acceptable as-is.
3. grep -vcxF -- "$file" relies on relative-path parity (carry-over observation 3).
Still present; still correct given both operands are relative to the same $repo_root CWD. Worth noting if this idiom is ever factored into a shared lib.
No issues requiring changes before merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4bf13c18e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Re: claude[bot] review pass (run 29684331427) — verdict: Approve, no blockers. Three non-blocking observations, each self-dispositioned by the reviewer; classified below.
|
…ia shared helper (#756) ## Summary The topic-docs `memory_dir` seam was parsed inline at two sites with a naive `${val%%#*}`-first strip — the same naive-string-op class the #469/#470 whitespace fixes closed, left as a review-deferred residual. It truncated a legitimately quoted value containing `#` (`memory_dir: "a#b"` → `a`) because it stripped a "comment" before resolving surrounding quotes, and an absent concern file collapsed straight to `.work` without consulting the contract's rung-2 (a save-point convention declared in `CLAUDE.md` / `.claude/rules`). Per the issue's acceptance criteria, both sites now delegate to **one** shared parser rather than re-forking the duplicated block. ## Fix - **`lib/parse-concern-value.sh`** (new) — the single seam parser. Order is quote-resolution **first**, then a quote-aware comment strip (unquoted ` #…` only; `a#b` / `.work/#topic` preserved), then whitespace-trim, then trailing-slash-normalize. Takes `<concern-file> <key> [fallback]`; the `fallback` is the **caller-resolved** rung-2 location (prose is an inference source, not a machine key — the agent resolves it and passes it in). Emits empty only when nothing resolves, so the caller owns the documented `.work` default. - **Both consumers wired through it:** claude-memory `orphan-rule-check.sh` (real script) and the session-flow retro `HANDOFF_DIR` snippet (`retro/context/session.md`). The non-interactive orphan check degrades to `.work` and notes that inferred/interactive resolution is the calling skill's job, rather than silently collapsing rung 2. - **Materialization + drift gate:** copies are synced from `lib/` into each plugin (installed plugins are cache-isolated, so each must be self-contained — the `lib/hook-utils.sh` precedent). New `scripts/sync-parse-concern-value.sh` (`--check` / `--check-bump`) + a `parse-concern-value-sync` CI job keep the copies byte-identical and enforce version bumps when the lib changes. The two copies live at different relpaths, so the generic cross-plugin drift check does not cluster them — a dedicated gate is required. ## Verification All run locally (Git Bash): - `lib/parse-concern-value.test.sh` — **19/19 pass**, incl. regressions: double/single-quoted `a#b` and `.work/#topic` keep `#`; quoted value with trailing comment drops the comment but keeps the inner `#`; absent/empty key returns the supplied fallback; held behavior (unquoted trailing ` # comment` stripped, whitespace trimmed, interior quoted space preserved, trailing slash normalized). - `orphan-rule-check.test.sh` — **14/14 pass**, incl. new Case 9: a ref inside a quoted `".scratch#dir"` tier is correctly excluded (the old `%%#*` truncated to `.scratch`, masking the orphan). - `scripts/sync-parse-concern-value.sh --check` — copies match source. - `scripts/check-cross-plugin-source-drift.sh --check` — no unregistered/drifted clusters. - `shellcheck` + `shfmt -d` — clean on all new/edited scripts. - `scripts/validate-plugins.sh` — all manifests + catalog valid. Closes #482 ## Related Realizes the shared-helper shape #474 called for, scoped to #482's two sites. #474 (wiring the *remaining* consumers to this helper) stays parked and is unblocked by this PR. --------- Co-authored-by: Claude Opus <noreply@anthropic.com>
…o shared parse-concern-value helper (#847) ## Summary Final wiring of the ratified bounded-widen scope on #474: fold the last two verified hand-rolled `memory_dir`/`contract_dir` copies into #482's already-landed `parse-concern-value.sh` helper (merged via #756). - **`plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh`** — `audit_noise_convention_roots_pattern` hand-rolled the same naive `${val%%#*}`-first strip #482 fixed at its two hot sites (truncates a quoted value at an interior `#`), plus a defect unique to this copy: `${val//[[:space:]]/}` collapsed *all* interior whitespace, and there was no quote-stripping. Now resolves both keys through the shared helper, materialized to `plugins/docs-hygiene/skills/audit-noise/scripts/lib/parse-concern-value.sh` (byte-identical + mode-identical, `100755`, to the two existing copies) and registered in `scripts/sync-parse-concern-value.sh`'s `copies` array — the same upstream-SSOT + sync-materialization pattern as the two prior sites (installed plugins are cache-isolated and cannot `source` a sibling's file). - **`plugins/session-flow/skills/handoff/context/structure.md`** L116 — replaced the placeholder note (`# the concern file's memory_dir when set — resolve it first`, no mechanism named) with a pointer to the shared helper via the retro skill's Phase 1.1 as the worked call form. Doc note only: the handoff skill has no script of its own to rewire. Per the operator's ratified bounded-widen decision (2026-07-19T23:26Z) and the tower's 2026-07-21 decision comment on #474, the broader tracker-seam-resolution class (#470/#469/#432/#365/#369) stays explicitly OUT of scope. Version bumps stack past two independently-merged sibling PRs that landed while this one was open: docs-hygiene `0.8.0` → `0.8.2` (stacks past #846's `0.8.1`, an exact version collision resolved by re-numbering this PR's entry on top and preserving #846's CHANGELOG entry underneath); session-flow `0.11.0` → `0.12.1` (stacks past a merged `0.12.0` that added the `orient` skill). ## Related - #482 / #756 — landed the shared `parse-concern-value.sh` helper and its first two consumers; this PR extends it to the remaining two per the ratified bounded-widen scope. - #846 — merged concurrently with this PR and independently bumped `docs-hygiene` to `0.8.1`; this PR's version was restacked to `0.8.2` on top, both CHANGELOG entries preserved. - #470 / #469 / #432 / #365 / #369 — the broader tracker-seam-resolution class; explicitly excluded from this PR's scope per the operator's ratified decision. ## Test plan - [x] `bash plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh` — 34/34 pass, including a new regression case: a quoted `memory_dir: ".scratch#dir/" # trailing comment` now correctly resolves to `.scratch#dir` (flags `.scratch#dir/foo/` as a concrete slice) instead of the old parser's silent truncation. - [x] Manual before/after demonstration of the two defects the old parser had: - Quoted + interior `#` + trailing comment + trailing slash: old `[.scratch]` (wrong — truncated inside the quotes) vs new `[.scratch#dir]` (correct). - Unquoted value with interior whitespace: old `[mydir]` (wrong — collapsed) vs new `[my dir]` (correct, surrounding whitespace trimmed only). - [x] `scripts/sync-parse-concern-value.sh --check` — all 3 plugin copies match `lib/parse-concern-value.sh`. - [x] `scripts/check-cross-plugin-source-drift.sh --check` — no unregistered/drifted clusters (the three copies have distinct within-plugin relpaths, so the generic drift check doesn't need to cluster them; the dedicated sync gate already covers them). - [x] `scripts/check-changelog-parity.sh --check` and `--check-bump origin/main` — both touched plugins (`docs-hygiene` 0.8.0→0.8.2, `session-flow` 0.11.0→0.12.1) carry matching CHANGELOG entries. - [x] `shellcheck` + `shfmt -d` — clean on all new/edited scripts. - [x] `scripts/validate-plugins.sh` — all manifests + catalog valid. Closes #474 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
orphan-rule-check.shhardcoded the literal.work/exclusion in its reference search instead of readingmemory_dirfrom the topic-docs seam (.claude/topic-docs.yaml). A consumer that overridesmemory_dirtherefore had its real memory tier scanned, so ephemeral files there registered false "references" that could mask an orphan always-loaded rule. This couples the check to the convention's default NAME rather than the externalized seam.Fix
memory_dirfrom${repo_root}/.claude/topic-docs.yamlusing the same sed-extract shape as the fleet precedentplugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh:15-38, and exclude THAT path in thegit greppathspec..work/only when the seam is unset..work/references (header comment,--helptext, inline comment) to describe the resolved tier instead of the hardcoded literal..work/stops being excluded — an additive "exclude both" bug fails these) and that the.work/fallback still holds when the seam is absent.Verification
bash plugins/claude-memory/skills/audit/scripts/orphan-rule-check.test.sh— all 12 checks pass (3 new).shellcheck --rcfile=.shellcheckrcon both changed scripts — clean.typos --config _typos.toml+markdownlint-cli2 --config .markdownlint-cli2.jsonconCHANGELOG.md— clean (0 issues).Related
.work-convention sweep: the always-loaded-rule reference search must resolve the in-repo memory tier from the externalized seam, not the default name.docs-hygienenoise-shapes.sh(same sed-extract from.claude/topic-docs.yaml).retroresolve-memory-dir) applies the same seam-resolution shape in session-flow; this PR is theclaude-memoryaudit-tier counterpart.Closes #419