diff --git a/.abcd/work/issues/open/iss-88-managed-repo-label-overclaims.md b/.abcd/work/issues/resolved/iss-88-managed-repo-label-overclaims.md similarity index 80% rename from .abcd/work/issues/open/iss-88-managed-repo-label-overclaims.md rename to .abcd/work/issues/resolved/iss-88-managed-repo-label-overclaims.md index a17d774..047d3eb 100644 --- a/.abcd/work/issues/open/iss-88-managed-repo-label-overclaims.md +++ b/.abcd/work/issues/resolved/iss-88-managed-repo-label-overclaims.md @@ -7,6 +7,7 @@ category: "observation" source: "agent-finding" found_during: "2026-07-13 B1 dogfood: prepare-this-repo audit of Manuscripts" found_at: "internal/core/ahoy/detect.go" +resolution: "Dropped .abcd/ from the ahoy strong-signal set: only index registration or a marker block promotes to managed-repo; a stray .abcd/ now reports unmanaged. Detector-first in internal/core/ahoy/detect_test.go." --- abcd ahoy classifies a repo with a partial or stray .abcd/ as folder_kind managed-repo even when adopted is null and index_registered is false -- managed reads as abcd manages this when it only means abcd could. No folder_kind value distinguishes a stray .abcd/ built by another workflow from an abcd-adopted repo. Observed on Manuscripts, whose .abcd/development/ was hand-built with zero abcd involvement yet is reported managed. Related: iss-62 managed-repo-identity-gate. Detector: a folder_kind vocabulary that separates adopted from merely-abcd-shaped; acceptance is ahoy --json on a hand-built .abcd/ not reporting managed. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 22afb6c..15463d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,12 @@ called out in a **Breaking** section. ### Fixed +- **`abcd ahoy` no longer overclaims `managed-repo` for a stray `.abcd/` + directory (iss-88).** Folder classification treated the mere presence of an + `.abcd/` directory as a strong managed signal, so a repo with an unregistered, + markerless `.abcd/` reported `managed-repo`. Only index registration or a + marker block now promotes a folder to managed; a stray `.abcd/` reports + `unmanaged-repo` (or `unmanaged-folder` outside a git repo). - **`abcd intent ""` no longer files a draft from a mistyped subcommand.** A near-miss for an intent subverb (`intent paln`, `intent lnk itd-5`) is refused with a did-you-mean and writes nothing, mirroring `abcd capture`'s diff --git a/internal/core/ahoy/detect.go b/internal/core/ahoy/detect.go index 14a1456..18f22e5 100644 --- a/internal/core/ahoy/detect.go +++ b/internal/core/ahoy/detect.go @@ -100,7 +100,9 @@ func classify(cwd string, id RepoIdentity, idx *historyIndex) (FolderKind, map[s gitRepo := isDir(filepath.Join(cwd, ".git")) signals["git_repo"] = gitRepo - strong := registered || abcdDir || markerFired + // A bare .abcd/ directory is not a managed signal on its own (iss-88): only + // index registration or a marker block promotes a folder to managed-repo. + strong := registered || markerFired switch { case strong: return ManagedRepo, signals diff --git a/internal/core/ahoy/detect_test.go b/internal/core/ahoy/detect_test.go index e1f4fc6..fdb63c1 100644 --- a/internal/core/ahoy/detect_test.go +++ b/internal/core/ahoy/detect_test.go @@ -65,7 +65,10 @@ func TestClassifyUnmanagedRepo(t *testing.T) { } } -func TestClassifyManagedRepoByAbcdDir(t *testing.T) { +// TestClassifyAbcdDirInRepoIsNotManaged pins iss-88: a git repo carrying a stray +// .abcd/ directory but no index registration and no marker block is an +// unmanaged-repo, not a managed-repo — the .abcd/ dir alone never promotes. +func TestClassifyAbcdDirInRepoIsNotManaged(t *testing.T) { setupHermetic(t) dir := t.TempDir() if err := os.Mkdir(filepath.Join(dir, ".git"), 0o755); err != nil { @@ -78,8 +81,29 @@ func TestClassifyManagedRepoByAbcdDir(t *testing.T) { if err != nil { t.Fatal(err) } - if det.FolderKind != ManagedRepo { - t.Errorf("kind = %q, want %q", det.FolderKind, ManagedRepo) + if det.FolderKind != UnmanagedRepo { + t.Errorf("kind = %q, want %q", det.FolderKind, UnmanagedRepo) + } +} + +// TestClassifyStrayAbcdDirIsNotManaged pins iss-88: a bare .abcd/ directory with +// no index registration and no marker block must NOT overclaim managed-repo. With +// no .git present it is an unmanaged folder. +func TestClassifyStrayAbcdDirIsNotManaged(t *testing.T) { + setupHermetic(t) + dir := t.TempDir() + if err := os.Mkdir(filepath.Join(dir, ".abcd"), 0o755); err != nil { + t.Fatal(err) + } + det, err := Detect(dir) + if err != nil { + t.Fatal(err) + } + if det.FolderKind == ManagedRepo { + t.Errorf("stray .abcd/ overclaims managed: kind = %q, want not %q", det.FolderKind, ManagedRepo) + } + if det.FolderKind != UnmanagedFolder { + t.Errorf("kind = %q, want %q", det.FolderKind, UnmanagedFolder) } } @@ -151,6 +175,12 @@ func TestDetectHookManifestGapOnBrokenPlugin(t *testing.T) { if err := os.Mkdir(filepath.Join(dir, ".abcd"), 0o755); err != nil { t.Fatal(err) } + // A marker block makes the folder genuinely managed (post iss-88 the .abcd/ + // dir alone no longer promotes), so the deeper gap checks run. + body := "# Project\n\n\nx\n\n" + if err := os.WriteFile(filepath.Join(dir, "CLAUDE.md"), []byte(body), 0o644); err != nil { + t.Fatal(err) + } det, err := Detect(dir) if err != nil { t.Fatal(err)