fix(session-flow): derive retro HANDOFF_DIR from resolved memory_dir#469
Conversation
The retro Phase 1.1 multi-session snippet hard-coded HANDOFF_DIR to the bare default .work/handoffs directly under an instruction to resolve it from memory_dir. A copy-as-is run bypassed the memory_dir seam, missing the handoff chain in any repo that relocates its memory tier. Derive HANDOFF_DIR from the resolved memory_dir instead: read the .claude/topic-docs.yaml concern file's memory_dir key, fall back to .work. Mirrors the topic-docs.yaml parse shape already used elsewhere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 30s —— View job PR Review
SummaryThe fix is correct and well-scoped. The hard-coded
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3042a8e7e9
ℹ️ 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".
…arse
The retro Phase 1.1 memory_dir parse stripped every whitespace character
with ${VAL//[[:space:]]/}, collapsing interior spaces in a valid quoted
value: memory_dir: ".agent work" resolved to .agentwork, so the
multi-session retro looked in a nonexistent handoff directory and
silently missed the chain.
Trim only trailing whitespace (${VAL%"${VAL##*[![:space:]]}"}); sed
already removed leading whitespace after the colon, and the closing
quote shields any significant trailing space before quote-stripping.
Interior spaces in quoted and plain scalars are preserved; unquoted YAML
trailing whitespace, inline comments, and trailing slashes still trim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 36s —— View job PR Review (synchronize — new commit
|
| Step | Expression | Effect |
|---|---|---|
| 1 | ${VAL##*[![:space:]]} |
Remove the longest prefix ending in a non-space; leaves only trailing spaces |
| 2 | ${VAL%"<above>"} |
Strip that trailing-space suffix from VAL |
Leading whitespace was already handled by the sed pattern (s/^memory_dir:[[:space:]]*//p). The only missing piece was the trailing trim, which this idiom provides precisely.
Correctness
Edge cases all pass:
memory_dir: .agent work→VAL=".agent work"→ interior space preserved, trailing newline trimmed ✓memory_dir: ".work"→ quote-stripped to.work✓memory_dir:(empty) →VAL=""→[[ -n "$VAL" ]]guard keepsMEMORY_DIR=.work✓memory_dir:(only spaces) → strips to""→ guard fires ✓- Nested/indented key (
memory_dir: ...) →^memory_dir:anchor still ignores it ✓
Scope: No other files changed in this commit. The version bump (0.10.1 → 0.10.2) and CHANGELOG entry are already in place from the prior commit.
Existing observations from the earlier review remain unchanged — the CLAUDE.md/.claude/rules/ save-point resolution gap vs. SKILL.md is tracked in #419 and not a regression here; the repo-root guard absence is still harmless (read-only path, empty NEWEST on bad value).
Verdict
No new issues. The whitespace fix is correct, minimal, and exactly addresses the Codex P2 finding. Ready to merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1621a1598a
ℹ️ 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".
Resolve session-flow version collision and CHANGELOG conflict; address #433 finding 2. - plugin.json: re-bump 0.10.2 -> 0.10.3 (main already shipped 0.10.2 via #469) - CHANGELOG: keep main 0.10.2 (retro HANDOFF_DIR fix) and re-home the override-boundary entry as 0.10.3, newest-first - workflow/context/pre-pr.md: add override-boundary note (finding 2) -- the pre-PR sequence incl. the simplify pass is fixed plugin identity; commands and criteria adapt at each gate, a differing consumer runs its own workflow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…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
The retro Phase 1.1 multi-session snippet (
plugins/session-flow/skills/retro/context/session.md) hard-codedHANDOFF_DIR=.work/handoffsdirectly beneath an instruction to resolve it from the concern file'smemory_dir. A copy-as-is execution encoded the bare default and bypassed the memory_dir seam — missing the handoff chain in any repo that relocates its memory tier via.claude/topic-docs.yaml.The snippet now derives
HANDOFF_DIRfrom the resolvedmemory_dir: it reads the.claude/topic-docs.yamlconcern file'smemory_dirkey and falls back to.work, matching the topic-docs.yaml parse shape already used elsewhere in the fleet. Comment prose trimmed to describe what the snippet actually performs, pointing to the handoff skill for the full resolution order.Version bumped 0.10.1 -> 0.10.2 with a matching CHANGELOG entry.
Closes #420
Related
.work-convention sweep: this is one instance of a broader pattern where copy-as-is snippets encode the bare.workdefault instead of resolving the memory_dir seam.