From d69cb0ede435a96351650a05ed3108d6f1e9687f Mon Sep 17 00:00:00 2001 From: prode Date: Sun, 31 May 2026 14:54:27 -0300 Subject: [PATCH] fix(docs): update usage messages to reflect npx command format --- cmd/agent.go | 6 +- cmd/cmd.go | 53 ++++-- cmd/cmd_test.go | 30 +++ cmd/export.go | 2 +- cmd/init.go | 2 +- cmd/mcp.go | 8 +- cmd/skill.go | 10 +- cmd/spec.go | 18 +- cmd/steering.go | 6 +- .../templates/guides/claude-code-sdd.md.tmpl | 6 +- .../templater/templates/root/CLAUDE.md.tmpl | 6 +- .../templater/templates/root/csdd.md.tmpl | 178 +++++++++--------- npm/csdd/bin/csdd.js | 17 +- 13 files changed, 200 insertions(+), 142 deletions(-) diff --git a/cmd/agent.go b/cmd/agent.go index f2367ff..90dd741 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -64,7 +64,7 @@ func agentCreate(args []string, templates embed.FS) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd agent create NAME --description '...'") + render.Err("usage: " + prog() + " agent create NAME --description '...'") return 1 } opts.Name = positionals[0] @@ -176,7 +176,7 @@ func agentShow(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd agent show NAME") + render.Err("usage: " + prog() + " agent show NAME") return 1 } r, err := workspace.Resolve(root) @@ -206,7 +206,7 @@ func agentDelete(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd agent delete NAME --force") + render.Err("usage: " + prog() + " agent delete NAME --force") return 1 } if !force { diff --git a/cmd/cmd.go b/cmd/cmd.go index 45ae3e6..935e093 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -94,17 +94,29 @@ func normalizeLeadingGlobalFlags(args []string) ([]string, error) { return args, nil } +// prog is the program name echoed in help text and usage/guidance messages. +// The npm launcher sets CSDD_PROG to "npx @protonspy/csdd" when csdd is run via +// npx, so the help reflects the exact command the user typed; a global install +// or a plain `go`-built binary falls back to the bare name "csdd". +func prog() string { + if p := os.Getenv("CSDD_PROG"); p != "" { + return p + } + return "csdd" +} + func help(w *os.File) { - fmt.Fprintln(w, strings.TrimSpace(helpText)) + fmt.Fprintln(w, strings.TrimSpace(helpText())) } -const helpText = ` +func helpText() string { + return fmt.Sprintf(` csdd — manage Claude Code workflow artifacts (steering, specs, skills, custom agents). USAGE - csdd Launch the interactive TUI. - csdd tui Launch the TUI explicitly. - csdd [flags] + %[1]s Launch the interactive TUI. + %[1]s tui Launch the TUI explicitly. + %[1]s [flags] RESOURCES init Bootstrap a Claude Code workspace. @@ -122,25 +134,26 @@ GLOBAL FLAGS -v, --version Show version. EXAMPLES - csdd init --with-baseline - csdd steering create api-conventions \ + %[1]s init --with-baseline + %[1]s steering create api-conventions \ --inclusion fileMatch --pattern 'src/api/**/*' --pattern '**/*Controller.*' - csdd steering create observability \ + %[1]s steering create observability \ --inclusion auto --description 'Logging/metrics. Use when adding instrumentation.' - csdd spec init photo-albums - csdd spec generate photo-albums --artifact requirements - csdd spec approve photo-albums --phase requirements - csdd skill create spec-tasks \ + %[1]s spec init photo-albums + %[1]s spec generate photo-albums --artifact requirements + %[1]s spec approve photo-albums --phase requirements + %[1]s skill create spec-tasks \ --description 'Generate tasks.md with boundary/depends annotations.' # .claude/skills/ - csdd agent create code-reviewer \ + %[1]s agent create code-reviewer \ --description 'Read-only adversarial reviewer' --tools Read --tools Grep # .claude/agents/ - csdd mcp add filesystem \ + %[1]s mcp add filesystem \ --command npx --arg -y --arg '@modelcontextprotocol/server-filesystem' --arg . # .mcp.json - csdd mcp add linear --url https://mcp.linear.app/mcp --type http - csdd mcp validate - csdd export kiro # .kiro/steering + .kiro/specs - csdd export codex --out ./build # AGENTS.md + .codex/config.toml -` + %[1]s mcp add linear --url https://mcp.linear.app/mcp --type http + %[1]s mcp validate + %[1]s export kiro # .kiro/steering + .kiro/specs + %[1]s export codex --out ./build # AGENTS.md + .codex/config.toml +`, prog()) +} // parseFlags wraps fs.Parse to allow positional arguments to appear before // flags (e.g., `csdd steering create api-conventions --inclusion always`). @@ -189,7 +202,7 @@ func addForce(fs *flag.FlagSet, dst *bool) { // parseAction extracts the action subcommand and returns the remaining args. func parseAction(resource string, args []string) (string, []string, error) { if len(args) == 0 { - return "", nil, fmt.Errorf("missing action for `csdd %s`", resource) + return "", nil, fmt.Errorf("missing action for `%s %s`", prog(), resource) } return args[0], args[1:], nil } diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 7a801e7..c124f24 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -67,6 +67,36 @@ func TestHelpAndVersion(t *testing.T) { } } +// TestProgName covers the dynamic program name: help and usage strings echo the +// bare binary name by default, but switch to whatever CSDD_PROG holds (the npm +// launcher sets it to "npx @protonspy/csdd" for npx invocations). +func TestProgName(t *testing.T) { + t.Run("default is bare csdd", func(t *testing.T) { + t.Setenv("CSDD_PROG", "") + _, help, _ := run(t, "--help") + if !strings.Contains(help, "csdd spec generate") { + t.Errorf("help should show bare csdd commands, got %q", help) + } + if strings.Contains(help, "npx @protonspy/csdd") { + t.Errorf("help should not show the npx prefix by default, got %q", help) + } + if _, _, errOut := run(t, "spec", "init"); !strings.Contains(errOut, "usage: csdd spec init") { + t.Errorf("usage should show bare csdd, got %q", errOut) + } + }) + + t.Run("CSDD_PROG is echoed verbatim", func(t *testing.T) { + t.Setenv("CSDD_PROG", "npx @protonspy/csdd") + _, help, _ := run(t, "--help") + if !strings.Contains(help, "npx @protonspy/csdd spec generate") { + t.Errorf("help should echo CSDD_PROG, got %q", help) + } + if _, _, errOut := run(t, "spec", "init"); !strings.Contains(errOut, "usage: npx @protonspy/csdd spec init") { + t.Errorf("usage should echo CSDD_PROG, got %q", errOut) + } + }) +} + func TestUnknownResource(t *testing.T) { code, _, errOut := run(t, "nonsense") if code == 0 { diff --git a/cmd/export.go b/cmd/export.go index cf017db..566fb84 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -45,7 +45,7 @@ func runExport(args []string) int { target, rest, err := parseAction("export", args) if err != nil { render.Err(err.Error()) - render.Info("usage: csdd export {kiro|codex} [--out DIR] [--force]") + render.Info("usage: " + prog() + " export {kiro|codex} [--out DIR] [--force]") return 1 } fs := flag.NewFlagSet("export "+target, flag.ContinueOnError) diff --git a/cmd/init.go b/cmd/init.go index aee0f0a..2efec27 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -50,7 +50,7 @@ func runInit(args []string, templates embed.FS) int { offerGitignore(root) render.Info("Enable the pre-push test gate: `git config core.hooksPath .githooks`") if !withBaseline { - render.Info("Run `csdd steering init` to scaffold standard steering files.") + render.Info("Run `" + prog() + " steering init` to scaffold standard steering files.") } return 0 } diff --git a/cmd/mcp.go b/cmd/mcp.go index 41d1526..1f1ecb8 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -138,7 +138,7 @@ func mcpAdd(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd mcp add NAME (--command CMD [--arg A]... | --url URL [--type sse|http]) [--env K=V]... [--disabled] [--force]") + render.Err("usage: " + prog() + " mcp add NAME (--command CMD [--arg A]... | --url URL [--type sse|http]) [--env K=V]... [--disabled] [--force]") return 1 } opts.Name = positionals[0] @@ -320,7 +320,7 @@ func mcpRemove(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd mcp remove NAME --force") + render.Err("usage: " + prog() + " mcp remove NAME --force") return 1 } name := positionals[0] @@ -369,7 +369,7 @@ func mcpToggle(args []string, disabled bool) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd mcp " + verb + " NAME") + render.Err("usage: " + prog() + " mcp " + verb + " NAME") return 1 } name := positionals[0] @@ -496,7 +496,7 @@ func mcpResolveLoadNamed(args []string, action string) (mcpResult, string, int) return res, "", code } if len(positionals) < 1 { - render.Err("usage: csdd mcp " + action + " NAME") + render.Err("usage: " + prog() + " mcp " + action + " NAME") return res, "", 1 } return res, positionals[0], 0 diff --git a/cmd/skill.go b/cmd/skill.go index b1d99e6..fab48e5 100644 --- a/cmd/skill.go +++ b/cmd/skill.go @@ -66,7 +66,7 @@ func skillCreate(args []string, templates embed.FS) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd skill create NAME --description '...'") + render.Err("usage: " + prog() + " skill create NAME --description '...'") return 1 } opts.Name = positionals[0] @@ -179,7 +179,7 @@ func skillShow(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd skill show NAME") + render.Err("usage: " + prog() + " skill show NAME") return 1 } r, err := workspace.Resolve(root) @@ -217,7 +217,7 @@ func skillAddArtifact(args []string, subdir string) int { return failOnFlagParse(err) } if len(positionals) < 2 { - render.Err("usage: csdd skill add-" + subdir + " SKILL FILE") + render.Err("usage: " + prog() + " skill add-" + subdir + " SKILL FILE") return 1 } r, err := workspace.Resolve(root) @@ -304,7 +304,7 @@ func skillValidate(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd skill validate NAME") + render.Err("usage: " + prog() + " skill validate NAME") return 1 } r, err := workspace.Resolve(root) @@ -340,7 +340,7 @@ func skillDelete(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd skill delete NAME --force") + render.Err("usage: " + prog() + " skill delete NAME --force") return 1 } if !force { diff --git a/cmd/spec.go b/cmd/spec.go index c44117f..82fae55 100644 --- a/cmd/spec.go +++ b/cmd/spec.go @@ -73,7 +73,7 @@ func specInit(args []string, templates embed.FS) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd spec init FEATURE") + render.Err("usage: " + prog() + " spec init FEATURE") return 1 } r, err := workspace.Resolve(root) @@ -113,7 +113,7 @@ func specInit(args []string, templates embed.FS) int { return 1 } render.OK("created " + workspace.Relative(r, target) + "/") - render.Info("next: `csdd spec generate --artifact requirements`") + render.Info(fmt.Sprintf("next: `%s spec generate --artifact requirements`", prog())) return 0 } @@ -192,7 +192,7 @@ func specShow(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd spec show FEATURE") + render.Err("usage: " + prog() + " spec show FEATURE") return 1 } r, err := workspace.Resolve(root) @@ -276,7 +276,7 @@ func specGenerate(args []string, templates embed.FS) int { return failOnFlagParse(err) } if len(positionals) < 1 || opts.Artifact == "" { - render.Err("usage: csdd spec generate FEATURE --artifact {requirements|design|tasks|research|bugfix}") + render.Err("usage: " + prog() + " spec generate FEATURE --artifact {requirements|design|tasks|research|bugfix}") return 1 } opts.Feature = positionals[0] @@ -298,7 +298,7 @@ func SpecGenerate(templates embed.FS, opts SpecGenerateOptions) error { } sdir := filepath.Join(paths.Specs(r), opts.Feature) if !pathExists(sdir) { - return fmt.Errorf("spec not found: %s. Run `csdd spec init %s` first", opts.Feature, opts.Feature) + return fmt.Errorf("spec not found: %s. Run `%s spec init %s` first", opts.Feature, prog(), opts.Feature) } data, err := loadSpecJSON(sdir) if err != nil { @@ -381,7 +381,7 @@ func specApprove(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 || opts.Phase == "" { - render.Err("usage: csdd spec approve FEATURE --phase {requirements|design|tasks}") + render.Err("usage: " + prog() + " spec approve FEATURE --phase {requirements|design|tasks}") return 1 } opts.Feature = positionals[0] @@ -473,7 +473,7 @@ func specValidate(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd spec validate FEATURE") + render.Err("usage: " + prog() + " spec validate FEATURE") return 1 } r, err := workspace.Resolve(root) @@ -519,7 +519,7 @@ func validationScope(specDir string, data SpecJSON) (validator.Phase, []validato } return validator.PhaseAll, []validator.Issue{{ File: "spec.json", - Msg: "no generated artifacts to validate; run `csdd spec generate --artifact requirements` first", + Msg: fmt.Sprintf("no generated artifacts to validate; run `%s spec generate --artifact requirements` first", prog()), }} } @@ -534,7 +534,7 @@ func specDelete(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd spec delete FEATURE --force") + render.Err("usage: " + prog() + " spec delete FEATURE --force") return 1 } feature := positionals[0] diff --git a/cmd/steering.go b/cmd/steering.go index 504359d..6f8aff0 100644 --- a/cmd/steering.go +++ b/cmd/steering.go @@ -120,7 +120,7 @@ func steeringCreate(args []string, templates embed.FS) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd steering create NAME --inclusion {always|fileMatch|manual|auto} [...]") + render.Err("usage: " + prog() + " steering create NAME --inclusion {always|fileMatch|manual|auto} [...]") return 1 } opts.Name = positionals[0] @@ -265,7 +265,7 @@ func steeringShow(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd steering show NAME") + render.Err("usage: " + prog() + " steering show NAME") return 1 } r, err := workspace.Resolve(root) @@ -299,7 +299,7 @@ func steeringDelete(args []string) int { return failOnFlagParse(err) } if len(positionals) < 1 { - render.Err("usage: csdd steering delete NAME") + render.Err("usage: " + prog() + " steering delete NAME") return 1 } name := positionals[0] diff --git a/internal/templater/templates/guides/claude-code-sdd.md.tmpl b/internal/templater/templates/guides/claude-code-sdd.md.tmpl index b77fcdc..cd0995f 100644 --- a/internal/templater/templates/guides/claude-code-sdd.md.tmpl +++ b/internal/templater/templates/guides/claude-code-sdd.md.tmpl @@ -166,7 +166,7 @@ Skills, agent prompts, and steering content SHALL be grounded in real expertise ## 4. Repository structure -Every project adopting this workflow SHALL have the following layout, scaffolded by `csdd init`. +Every project adopting this workflow SHALL have the following layout, scaffolded by `npx @protonspy/csdd init`. ```text repo/ @@ -269,7 +269,7 @@ In Claude Code, steering is loaded as **always-on project memory via `@`-imports Every imported file loads **always**, on every session — there is no native conditional `fileMatch`/`auto` loading of arbitrary markdown in Claude Code. -> **IMPORTANT semantic note on inclusion modes.** `csdd steering create --inclusion {always|fileMatch|auto|manual}` still exists, and the `inclusion:` / `fileMatchPattern:` / `name:` / `description:` frontmatter is still produced and still validated. Under Claude Code, however, this frontmatter is **advisory metadata documenting the file's intended scope** — it is *not* a runtime loading mechanism. Everything imported into `CLAUDE.md` loads always, regardless of `inclusion:` value. Use the frontmatter to communicate intent to humans and to keep portability with other tools; do not expect `fileMatch` to change what Claude Code actually loads. If you want a steering file to load only on demand, do not import it into `CLAUDE.md` and instead reference it explicitly with `@.claude/steering/.md` in a prompt when you need it. +> **IMPORTANT semantic note on inclusion modes.** `npx @protonspy/csdd steering create --inclusion {always|fileMatch|auto|manual}` still exists, and the `inclusion:` / `fileMatchPattern:` / `name:` / `description:` frontmatter is still produced and still validated. Under Claude Code, however, this frontmatter is **advisory metadata documenting the file's intended scope** — it is *not* a runtime loading mechanism. Everything imported into `CLAUDE.md` loads always, regardless of `inclusion:` value. Use the frontmatter to communicate intent to humans and to keep portability with other tools; do not expect `fileMatch` to change what Claude Code actually loads. If you want a steering file to load only on demand, do not import it into `CLAUDE.md` and instead reference it explicitly with `@.claude/steering/.md` in a prompt when you need it. ### 5.2 Foundational files @@ -2126,7 +2126,7 @@ Do not change production implementation without asking for confirmation. ### Phase 1 — Foundations (week 1) -- [ ] Run `csdd init` to scaffold `CLAUDE.md`, `.claude/`, `specs/`, rules, templates, skills, and this guide. +- [ ] Run `npx @protonspy/csdd init` to scaffold `CLAUDE.md`, `.claude/`, `specs/`, rules, templates, skills, and this guide. - [ ] Configure `.gitignore` and permission deny rules for protected paths. - [ ] Generate foundational steering: `product.md`, `tech.md`, `structure.md`; confirm they are imported into `CLAUDE.md`. - [ ] Configure required MCP servers at the correct scope (project vs user). diff --git a/internal/templater/templates/root/CLAUDE.md.tmpl b/internal/templater/templates/root/CLAUDE.md.tmpl index 1490a85..c88b8df 100644 --- a/internal/templater/templates/root/CLAUDE.md.tmpl +++ b/internal/templater/templates/root/CLAUDE.md.tmpl @@ -33,7 +33,7 @@ between the markers. ## Workflow at a glance -1. `csdd spec init ` then iterate requirements → design → tasks. +1. `npx @protonspy/csdd spec init ` then iterate requirements → design → tasks. 2. Each phase requires explicit human approval recorded in `spec.json`. 3. `ready_for_implementation` flips to `true` only after all three phases are approved. 4. Implementation runs one task per iteration with fresh-context sub-agents (implementer → reviewer → debugger), TDD red→green per task. @@ -51,5 +51,5 @@ between the markers. ## Reference - Operational guide for agents: [`csdd.md`](./csdd.md) -- Canonical spec: `docs/guides/claude-code-sdd.md` — the self-contained SDD+TDD guide (scaffolded by `csdd init`) -- CLI help: `csdd --help` +- Canonical spec: `docs/guides/claude-code-sdd.md` — the self-contained SDD+TDD guide (scaffolded by `npx @protonspy/csdd init`) +- CLI help: `npx @protonspy/csdd --help` diff --git a/internal/templater/templates/root/csdd.md.tmpl b/internal/templater/templates/root/csdd.md.tmpl index 0d93c97..9c4111b 100644 --- a/internal/templater/templates/root/csdd.md.tmpl +++ b/internal/templater/templates/root/csdd.md.tmpl @@ -18,9 +18,9 @@ | **agent** | `.claude/agents/.md` | Custom sub-agent definition with least-privilege tool scope. | | **mcp** | `.mcp.json` | Model Context Protocol servers the agent may connect to. | -`csdd` is **the only sanctioned author** of these files. Do not hand-edit frontmatter, `spec.json`, or task annotations to bypass validation. If you need a change, regenerate from template (`csdd spec generate --force`) and edit the body, then re-validate. +`csdd` is **the only sanctioned author** of these files. Do not hand-edit frontmatter, `spec.json`, or task annotations to bypass validation. If you need a change, regenerate from template (`npx @protonspy/csdd spec generate --force`) and edit the body, then re-validate. -The full spec this CLI implements lives at `docs/guides/claude-code-sdd.md` (scaffolded by `csdd init`) — a single, self-contained guide and the canonical reference. **This document is the operational subset you need to use the tool.** +The full spec this CLI implements lives at `docs/guides/claude-code-sdd.md` (scaffolded by `npx @protonspy/csdd init`) — a single, self-contained guide and the canonical reference. **This document is the operational subset you need to use the tool.** --- @@ -41,62 +41,62 @@ Discovery → Requirements → Design → Tasks → Implementation human gate human gate human gate ``` -The CLI enforces this mechanically. `csdd spec generate --artifact design` will fail if `requirements` is not approved. Use `--force` **only** when the user explicitly authorizes a Quick Plan / fast-track flow. +The CLI enforces this mechanically. `npx @protonspy/csdd spec generate --artifact design` will fail if `requirements` is not approved. Use `--force` **only** when the user explicitly authorizes a Quick Plan / fast-track flow. --- ## 2. Cheat sheet — every command -Run `csdd --help` for the top-level surface. The subcommands you will actually use: +Run `npx @protonspy/csdd --help` for the top-level surface. The subcommands you will actually use: ```bash # bootstrap (one time per repo) -csdd init [--root PATH] [--with-baseline] +npx @protonspy/csdd init [--root PATH] [--with-baseline] # steering (project memory) -csdd steering init [--root PATH] -csdd steering create NAME --inclusion {always|fileMatch|manual|auto} \ +npx @protonspy/csdd steering init [--root PATH] +npx @protonspy/csdd steering create NAME --inclusion {always|fileMatch|manual|auto} \ [--pattern GLOB]... [--description TEXT] [--title TEXT] [--force] -csdd steering list [--inclusion MODE] -csdd steering show NAME -csdd steering delete NAME [--force] -csdd steering validate [NAME] +npx @protonspy/csdd steering list [--inclusion MODE] +npx @protonspy/csdd steering show NAME +npx @protonspy/csdd steering delete NAME [--force] +npx @protonspy/csdd steering validate [NAME] # specs -csdd spec init FEATURE [--language en] -csdd spec list -csdd spec show FEATURE -csdd spec status FEATURE # show + validate -csdd spec generate FEATURE --artifact {requirements|design|tasks|research|bugfix} [--force] -csdd spec approve FEATURE --phase {requirements|design|tasks} [--force] -csdd spec validate FEATURE -csdd spec delete FEATURE --force +npx @protonspy/csdd spec init FEATURE [--language en] +npx @protonspy/csdd spec list +npx @protonspy/csdd spec show FEATURE +npx @protonspy/csdd spec status FEATURE # show + validate +npx @protonspy/csdd spec generate FEATURE --artifact {requirements|design|tasks|research|bugfix} [--force] +npx @protonspy/csdd spec approve FEATURE --phase {requirements|design|tasks} [--force] +npx @protonspy/csdd spec validate FEATURE +npx @protonspy/csdd spec delete FEATURE --force # skills -csdd skill create NAME --description TEXT [--title TEXT] -csdd skill list -csdd skill show NAME -csdd skill add-reference SKILL FILE -csdd skill add-script SKILL FILE -csdd skill add-asset SKILL FILE -csdd skill validate NAME -csdd skill delete NAME --force +npx @protonspy/csdd skill create NAME --description TEXT [--title TEXT] +npx @protonspy/csdd skill list +npx @protonspy/csdd skill show NAME +npx @protonspy/csdd skill add-reference SKILL FILE +npx @protonspy/csdd skill add-script SKILL FILE +npx @protonspy/csdd skill add-asset SKILL FILE +npx @protonspy/csdd skill validate NAME +npx @protonspy/csdd skill delete NAME --force # custom sub-agents -csdd agent create NAME --description TEXT [--tools TOOL]... [--model M] [--title T] [--force] -csdd agent list -csdd agent show NAME -csdd agent delete NAME --force +npx @protonspy/csdd agent create NAME --description TEXT [--tools TOOL]... [--model M] [--title T] [--force] +npx @protonspy/csdd agent list +npx @protonspy/csdd agent show NAME +npx @protonspy/csdd agent delete NAME --force # mcp servers (.mcp.json) -csdd mcp add NAME --command CMD [--arg A]... [--env K=V]... [--disabled] [--force] # stdio -csdd mcp add NAME --url URL [--type sse|http] [--env K=V]... [--disabled] [--force] # remote -csdd mcp list -csdd mcp show NAME -csdd mcp enable NAME -csdd mcp disable NAME -csdd mcp remove NAME --force -csdd mcp validate +npx @protonspy/csdd mcp add NAME --command CMD [--arg A]... [--env K=V]... [--disabled] [--force] # stdio +npx @protonspy/csdd mcp add NAME --url URL [--type sse|http] [--env K=V]... [--disabled] [--force] # remote +npx @protonspy/csdd mcp list +npx @protonspy/csdd mcp show NAME +npx @protonspy/csdd mcp enable NAME +npx @protonspy/csdd mcp disable NAME +npx @protonspy/csdd mcp remove NAME --force +npx @protonspy/csdd mcp validate ``` **Global flags (every command):** `--root PATH` overrides the auto-detected workspace; `--force` overrides safety checks; `-h` / `--help` prints usage; `-v` / `--version` prints version. Exit codes: `0` OK, `1` user error, `2` validation failure. @@ -109,39 +109,39 @@ csdd mcp validate ```bash # 1. Bootstrap (only on a fresh repo) -csdd init --with-baseline +npx @protonspy/csdd init --with-baseline # 2. Create the feature workspace -csdd spec init photo-albums +npx @protonspy/csdd spec init photo-albums # 3. Draft requirements -csdd spec generate photo-albums --artifact requirements +npx @protonspy/csdd spec generate photo-albums --artifact requirements # → opens specs/photo-albums/requirements.md # → edit it to capture real requirements in EARS phrasing (see §4) -csdd spec validate photo-albums # exit 2 means fix what it reports +npx @protonspy/csdd spec validate photo-albums # exit 2 means fix what it reports # 4. Hand off for human review and approval -csdd spec approve photo-albums --phase requirements +npx @protonspy/csdd spec approve photo-albums --phase requirements # 5. Draft design (gate-blocked until step 4 succeeds) -csdd spec generate photo-albums --artifact design +npx @protonspy/csdd spec generate photo-albums --artifact design # → edit design.md (architecture, boundary map, File Structure Plan, traceability) -csdd spec validate photo-albums -csdd spec approve photo-albums --phase design +npx @protonspy/csdd spec validate photo-albums +npx @protonspy/csdd spec approve photo-albums --phase design # 6. Draft tasks -csdd spec generate photo-albums --artifact tasks +npx @protonspy/csdd spec generate photo-albums --artifact tasks # → edit tasks.md with _Requirements:_ / _Boundary:_ / _Depends:_ / (P) (see §5) -csdd spec validate photo-albums -csdd spec approve photo-albums --phase tasks +npx @protonspy/csdd spec validate photo-albums +npx @protonspy/csdd spec approve photo-albums --phase tasks # spec.json now has ready_for_implementation: true — implementation can start ``` -**Use `csdd spec status ` between any two steps** to see phase + approvals + current validation issues in one shot. +**Use `npx @protonspy/csdd spec status ` between any two steps** to see phase + approvals + current validation issues in one shot. ### Variants -- **Bugfix Spec** — replace `requirements.md` with `bugfix.md`: `csdd spec generate FEATURE --artifact bugfix`. The phase gate is skipped (bugfix is off-chain). The validator requires the sections `Current Behavior:`, `Expected Behavior:`, `Unchanged Behavior:`, and `Root Cause`. Document the root cause **before** writing any code. -- **Research** — for brownfield or novel domains add `csdd spec generate FEATURE --artifact research` before design. Every finding in `research.md` must connect to a concrete commitment in `design.md`. +- **Bugfix Spec** — replace `requirements.md` with `bugfix.md`: `npx @protonspy/csdd spec generate FEATURE --artifact bugfix`. The phase gate is skipped (bugfix is off-chain). The validator requires the sections `Current Behavior:`, `Expected Behavior:`, `Unchanged Behavior:`, and `Root Cause`. Document the root cause **before** writing any code. +- **Research** — for brownfield or novel domains add `npx @protonspy/csdd spec generate FEATURE --artifact research` before design. Every finding in `research.md` must connect to a concrete commitment in `design.md`. - **No spec needed** — for small reversible changes (typos, doc tweaks, one-line bugfixes) skip the spec layer entirely. SDD is a contract layer, not a tax. --- @@ -259,7 +259,7 @@ Steering files live in `.claude/steering/*.md`. `csdd` imports them into `CLAUDE.md` as `@`-references inside the managed block between `` and ``, so they load as **always-on project memory** on every interaction. Do not hand-edit that block — -`csdd steering create` / `csdd steering init` keep it in sync. +`npx @protonspy/csdd steering create` / `npx @protonspy/csdd steering init` keep it in sync. ### Inclusion modes — advisory metadata @@ -280,18 +280,18 @@ validator still checks the frontmatter is well-formed. ```bash # always -csdd steering create security --inclusion always +npx @protonspy/csdd steering create security --inclusion always # fileMatch (--pattern is repeatable) -csdd steering create api-conventions --inclusion fileMatch \ +npx @protonspy/csdd steering create api-conventions --inclusion fileMatch \ --pattern 'src/api/**/*' --pattern '**/*Controller.*' # auto (--description is required and is the trigger phrase) -csdd steering create observability --inclusion auto \ +npx @protonspy/csdd steering create observability --inclusion auto \ --description 'Logging, metrics, tracing. Use when adding or modifying instrumentation.' # manual -csdd steering create regulated-domain --inclusion manual +npx @protonspy/csdd steering create regulated-domain --inclusion manual ``` ### What belongs / does not belong @@ -309,10 +309,10 @@ csdd steering create regulated-domain --inclusion manual ## 7. Skills — executable workflow bundles -Use `csdd skill` to create a reusable, well-scoped capability bundle the agent can load on demand. +Use `npx @protonspy/csdd skill` to create a reusable, well-scoped capability bundle the agent can load on demand. ```bash -csdd skill create spec-tasks \ +npx @protonspy/csdd skill create spec-tasks \ --description 'Generate tasks.md with boundary/depends annotations.' ``` @@ -348,10 +348,10 @@ This produces: ### Adding content ```bash -csdd skill add-reference spec-tasks api-errors.md -csdd skill add-script spec-tasks validate_ids.py # marked executable -csdd skill add-asset spec-tasks report-template.md -csdd skill validate spec-tasks +npx @protonspy/csdd skill add-reference spec-tasks api-errors.md +npx @protonspy/csdd skill add-script spec-tasks validate_ids.py # marked executable +npx @protonspy/csdd skill add-asset spec-tasks report-template.md +npx @protonspy/csdd skill validate spec-tasks ``` If `references/` exists but is not cited from `SKILL.md`, validation fails. Either cite it with a load trigger or delete the file. @@ -363,11 +363,11 @@ If `references/` exists but is not cited from `SKILL.md`, validation fails Custom agents live at `.claude/agents/.md` and define delegated, scoped roles (reviewer, debugger, security reviewer…). ```bash -csdd agent create code-reviewer \ +npx @protonspy/csdd agent create code-reviewer \ --description 'Read-only adversarial reviewer' \ --tools Read --tools Grep -csdd agent create security-reviewer \ +npx @protonspy/csdd agent create security-reviewer \ --description 'No-write security review of the current diff' \ --tools Read --tools Grep --tools Bash \ --model sonnet @@ -393,7 +393,7 @@ csdd agent create security-reviewer \ ## 9. MCP servers — workspace integrations -Model Context Protocol servers give the agent extra tools (filesystem access, issue trackers, search, internal APIs…). They are declared in `.mcp.json` and managed exclusively with `csdd mcp` — **do not hand-edit the JSON**; the CLI keeps it well-formed and reviewable. +Model Context Protocol servers give the agent extra tools (filesystem access, issue trackers, search, internal APIs…). They are declared in `.mcp.json` and managed exclusively with `npx @protonspy/csdd mcp` — **do not hand-edit the JSON**; the CLI keeps it well-formed and reviewable. ### Two transports @@ -408,18 +408,18 @@ A server is **either** stdio **or** remote — never both. The validator rejects ```bash # stdio server (npx-launched filesystem server scoped to the repo) -csdd mcp add filesystem \ +npx @protonspy/csdd mcp add filesystem \ --command npx --arg -y --arg '@modelcontextprotocol/server-filesystem' --arg . # remote server over SSE, carrying a token via env -csdd mcp add linear --url https://mcp.linear.app/sse --type sse --env LINEAR_API_KEY=$LINEAR_API_KEY - -csdd mcp list # name · transport · state · endpoint -csdd mcp show filesystem # the entry as stored -csdd mcp disable linear # keep the entry but stop loading it -csdd mcp enable linear -csdd mcp remove linear --force -csdd mcp validate # exit 2 on any structural problem +npx @protonspy/csdd mcp add linear --url https://mcp.linear.app/sse --type sse --env LINEAR_API_KEY=$LINEAR_API_KEY + +npx @protonspy/csdd mcp list # name · transport · state · endpoint +npx @protonspy/csdd mcp show filesystem # the entry as stored +npx @protonspy/csdd mcp disable linear # keep the entry but stop loading it +npx @protonspy/csdd mcp enable linear +npx @protonspy/csdd mcp remove linear --force +npx @protonspy/csdd mcp validate # exit 2 on any structural problem ``` `--env K=V` is repeatable. `--disabled` adds the server without activating it. `--force` replaces an existing entry of the same name. @@ -427,7 +427,7 @@ csdd mcp validate # exit 2 on any structural problem ### Least privilege - Prefer the **narrowest scope**: pass paths/args that limit a stdio server to what the task needs (e.g. a single directory, not `/`). -- Avoid `--auto-approve`. Auto-approving tools bypasses human review of each call; `csdd mcp add` warns when it is set and `csdd mcp show` surfaces it. +- Avoid `--auto-approve`. Auto-approving tools bypasses human review of each call; `npx @protonspy/csdd mcp add` warns when it is set and `npx @protonspy/csdd mcp show` surfaces it. - Never inline secrets into `--arg`. Use `--env` and reference environment variables; do not commit real tokens into `mcp.json`. - Disable rather than delete when you only need to pause a server, so its reviewed config is preserved. @@ -439,26 +439,26 @@ MCP server names are kebab-case (`filesystem`, `brave-search`, `linear`), like e Every `csdd * validate` runs **mechanical** checks (the validator never asks for judgment). Exit code `2` means there are issues; the agent must fix them before re-running. -### `csdd steering validate [NAME]` +### `npx @protonspy/csdd steering validate [NAME]` - `inclusion` frontmatter is one of `always|fileMatch|manual|auto`. - `fileMatch` files have a non-empty `fileMatchPattern`. - `auto` files have both `name` and `description`. -### `csdd spec validate ` — Requirements phase +### `npx @protonspy/csdd spec validate ` — Requirements phase - Every numbered criterion starts with `WHEN`/`WHILE`/`IF`/`WHERE`/`THE SYSTEM`. - No criterion uses `should`. - Requirement headers `### Requirement N:` are unique. -### `csdd spec validate ` — Design phase +### `npx @protonspy/csdd spec validate ` — Design phase - `## Architecture Pattern & Boundary Map` section present. - `## File Structure Plan` section present. - Every requirement ID from `requirements.md` appears in the `## Requirements Traceability` table. - `design.md` is **≤ 1000 lines** (over that, split the spec into multiple). -### `csdd spec validate ` — Tasks phase +### `npx @protonspy/csdd spec validate ` — Tasks phase - Every leaf task has `_Requirements:_` with numeric IDs only. - Every requirement ID referenced from a task exists in `requirements.md`. @@ -471,7 +471,7 @@ Every `csdd * validate` runs **mechanical** checks (the validator never asks for - `bugfix.md` contains `Current Behavior:`, `Expected Behavior:`, `Unchanged Behavior:`, and `Root Cause` sections. -### `csdd skill validate ` +### `npx @protonspy/csdd skill validate ` - Frontmatter `name` matches the directory. - Frontmatter `description` is non-empty. @@ -479,7 +479,7 @@ Every `csdd * validate` runs **mechanical** checks (the validator never asks for - All required headings present (§7). - Every file in `references/` is mentioned by name in `SKILL.md`. -### `csdd mcp validate` +### `npx @protonspy/csdd mcp validate` - `mcp.json` is well-formed JSON. - Every server sets exactly one transport: `command` (stdio) **or** `url` (remote), never both or neither. @@ -499,10 +499,10 @@ Every `csdd * validate` runs **mechanical** checks (the validator never asks for | `task N _Boundary: X_ does not match any component in design.md` | Either fix the boundary name to match a `### X` under `## Components and Interfaces`, or add that component to `design.md`. | | `parallel tasks A and B share boundary 'X'` | Remove the `(P)` from one of them, or move it into a different boundary. | | `task N _Depends:_ references unknown task 'Y.Z'` | Fix the ID to point at an existing task, or remove the dependency. | -| `phase gate: 'X' must be approved before generating 'Y'` | Run `csdd spec approve --phase X` first. Use `--force` only if the user explicitly authorizes Quick Plan. | +| `phase gate: 'X' must be approved before generating 'Y'` | Run `npx @protonspy/csdd spec approve --phase X` first. Use `--force` only if the user explicitly authorizes Quick Plan. | | `SKILL.md ~N tokens (>5000)` | Move detailed content into `references/` files and add load triggers in `SKILL.md`. | | `references/X.md: not mentioned in SKILL.md` | Cite the file with a load trigger ("Read references/X.md when …") or delete it. | -| `auto inclusion requires --description` | Re-run `csdd steering create` with `--description ""`. | +| `auto inclusion requires --description` | Re-run `npx @protonspy/csdd steering create` with `--description ""`. | | `fileMatch requires non-empty fileMatchPattern` | Re-run with one or more `--pattern ''`. | | `server 'X' has neither command nor url` | Re-add with `--command` (stdio) or `--url` + `--type` (remote). | | `server 'X' sets both command and url` | Pick one transport; re-add the server with only `--command` or only `--url`. | @@ -520,7 +520,7 @@ Every `csdd * validate` runs **mechanical** checks (the validator never asks for - **Mixing exploration with production-impacting changes** in the same session — promote to a spec first. - **Putting secrets, tokens, or customer data** in steering or specs (the agent reads these on every turn). - **Skipping the "why"** when adding a rule to steering — without rationale future reviewers can't judge edge cases. -- **Hand-editing `spec.json`** to mark a phase approved. Use `csdd spec approve`. +- **Hand-editing `spec.json`** to mark a phase approved. Use `npx @protonspy/csdd spec approve`. - **Building skills from generic LLM output** — skills are grounded in real expertise (a hands-on session, runbooks, code-review history). - **Authorizing broad tool scope** to a custom agent ("--tools Read --tools Write --tools Bash" without need). - **Adding MCP servers with broad scope or `--auto-approve`** — scope each server to the task and keep tool approvals human-reviewed. @@ -545,12 +545,12 @@ The CLI never blocks you from doing the right thing — it blocks you from doing ## 14. One-page summary -1. **Always start from a template** — `csdd spec generate`, `csdd steering create`, `csdd skill create`, `csdd agent create`. Never hand-author from scratch. -2. **Always validate** — `csdd spec validate`, `csdd skill validate`, `csdd steering validate`. Fix every `✗` before continuing. +1. **Always start from a template** — `npx @protonspy/csdd spec generate`, `npx @protonspy/csdd steering create`, `npx @protonspy/csdd skill create`, `npx @protonspy/csdd agent create`. Never hand-author from scratch. +2. **Always validate** — `npx @protonspy/csdd spec validate`, `npx @protonspy/csdd skill validate`, `npx @protonspy/csdd steering validate`. Fix every `✗` before continuing. 3. **Always respect phase gates** — requirements → design → tasks → implementation. No skipping without explicit human authorization. 4. **Always use EARS** for acceptance criteria. `SHALL`, not `should`. One behavior per criterion. 5. **Always annotate tasks** — `_Requirements:_` on leaves, `_Boundary:_` on parallel work, `_Depends:_` across boundaries only. 6. **Always apply least privilege** — agents get `Read, Grep` by default; add tools only when justified. 7. **Always keep `SKILL.md` under 500 lines / 5k tokens** — push detail into `references/` with explicit triggers. -If you forget everything else, remember: **the validator is your friend**. Run `csdd spec status ` between every step. +If you forget everything else, remember: **the validator is your friend**. Run `npx @protonspy/csdd spec status ` between every step. diff --git a/npm/csdd/bin/csdd.js b/npm/csdd/bin/csdd.js index 94d565c..62daab5 100644 --- a/npm/csdd/bin/csdd.js +++ b/npm/csdd/bin/csdd.js @@ -38,7 +38,22 @@ try { process.exit(1); } -const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" }); +// When invoked via `npx @protonspy/csdd` (which is `npm exec` under the hood), +// echo that exact spelling in the binary's --help / usage output. A global +// install runs this same launcher as the bare `csdd` command — there npm is not +// in the picture (npm_command is unset), so the binary keeps its default name. +// An explicit CSDD_PROG always wins. +const env = { ...process.env }; +if (!env.CSDD_PROG) { + const argv1 = process.argv[1] || ""; + const viaNpx = + process.env.npm_command === "exec" || + argv1.includes("/_npx/") || + argv1.includes("\\_npx\\"); + if (viaNpx) env.CSDD_PROG = "npx @protonspy/csdd"; +} + +const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit", env }); // Forward terminating signals so the TUI shuts down cleanly. for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {