From a698e1723770374af1a5e641a26f1b6d4f384096 Mon Sep 17 00:00:00 2001 From: nmccready Date: Sun, 5 Jul 2026 10:36:26 -0400 Subject: [PATCH 1/2] fix(index): stop leaking per-engineer STATE_* snapshots into AGENTS.md STATE_*.md snapshots are per-engineer working files. Indexing them all churned the shared, committed AGENTS.md with personal scratch state. The State section is now a pointer to the state convention rule (.agents/rules/state.md); a snapshot is listed only when it opts in as a shared task via `shared: true` frontmatter (mirrors the `import: true` opt-in for reference docs). Co-Authored-By: Claude Fable 5 --- docs/commands/index.md | 5 +++- internal/agent/agent.go | 40 +++++++++++++++++++++++++++---- internal/agent/index_test.go | 16 +++++++++---- internal/agent/templates/state.md | 13 ++++++++++ src/md/STATE_TEMPLATE.md | 13 ++++++++++ 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/docs/commands/index.md b/docs/commands/index.md index 01a7504..d10143c 100644 --- a/docs/commands/index.md +++ b/docs/commands/index.md @@ -10,7 +10,10 @@ Regenerates `AGENTS.md` from the contents of `.agents/`. content. Plans and specs are listed **recursively**, so documents grouped per effort in subdirectories (`plans/auth-effort/rollout.md`) index correctly. -- **State** — `STATE_*.md` snapshots. +- **State** — a pointer to the state convention rule + (`.agents/rules/state.md`). `STATE_*.md` snapshots are per-engineer + working files and are **not** listed; a snapshot that represents a + shared task opts into the index with `shared: true` frontmatter. - **Inherits** — preserved verbatim across regenerations. - **Managed Claude import block** — `@`-imports for passive rules (and explicitly-passive workflows) so Claude actually loads them; fully diff --git a/internal/agent/agent.go b/internal/agent/agent.go index a39c9db..13e1bce 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -1583,13 +1583,26 @@ func (a *App) generateAgentsMD() { } } - // State + // State — STATE_*.md snapshots are per-engineer working files, so + // the index must not enumerate them: that leaks one engineer's + // scratch state into the shared, committed AGENTS.md and churns it + // on every regeneration. The section is a pointer to the state + // convention rule; a snapshot appears here only when it opts in as + // a shared task via `shared: true` frontmatter (mirrors the + // `import: true` opt-in for reference docs). b.WriteString("## State\n\n") + b.WriteString("Follow [rules/state.md](.agents/rules/state.md): record progress in `.agents/STATE__.md` snapshots. Snapshots are per-engineer and not indexed unless marked `shared: true` in frontmatter.\n") hasState := false if entries, err := os.ReadDir(agentsDir); err == nil { for _, entry := range entries { name := entry.Name() if strings.HasPrefix(name, "STATE_") && strings.HasSuffix(name, ".md") { + if !stateSnapshotIsShared(filepath.Join(agentsDir, name)) { + continue + } + if !hasState { + b.WriteString("\n### Shared\n\n") + } baseName := strings.TrimSuffix(name, ".md") b.WriteString(fmt.Sprintf("- [%s](.agents/%s)\n", baseName, name)) hasState = true @@ -1598,11 +1611,11 @@ func (a *App) generateAgentsMD() { } legacyState := filepath.Join(agentsDir, "STATE.md") if _, err := os.Stat(legacyState); err == nil { + if !hasState { + b.WriteString("\n### Shared\n\n") + hasState = true + } b.WriteString("- [STATE.md](.agents/STATE.md)\n") - hasState = true - } - if !hasState { - b.WriteString("_No state snapshots yet. Agents will create STATE_*context*_*timestamp*.md files as they work._\n") } b.WriteString("\n") @@ -1785,6 +1798,23 @@ func indexEntry(name, link, srcPath string) string { return fmt.Sprintf("- [%s](%s)\n", name, link) } +// stateSnapshotIsShared reports whether a STATE_*.md snapshot opts +// into the AGENTS.md index as a shared task via `shared: true` +// frontmatter. Snapshots are per-engineer by default and stay out of +// the shared index. +func stateSnapshotIsShared(path string) bool { + raw, err := os.ReadFile(path) + if err != nil { + return false + } + block, err := parseFMBlock(string(raw)) + if err != nil || !block.present { + return false + } + v, _ := block.get("shared") + return strings.EqualFold(strings.TrimSpace(v), "true") +} + // isBucketActive reports whether the target with the given ID is in // ActiveTargets. Used to gate hooks merge/local-sync operations. func (a *App) isBucketActive(targetID string) bool { diff --git a/internal/agent/index_test.go b/internal/agent/index_test.go index 5b9fbe3..be8fc5a 100644 --- a/internal/agent/index_test.go +++ b/internal/agent/index_test.go @@ -467,8 +467,10 @@ func TestCmdIndex_WithStateSnapshots(t *testing.T) { os.MkdirAll(filepath.Join(dir, ".agents", "rules"), 0o755) os.WriteFile(filepath.Join(dir, ".agents", "rules", "test.md"), []byte("body"), 0o644) - // Add a state snapshot. - os.WriteFile(filepath.Join(dir, ".agents", "STATE_test_2026-07-02.md"), []byte("state content"), 0o644) + // Per-engineer snapshot (no frontmatter) must stay out of the index. + os.WriteFile(filepath.Join(dir, ".agents", "STATE_private_2026-07-02.md"), []byte("state content"), 0o644) + // Shared-task snapshot opts in via frontmatter. + os.WriteFile(filepath.Join(dir, ".agents", "STATE_shared_2026-07-02.md"), []byte("---\nshared: true\n---\n\nstate content"), 0o644) var buf bytes.Buffer app := &App{ @@ -485,8 +487,14 @@ func TestCmdIndex_WithStateSnapshots(t *testing.T) { data, _ := os.ReadFile(filepath.Join(dir, "AGENTS.md")) content := string(data) - if !strings.Contains(content, "STATE_test") { - t.Errorf("state snapshot not indexed:\n%s", content[:500]) + if strings.Contains(content, "STATE_private") { + t.Errorf("per-engineer state snapshot leaked into index:\n%s", content) + } + if !strings.Contains(content, "STATE_shared") { + t.Errorf("shared state snapshot not indexed:\n%s", content) + } + if !strings.Contains(content, "rules/state.md") { + t.Errorf("state section missing pointer to rules/state.md:\n%s", content) } } diff --git a/internal/agent/templates/state.md b/internal/agent/templates/state.md index 46c07d4..e2a2022 100644 --- a/internal/agent/templates/state.md +++ b/internal/agent/templates/state.md @@ -22,3 +22,16 @@ Save both if necessary, but the state file is more important for tracking progre Description of current state, progress, blockers, and next steps. Be sure to indicate whats left and what done via - [ ] checkboxes in state file + +## Sharing + +STATE_* snapshots are per-engineer working files. `sync-agents index` does +not list them in AGENTS.md; only the pointer to this rule appears there. +If a snapshot tracks a shared task the whole team should see, opt it into +the index with frontmatter: + +```yaml +--- +shared: true +--- +``` diff --git a/src/md/STATE_TEMPLATE.md b/src/md/STATE_TEMPLATE.md index 46c07d4..e2a2022 100644 --- a/src/md/STATE_TEMPLATE.md +++ b/src/md/STATE_TEMPLATE.md @@ -22,3 +22,16 @@ Save both if necessary, but the state file is more important for tracking progre Description of current state, progress, blockers, and next steps. Be sure to indicate whats left and what done via - [ ] checkboxes in state file + +## Sharing + +STATE_* snapshots are per-engineer working files. `sync-agents index` does +not list them in AGENTS.md; only the pointer to this rule appears there. +If a snapshot tracks a shared task the whole team should see, opt it into +the index with frontmatter: + +```yaml +--- +shared: true +--- +``` From a0b6a97d7281fc4ab4594cdbc09647d02efd8cc2 Mon Sep 17 00:00:00 2001 From: nmccready Date: Sun, 5 Jul 2026 10:39:27 -0400 Subject: [PATCH 2/2] test(bats): update State-section tests for shared: true opt-in Co-Authored-By: Claude Fable 5 --- test/sync-agents.bats | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/sync-agents.bats b/test/sync-agents.bats index 6a0eb11..2a93c7f 100644 --- a/test/sync-agents.bats +++ b/test/sync-agents.bats @@ -1344,20 +1344,20 @@ CONF [ -f "$TEST_DIR/.agents/rules/state.md" ] } -@test "AGENTS.md State section lists STATE_ files" { +@test "AGENTS.md State section indexes only shared STATE_ files" { "$SCRIPT" -d "$TEST_DIR" init - # Create some state snapshot files + # Per-engineer snapshot (no frontmatter) must stay out of the index echo "state 1" > "$TEST_DIR/.agents/STATE_feature-work_20260425120000.md" - echo "state 2" > "$TEST_DIR/.agents/STATE_bugfix_20260426080000.md" + # Shared-task snapshot opts in via frontmatter + printf -- '---\nshared: true\n---\n\nstate 2\n' > "$TEST_DIR/.agents/STATE_bugfix_20260426080000.md" "$SCRIPT" -d "$TEST_DIR" index - # AGENTS.md should reference the state files - grep -q "STATE_feature-work_20260425120000" "$TEST_DIR/AGENTS.md" + ! grep -q "STATE_feature-work_20260425120000" "$TEST_DIR/AGENTS.md" grep -q "STATE_bugfix_20260426080000" "$TEST_DIR/AGENTS.md" } -@test "AGENTS.md State section shows placeholder when no state files" { +@test "AGENTS.md State section points at the state convention rule" { "$SCRIPT" -d "$TEST_DIR" init - [[ "$(cat "$TEST_DIR/AGENTS.md")" == *"No state snapshots yet"* ]] + grep -q "rules/state.md" "$TEST_DIR/AGENTS.md" } # --------------------------------------------------------------------------