Skip to content

chore: rescue unlanded session docs, gitignore .codex/, de-drift AGENTS.md - #852

Merged
lmorchard merged 3 commits into
mainfrom
chore/repo-housekeeping
Sep 15, 2026
Merged

lmorchard merged 3 commits into
mainfrom
chore/repo-housekeeping

Conversation

@lmorchard

@lmorchard lmorchard commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Repo housekeeping from a state audit of the main clone. Three independent
cleanups, no behavior change.

1. Rescue unlanded dev-session docs

Four dev sessions left spec.md / plan.md uncommitted inside agent-session
worktrees. Only checks.md ever reached main, and for #779 and #783 nothing
did. Recovered here before those worktrees get pruned:

Session Files recovered
#779 durable input queues checks.md, plan.md, spec.md
#783 synchronous hooks spec.md
#145 steering messages spec.md
#193 garden folders plan.md, spec.md

Empty notes.md files were skipped.

2. Gitignore .codex/

.gitignore covered .claude/ but not .codex/, so a 3.4G directory holding
15 Codex worktrees showed up as untracked on every git status in the main
clone.

3. AGENTS.md becomes a symlink to CLAUDE.md

AGENTS.md was an untracked Aug 5 snapshot of CLAUDE.md with a blind
claude -> Codex find/replace run over it, which corrupted real facts:

It claimed Truth
extra_skill_paths targets ~/.Codex/skills ~/.claude/skills
the skill is skills/{...,Codex,...} claude_code
.Codex/ is the gitignored worktree root .claude/ is

It was also missing the static/ module-graph convention (from #844) and
metrics.py (from #848). Since it was a verbatim copy in the first place, a
symlink gives Codex the same conventions and removes the drift vector
entirely.

Unrelated note for future readers: the vault guide's default filename is also
AGENTS.md, but that path resolves against the vault root, not the repo root.
Different file.

Verification

Doc, config, and symlink only -- no Python or JS touched, so this worktree
skipped uv sync and has no local venv; CI's make check is the gate. Symlink
verified stored as git mode 120000 -> CLAUDE.md, and the new ignore pattern
verified matching .codex/worktrees/....

4. scripts/prune_agent_worktrees.py + make prune-worktrees

The audit that produced this PR found 21 worktrees and 30 local branches, 25 of
which held work already in origin/main. Doing that by hand means checking each
branch against both main's ancestry and its PR's merge state, so it's captured
as a script rather than a one-off.

Classifies each branch as LANDED (ancestor of origin/main, or a merged PR
whose merge postdates the branch tip -- i.e. squash-merged) or UNLANDED, and
only ever prunes the former. Detached worktrees are reported separately, since
they have no branch to key off and git worktree prune only reaps directories
that are already gone.

Safety: refuses to drop a landed worktree with tracked edits unless --force,
prints the git branch <name> <sha> recovery line for everything it deletes,
and is dry-run by default -- matching the existing
migrate-sidecars-dry / migrate-sidecars pairing.

Verified against this clone: 25 LANDED / 5 UNLANDED / 2 detached, matching a
hand audit of the same state. ruff check and ruff format were run on the file
directly, since scripts/ sits outside make lint and make fmt (both scoped
to src/ and tests/).

Four dev sessions left their spec.md/plan.md uncommitted in agent-session
worktrees; only checks.md ever reached main (and for #779/#783, nothing did).
Recovered before those worktrees get pruned:

- #779 durable input queues: checks.md, plan.md, spec.md
- #783 synchronous hooks: spec.md
- #145 steering messages: spec.md
- #193 garden folders: plan.md, spec.md

Empty notes.md files were skipped.
Two untracked-noise sources in the main clone:

- .gitignore covered .claude/ but not .codex/, so a 3.4G directory of Codex
  worktrees showed up as untracked on every status.
- AGENTS.md was an Aug 5 snapshot of CLAUDE.md with a blind claude->Codex
  find/replace run over it, which corrupted real facts: it pointed
  extra_skill_paths at ~/.Codex/skills instead of ~/.claude/skills, renamed
  the claude_code skill to Codex, and claimed .Codex/ was the gitignored
  worktree root. It was also missing the static-module-graph convention and
  metrics.py. A symlink keeps Codex on the same conventions and cannot drift.

Note: the vault guide's default filename is also AGENTS.md, but that path is
relative to the vault root, not the repo root - unrelated file.
Copilot AI lite review requested due to automatic review settings September 15, 2026 23:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Resolve the documented inconsistencies and duplicate session record, and add the missing advertised symlink.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Housekeeping PR restoring development-session documentation and adding .codex/ to .gitignore. The advertised AGENTS.md symlink is absent from the supplied patch.

Changes:

  • Restores specs, plans, and checks for four sessions.
  • Adds .codex/ to .gitignore.
  • Leaves documentation inconsistencies and duplicate records to resolve.
File summaries
File Summary
docs/dev-sessions/20260813T191028Z-garden-folders/spec.md Restored specification; contraction and regression-test-path corrections are needed.
docs/dev-sessions/20260813T191028Z-garden-folders/plan.md Restored implementation plan.
docs/dev-sessions/2026-08-13-145-steering-messages/spec.md Restored specification; documented test path is incorrect.
docs/dev-sessions/2026-08-10-1200-779-concurrency-durable-input-queues/spec.md Duplicates an existing session record and should be merged or removed.
docs/dev-sessions/2026-08-10-1200-779-concurrency-durable-input-queues/plan.md Contains a SQLite/JSONL contradiction and editing aside.
docs/dev-sessions/2026-08-10-1200-779-concurrency-durable-input-queues/checks.md Frozen acceptance outcomes remain marked pending.
docs/dev-sessions/2026-08-08-0000-783-synchronous-hooks/spec.md Restored synchronous-hooks specification.
.gitignore Adds the .codex/ ignore rule; the advertised AGENTS.md symlink is not present.
Review details

Suppressed comments (1)

docs/dev-sessions/2026-08-10-1200-779-concurrency-durable-input-queues/spec.md:8

  • This proposed implementation still says the inbox is SQLite-backed, while the acceptance criteria and design decision below specify JSONL. That contradiction could lead anyone following this recovered spec to implement the wrong persistence layer; make this bullet consistently describe JSONL storage.
- Migrate `conversation_manager.py`'s input handling from in-memory dispatch to a durable, SQLite-backed `Inbox` queue (similar to how `notifications.py` uses JSONL, but for incoming turns).
  • Files reviewed: 8/9 changed files
  • Comments generated: 6
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

## C1
CRITERION: WHEN `enqueue_turn` is called, THEN the system SHALL write the incoming turn to a durable JSONL session inbox file instead of an in-memory queue.
CHECK: `pytest tests/test_conversation_manager.py::test_enqueue_turn_writes_to_jsonl` passes.
AT FREEZE: (pending)
**Source issue:** https://github.com/lmorchard/decafclaw/issues/779 — **Tier:** `auto-ok` (All criteria are verifiable via unit tests without human judgment. Adding a JSONL-backed queue for incoming turns does not touch risk-gated paths.)

**Approach:**
- Migrate `conversation_manager.py`'s input handling from in-memory dispatch to a durable, SQLite-backed Inbox queue (similar to how `notifications.py` uses JSONL, but for incoming turns). Wait, design decisions specify "backed by JSONL files rather than SQLite".
@@ -0,0 +1,33 @@
**Concept from opencode:**

## Regression guards

- GUARD: `pytest tests/test_agent.py` passes and existing agent turn flow is preserved.
Comment on lines +15 to +17
- Need to be conservative — dont break existing links or surprise users
- May want a dry-run mode that logs proposed moves without executing
- Should respect any user-created folder structure (dont flatten what the user organized)

## Regression guards

- GUARD: `pytest tests/test_vault_tools.py tests/test_garden_recompute.py` passes — existing vault operations and importance recompute remain fully functional.
Agent-driven development leaves a lot of git debris; this clone had 21
worktrees and 30 local branches, 25 of which held work already in origin/main.
Cleaning that up by hand means checking each branch against both main's
ancestry and its PR's merge state, which is exactly the kind of manual
invocation that leaves nothing behind.

The script classifies each branch as LANDED (ancestor of origin/main, or a
merged PR whose merge postdates the branch tip, i.e. squash-merged) or
UNLANDED, and only ever prunes the former. It also reports detached worktrees
separately: they have no branch to key off, so a branch-driven scan misses
them and 'git worktree prune' only reaps directories that are already gone.

Refuses to drop a landed worktree that has tracked edits unless --force, and
prints the recovery command for every branch it deletes. Dry run by default,
matching the migrate-sidecars-dry / migrate-sidecars pairing.

Verified against this clone: 25 LANDED / 5 UNLANDED / 2 detached, matching a
hand audit of the same state. Note scripts/ sits outside 'make lint' and
'make fmt' (both scoped to src/ and tests/); ruff check and format were run on
this file directly.
Copilot AI review requested due to automatic review settings September 15, 2026 23:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The pruning tool’s branch-base classification, destructive-path test coverage, and stale plan wording must be addressed.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (5)

docs/dev-sessions/2026-08-10-1200-779-concurrency-durable-input-queues/plan.md:8

  • This recovered plan still says the approach is SQLite-backed, then corrects itself inline, while the goal and criteria consistently require JSONL. Replace the stale wording so the session plan is unambiguous for future readers.
- Migrate `conversation_manager.py`'s input handling from in-memory dispatch to a durable, SQLite-backed Inbox queue (similar to how `notifications.py` uses JSONL, but for incoming turns). Wait, design decisions specify "backed by JSONL files rather than SQLite".

scripts/prune_agent_worktrees.py:124

  • A branch's committer timestamp is not proof that its current tip is the PR that was merged: local branches can share headRefName with a fork or an older PR, and commit timestamps can be backdated or use different offsets. This can classify unrelated work as LANDED and then delete it with branch -D; use the merged PR's headRefOid (and head-repository identity) and require the local tip to match before accepting the squash-merged verdict.
    merged_at = prs.get(name)
    if merged_at:
        last = git("log", "-1", "--format=%cI", name)
        if last <= merged_at:
            return "LANDED", f"squash-merged {merged_at[:10]}"

scripts/prune_agent_worktrees.py:204

  • inspect_worktree() records untracked files, but this guard ignores them. A landed worktree with a clean index and an untracked spec.md will still be removed by the unconditional git worktree remove --force, even when the script was not invoked with its --force option. Treat b.untracked as dirty here so local files are not silently lost.
        if b.dirty_tracked and not force:
            print(f"SKIP     {b.name}: {len(b.dirty_tracked)} tracked edit(s); use --force")

scripts/prune_agent_worktrees.py:123

  • %cI includes the commit's original timezone offset, while GitHub's mergedAt is normally UTC (Z); ISO-8601 strings with different offsets are not safely orderable lexicographically. A commit at 12:00−05:00 can compare earlier than a 16:00Z merge even though it was committed at 17:00Z, so this can classify post-merge work as LANDED. Parse timestamps as aware datetimes, and preferably use the PR head/merge commit IDs instead of wall-clock ordering for a destructive decision.
        if last <= merged_at:

scripts/prune_agent_worktrees.py:139

  • A failed git status is treated as an empty status because check=False is ignored and both result lists remain empty. apply() then calls git worktree remove --force and deletes the branch as if the worktree were clean; a permission error or corrupted worktree can therefore bypass the safety check. Fail closed on a nonzero status result (or mark the worktree uninspectable and skip it).
        check=False,
    )
  • Files reviewed: 10/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

print("warning: gh not found; squash-merged branches will read as UNLANDED")
return {}
proc = subprocess.run(
["gh", "pr", "list", "--state", "merged", "--limit", "500", "--json", "headRefName,mergedAt"],
Comment on lines +198 to +202
def apply(branches: list[Branch], force: bool) -> int:
failures = 0
for b in sorted(branches, key=lambda x: x.name):
if not b.landed:
continue
@lmorchard
lmorchard merged commit 18d5c4c into main Sep 15, 2026
3 checks passed
@lmorchard
lmorchard deleted the chore/repo-housekeeping branch September 15, 2026 23:14
lmorchard added a commit that referenced this pull request Sep 16, 2026
Doc currency for #852-#858. AGENTS.md is a symlink to this file, so Codex picks
these up automatically.

- Running: adds check-tui, test-tui, prune-worktrees{,-dry}; corrects 'make
  check' (composed from install-js, check-message-types, lint, typecheck and
  check-js) and 'make lint' (described as 'Compile-check'; it is ruff over four
  directories, and was inaccurate before this session too — fixed because it
  sits in the same list being corrected for accuracy).
- Workflow: a new convention for the defect that recurred four times in one
  session — a gate that exists in the Makefile and still runs nowhere. Compose
  targets rather than duplicating their commands, confirm CI invokes the target
  and read the job log, and probe every gate for teeth. Notes that pyright
  exits 0 when it reports only warnings, which makes a pyrightconfig.json
  addition report without failing.

Both composition lists are checked against Makefile:105 programmatically, not
by eye — an earlier draft listed only three of the five prerequisites.
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.

2 participants