diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c5b56d..871e779 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: '1.26' + go-version-file: go.mod - name: Tidy run: | go mod tidy @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: '1.26' + go-version-file: go.mod - uses: golangci/golangci-lint-action@v7 with: version: v2.12.2 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d248db8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,24 @@ +# cli-common — agent entrypoint + +Shared libraries and the normative standards docs for the Open CLI Collective +CLI family. This repo is the standards home, so the source-of-truth links +below are local files, not GitHub URLs. + +Read first: + +- [`docs/development.md`](docs/development.md) — repo-local facts: package + map, build/test commands, hermetic-test rules, release/tagging policy for + this library. +- [`docs/README.md`](docs/README.md) — the standards index: a one-line "use + this when…" per doc, plus the cross-doc conflict-resolution order. + +Shared automation: + +Source of truth: https://github.com/open-cli-collective/.github +Local convenience copy, if present: `../.github` + +When editing a standards doc, keep per-CLI divergences in that doc's +"Current divergences" section, and follow +[`docs/agent-implementation.md`](docs/agent-implementation.md) for how agent +guidance is shaped and where a repeated failure mode gets enforced (docs, +tests, lint, CI, or shared helpers). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d248db8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,24 @@ +# cli-common — agent entrypoint + +Shared libraries and the normative standards docs for the Open CLI Collective +CLI family. This repo is the standards home, so the source-of-truth links +below are local files, not GitHub URLs. + +Read first: + +- [`docs/development.md`](docs/development.md) — repo-local facts: package + map, build/test commands, hermetic-test rules, release/tagging policy for + this library. +- [`docs/README.md`](docs/README.md) — the standards index: a one-line "use + this when…" per doc, plus the cross-doc conflict-resolution order. + +Shared automation: + +Source of truth: https://github.com/open-cli-collective/.github +Local convenience copy, if present: `../.github` + +When editing a standards doc, keep per-CLI divergences in that doc's +"Current divergences" section, and follow +[`docs/agent-implementation.md`](docs/agent-implementation.md) for how agent +guidance is shaped and where a repeated failure mode gets enforced (docs, +tests, lint, CI, or shared helpers). diff --git a/Makefile b/Makefile index ae541ec..60f9804 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ .PHONY: check tidy lint test build -# CI gate: everything that must pass before merge. -check: tidy lint test +# CI gate: everything that must pass before merge (repo-layout.md §2.1 — +# check mirrors CI, including the build step). +check: tidy lint test build # Tidy and verify the module is clean. Catches both modified tracked and # newly-generated untracked go.mod/go.sum (the latter once deps are added). diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..d2d714f --- /dev/null +++ b/docs/development.md @@ -0,0 +1,52 @@ +# Development guide — cli-common + +Repo-local facts for working in this repository. Family-wide policy lives in +[`README.md`](README.md) (the standards index) and the standards docs beside +it — do not copy policy prose here. + +## What this repo is + +The shared-library and standards home for the Open CLI Collective. No binary +ships from here; consumers depend on it as a Go module +(`github.com/open-cli-collective/cli-common`). It follows the **library-repo +profile** in [`repo-layout.md`](repo-layout.md) §2.1. + +## Packages + +| Package | Implements | +|---|---| +| `credstore` | OS-keyring credential store — [`working-with-secrets.md`](working-with-secrets.md) §1.3–§1.5, §1.8, §1.12, §2.1 | +| `statedir` | config/cache/data path resolver — [`working-with-state.md`](working-with-state.md) §6a | +| `statedirtest` | hermetic test helper (8-var env override) — [`working-with-state.md`](working-with-state.md) §3.1 / §5.3 | +| `cache` | tier-1 cache core: envelope, atomic write, freshness — [`working-with-state.md`](working-with-state.md) §6b | + +## Build / test + +```sh +make check # tidy + lint + test + build — mirrors CI +``` + +Requires Go per the `go` directive in `go.mod` (the single version source, +`repo-layout.md` §3) and golangci-lint v2. + +Tests MUST be hermetic: use the in-memory credstore backend +(`Options.Backend = BackendMemory`) and `statedirtest.Hermetic` (not +`t.Parallel`-safe — use sequentially). Tests never touch the developer's real +OS keychain or home directories; that class of leak is exactly what +`statedirtest` exists to prevent. + +## Releases + +No auto-release, no `version.txt` (library profile, `repo-layout.md` §2.1). +Semver tags are cut manually. Any exported API **or behavior** change in any +exported package (`credstore`, `statedir`, `statedirtest`, `cache`) is either +purely additive or rides the coordinated consumer release train in +[`working-with-state.md`](working-with-state.md) §6 — no tag until every +ported consumer is green against the candidate SHA. + +## Known dependency cost + +`byteness/keyring` compiles its 1Password openers (and transitively wazero / +jaeger) into every consumer — documented in +[`working-with-secrets.md`](working-with-secrets.md) §1.10; remediation +tracked in cli-common#57. diff --git a/docs/output-and-rendering.md b/docs/output-and-rendering.md index 8aa5ccb..ba4d512 100644 --- a/docs/output-and-rendering.md +++ b/docs/output-and-rendering.md @@ -239,7 +239,7 @@ The new docs are forward-looking. The following current divergences from this st - **Resource-read JSON is widespread.** slck, sfdc, nrq, cfl all expose JSON on resource reads via global `-o json`; gro exposes it via per-command `--json`. Only jtk holds the §2 line (reserves JSON for `automation export`). New CLIs MUST NOT add resource-read JSON. - **gro has no root `--no-color` flag** — `google-readonly/internal/cmd/root/root.go:107-109` registers a global `--verbose` and the credstore backend flag but no color flag. Its lipgloss styling at `google-readonly/internal/view/view.go:34` honors the `NO_COLOR` env natively, which papers over the gap for users who set the env, but the missing flag is a divergence from §8. -- **isatty gating is expected via library defaults but unverified per CLI.** §8 (as amended 2026-06-11) requires color to auto-disable on non-TTY output; fatih/color and lipgloss do this by default, so conformance is expected unless a CLI overrides it — a per-CLI verification pass is tracked in cli-common#55. +- **isatty gating: verified family-wide 2026-06-11** (cli-common#55). §8 requires color to auto-disable on non-TTY output. Audited every color path — color-package imports plus raw-ANSI-literal sweep of non-test source — across the family: atlassian-cli@af0b0b0 (jtk+cfl, all color in `shared/view/view.go`), google-readonly@d61bbc2, newrelic-cli@ed6e6a4, slack-chat-api@edf1aa0 (no color paths at all), salesforce-cli@f62ccc8. No raw ANSI writers, no `color.NoColor = false` / `EnableColor` / forced renderer profiles anywhere; every override found is a *disable* (`--no-color` plumbing; gro additionally drops to `termenv.Ascii`). Library TTY-detection defaults are therefore in effect in all six CLIs — conformant with §8's **isatty-gating requirement** specifically (gro's missing root `--no-color` flag, above, remains an open §8 divergence). - **No CLI has output goldens.** Per §9.5 this is recommended-not-normative pending the cli-common helper. Command-surface divergences (init flag-skip failures, missing `set-credential`) are catalogued in `command-surface.md` §9. Scriptability divergences (missing `--non-interactive`, `me` not exiting non-zero) are catalogued in `scriptability.md` §9. diff --git a/docs/working-with-secrets.md b/docs/working-with-secrets.md index 31d427c..88b3316 100644 --- a/docs/working-with-secrets.md +++ b/docs/working-with-secrets.md @@ -318,6 +318,8 @@ In automation, prefer `set-credential` per-secret over `init` for everything: it A note on what credstore exposes: as of #24, `credstore` recognizes six backend names — `keychain`, `wincred`, `secret-service`, `file`, `pass`, `memory`. `pass` is the only external secret manager exposed natively; it shells out to the `pass` CLI binary and has no Go SDK dependencies. KeePassXC users get native runtime resolution today through Secret Service (no separate backend needed). 1Password native backends are deliberately not exposed: ByteNess's `op` / `op-connect` / `op-desktop` openers all depend on the upstream `github.com/1password/onepassword-sdk-go` package, which is still pre-1.0 — exposing them here would put a beta SDK on the credential-access critical path. The "default path" above remains the recommendation for most users; `pass` is an opt-in alternative for users who specifically want runtime resolution and accept the per-backend availability/version coupling. +**Known dependency cost (documented trade-off).** Not exposing the 1Password backends does not remove their code: `byteness/keyring` imports its op openers unconditionally, so the 1Password SDKs — and transitively a WASM runtime (`wazero` via `extism`) and the archived `jaeger-client-go` — compile into every credstore consumer. Measured 2026-06-11 against keyring v1.9.3 on a real consumer binary (`slck`): 63 packages in the import graph, ~10.6 MB of attributable symbols, no dead-code elimination (the openers are `init()`-registered). The accepted interim posture is this documented cost; the remediation — an upstream opt-out build tag in ByteNess/keyring — is committed in cli-common#57, and when it lands the consumer build flag becomes part of this standard's build configuration. + ## §1.11 Compliance criteria A CLI is compliant with this standard when all of the following are true at runtime, observable from the user's perspective: