From 80a5128ff5fa0d9964c9fe2175b5b6f681edc804 Mon Sep 17 00:00:00 2001 From: Jeppe Lillevang Salling Date: Sun, 21 Jun 2026 10:03:52 +0000 Subject: [PATCH 1/3] feat(cli): add read-only `agent-init status` subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `status [target]` reports the scaffold's current visibility (shared / local / hidden / shadowed-by-global), the absolute path of the file carrying the agent-init ignore block, and the exact sed undo command. When the scaffold is committed locally but a global-default ignore exists, the report includes the force-add hint explaining why files may still be tracked. Detection follows git's ignore precedence (.gitignore > .git/info/exclude > core.excludesfile) and reports the most-local carrier. The subcommand is strictly read-only — no files and no git config are mutated. `gitignore` gains `HasBlock` and exports `MarkerStart` / `MarkerEnd` so the detector and the undo hint reuse the same markers the writers do. Closes #60 Co-Authored-By: Claude Opus 4.7 (1M context) --- .agent/CODEBASE.md | 42 ++-- README.md | 1 + docs/cli.md | 39 +++- internal/cli/cli.go | 11 + internal/cli/cli_test.go | 4 +- internal/cli/status.go | 174 ++++++++++++++ internal/cli/status_test.go | 326 +++++++++++++++++++++++++++ internal/gitignore/gitignore.go | 17 ++ internal/gitignore/gitignore_test.go | 44 ++++ 9 files changed, 635 insertions(+), 23 deletions(-) create mode 100644 internal/cli/status.go create mode 100644 internal/cli/status_test.go diff --git a/.agent/CODEBASE.md b/.agent/CODEBASE.md index d85108d..cb85af4 100644 --- a/.agent/CODEBASE.md +++ b/.agent/CODEBASE.md @@ -35,7 +35,9 @@ _Generated by `gen-codemap.sh`. Re-run after structural changes._ |-- internal | |-- cli | | |-- cli.go -| | `-- cli_test.go +| | |-- cli_test.go +| | |-- status.go +| | `-- status_test.go | |-- flavors | | |-- claudecowork | | |-- common @@ -90,8 +92,8 @@ _Generated by `gen-codemap.sh`. Re-run after structural changes._ ### Go internal/cli/cli.go:66:type Version struct { -internal/cli/cli.go:152:type App struct { -internal/cli/cli.go:160:func New(out, errOut io.Writer, version Version) App { +internal/cli/cli.go:161:type App struct { +internal/cli/cli.go:169:func New(out, errOut io.Writer, version Version) App { internal/cli/cli_test.go:15:func TestListFlavors(t *testing.T) { internal/cli/cli_test.go:28:func TestInitLegacyTargetArgument(t *testing.T) { internal/cli/cli_test.go:47:func TestInitLegacyTargetArgumentWithoutFlag(t *testing.T) { @@ -133,6 +135,16 @@ internal/cli/cli_test.go:799:func TestUnknownFlavorErrorPointsAtHelp(t *testing. internal/cli/cli_test.go:815:func TestHelpFlagsMatchDocs(t *testing.T) { internal/cli/cli_test.go:842:func TestVersion(t *testing.T) { internal/cli/cli_test.go:855:func TestVersionDefaultsToDev(t *testing.T) { +internal/cli/status_test.go:52:func TestStatusSharedWhenNoBlockAnywhere(t *testing.T) { +internal/cli/status_test.go:75:func TestStatusLocalWhenBlockInGitignore(t *testing.T) { +internal/cli/status_test.go:109:func TestStatusHiddenWhenBlockInGitInfoExclude(t *testing.T) { +internal/cli/status_test.go:146:func TestStatusShadowedByGlobalWhenOnlyGlobalCarriesBlock(t *testing.T) { +internal/cli/status_test.go:189:func TestStatusPrefersLocalOverHiddenOverGlobal(t *testing.T) { +internal/cli/status_test.go:226:func TestStatusDefaultTargetIsCwd(t *testing.T) { +internal/cli/status_test.go:256:func TestStatusExplicitTargetIsResolvedToAbsolutePath(t *testing.T) { +internal/cli/status_test.go:273:func TestStatusRejectsExtraPositionalArgs(t *testing.T) { +internal/cli/status_test.go:285:func TestStatusHelp(t *testing.T) { +internal/cli/status_test.go:308:func TestStatusIgnoresGlobalLookupErrors(t *testing.T) { internal/flavors/claudecowork/flavor.go:11:func Templates() embed.FS { internal/flavors/claudecowork/flavor.go:18:func ExecutablePaths() []string { internal/flavors/claudecowork/flavor.go:25:func NextSteps(target string) string { @@ -172,23 +184,13 @@ internal/gitconfig/gitconfig_test.go:162:func TestEnsureGlobalExpandsTildeInConf internal/gitconfig/gitconfig_test.go:176:func TestEnsureGlobalAppendsToExistingFileWithoutOurBlock(t *testing.T) { internal/gitconfig/gitconfig_test.go:198:func TestEnsureGlobalIsIdempotent(t *testing.T) { internal/gitconfig/gitconfig_test.go:223:func TestGlobalPathDoesNotWriteOrSet(t *testing.T) { -internal/gitignore/gitignore.go:48:func Block() string { -internal/gitignore/gitignore.go:63:func LocalPath(target string) (string, error) { -internal/gitignore/gitignore.go:75:func EnsureLocal(target string) (string, error) { -internal/gitignore/gitignore.go:94:func HiddenPath(target string) (string, error) { -internal/gitignore/gitignore.go:115:func EnsureHidden(target string) (string, error) { -internal/gitignore/gitignore.go:139:func Upsert(content string) string { -internal/gitignore/gitignore_test.go:10:func TestBlockHasMarkersAndEnvelope(t *testing.T) { -internal/gitignore/gitignore_test.go:27:func TestEnsureLocalCreatesAndAppends(t *testing.T) { -internal/gitignore/gitignore_test.go:70:func TestEnsureLocalIsIdempotent(t *testing.T) { -internal/gitignore/gitignore_test.go:95:func TestEnsureLocalReplacesStaleBlockInPlace(t *testing.T) { -internal/gitignore/gitignore_test.go:128:func TestEnsureHiddenCreatesAndAppends(t *testing.T) { -internal/gitignore/gitignore_test.go:174:func TestEnsureHiddenIsIdempotent(t *testing.T) { -internal/gitignore/gitignore_test.go:196:func TestEnsureHiddenReplacesStaleBlockInPlace(t *testing.T) { -internal/gitignore/gitignore_test.go:234:func TestEnsureHiddenWritesNoGitignore(t *testing.T) { -internal/scaffold/color_test.go:9:func TestColorDisabledForNonTTYOutputs(t *testing.T) { -internal/scaffold/color_test.go:26:func TestColorDisabledByEnvironment(t *testing.T) { -internal/scaffold/color_test.go:50:func TestColorEnabledForTerminalFile(t *testing.T) { +internal/gitignore/gitignore.go:56:func Block() string { +internal/gitignore/gitignore.go:71:func LocalPath(target string) (string, error) { +internal/gitignore/gitignore.go:83:func EnsureLocal(target string) (string, error) { +internal/gitignore/gitignore.go:102:func HiddenPath(target string) (string, error) { +internal/gitignore/gitignore.go:123:func EnsureHidden(target string) (string, error) { +internal/gitignore/gitignore.go:147:func Upsert(content string) string { +internal/gitignore/gitignore.go:155:func HasBlock(content string) bool { ## Stats diff --git a/README.md b/README.md index 8d0cb1b..56379b9 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ go build \ ```bash agent-init init [flavor] [target-dir] agent-init add-tracker +agent-init status [target] agent-init list-flavors agent-init list-trackers agent-init version diff --git a/docs/cli.md b/docs/cli.md index 93f5c2f..c211361 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,10 +1,11 @@ # CLI -`agent-init` is a small CLI with five subcommands. Source: [internal/cli/cli.go](../internal/cli/cli.go). +`agent-init` is a small CLI with six subcommands. Source: [internal/cli/cli.go](../internal/cli/cli.go). ``` agent-init init [flavor] [target-dir] agent-init add-tracker +agent-init status [target] agent-init list-flavors agent-init list-trackers agent-init version @@ -88,6 +89,42 @@ There is no `remove-tracker` subcommand yet. Manual cleanup: 2. Remove the entry from `.mcp.json` under `mcpServers`. 3. Remove the tracker name from `AGENTS.md`'s "Active trackers" line. +## `status` + +Reports how the scaffold's agentic envelope is currently tracked by git. Read-only — `status` writes no files and touches no git configuration. + +```bash +agent-init status # report status of the current directory +agent-init status ./my-tool # report status of ./my-tool +``` + +The optional positional argument defaults to `.`. The target is resolved to an absolute path before reporting, so the printed paths are unambiguous. + +### Output + +Each line is `