@elmundi/ship-cli 0.8.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +415 -22
  2. package/bin/shipctl.mjs +165 -0
  3. package/lib/adapters/_fs.mjs +165 -0
  4. package/lib/adapters/agents/index.mjs +26 -0
  5. package/lib/adapters/ci/azure-pipelines.mjs +23 -0
  6. package/lib/adapters/ci/buildkite.mjs +24 -0
  7. package/lib/adapters/ci/circleci.mjs +23 -0
  8. package/lib/adapters/ci/gh-actions.mjs +29 -0
  9. package/lib/adapters/ci/gitlab-ci.mjs +23 -0
  10. package/lib/adapters/ci/jenkins.mjs +23 -0
  11. package/lib/adapters/ci/manual.mjs +18 -0
  12. package/lib/adapters/index.mjs +122 -0
  13. package/lib/adapters/language/dart.mjs +23 -0
  14. package/lib/adapters/language/go.mjs +23 -0
  15. package/lib/adapters/language/java.mjs +27 -0
  16. package/lib/adapters/language/js.mjs +32 -0
  17. package/lib/adapters/language/kotlin.mjs +48 -0
  18. package/lib/adapters/language/py.mjs +34 -0
  19. package/lib/adapters/language/rust.mjs +23 -0
  20. package/lib/adapters/language/swift.mjs +37 -0
  21. package/lib/adapters/language/ts.mjs +35 -0
  22. package/lib/adapters/trackers/azure-boards.mjs +49 -0
  23. package/lib/adapters/trackers/clickup.mjs +43 -0
  24. package/lib/adapters/trackers/github-issues.mjs +52 -0
  25. package/lib/adapters/trackers/jira.mjs +72 -0
  26. package/lib/adapters/trackers/linear.mjs +62 -0
  27. package/lib/adapters/trackers/none.mjs +18 -0
  28. package/lib/adapters/trackers/spreadsheet.mjs +28 -0
  29. package/lib/artifacts/fs-index.mjs +230 -0
  30. package/lib/bootstrap/render.mjs +373 -0
  31. package/lib/cache/store.mjs +422 -0
  32. package/lib/commands/bootstrap.mjs +4 -0
  33. package/lib/commands/callback.mjs +302 -0
  34. package/lib/commands/config.mjs +257 -0
  35. package/lib/commands/docs.mjs +1 -1
  36. package/lib/commands/doctor.mjs +583 -0
  37. package/lib/commands/feedback.mjs +355 -0
  38. package/lib/commands/help.mjs +96 -21
  39. package/lib/commands/init.mjs +830 -158
  40. package/lib/commands/kickoff.mjs +192 -0
  41. package/lib/commands/knowledge.mjs +368 -0
  42. package/lib/commands/lanes.mjs +502 -0
  43. package/lib/commands/manifest-catalog.mjs +102 -38
  44. package/lib/commands/migrate.mjs +204 -0
  45. package/lib/commands/new.mjs +452 -0
  46. package/lib/commands/patterns.mjs +9 -43
  47. package/lib/commands/run.mjs +617 -0
  48. package/lib/commands/sync.mjs +749 -0
  49. package/lib/commands/telemetry.mjs +390 -0
  50. package/lib/commands/verify.mjs +187 -0
  51. package/lib/config/io.mjs +232 -0
  52. package/lib/config/migrate.mjs +215 -0
  53. package/lib/config/schema.mjs +650 -0
  54. package/lib/detect.mjs +162 -19
  55. package/lib/feedback/drafts.mjs +129 -0
  56. package/lib/find-ship-root.mjs +16 -10
  57. package/lib/http.mjs +237 -11
  58. package/lib/state/idempotency.mjs +183 -0
  59. package/lib/state/lockfile.mjs +180 -0
  60. package/lib/telemetry/outbox.mjs +224 -0
  61. package/lib/templates.mjs +53 -65
  62. package/lib/verify/checks/agents-on-disk.mjs +58 -0
  63. package/lib/verify/checks/api-reachable.mjs +39 -0
  64. package/lib/verify/checks/artifacts-up-to-date.mjs +78 -0
  65. package/lib/verify/checks/bootstrap-files.mjs +67 -0
  66. package/lib/verify/checks/cache-integrity.mjs +51 -0
  67. package/lib/verify/checks/ci-secrets.mjs +86 -0
  68. package/lib/verify/checks/config-present.mjs +39 -0
  69. package/lib/verify/checks/gitignore-cache.mjs +51 -0
  70. package/lib/verify/checks/rules-markers.mjs +135 -0
  71. package/lib/verify/checks/stack-enums.mjs +33 -0
  72. package/lib/verify/checks/tracker-labels.mjs +91 -0
  73. package/lib/verify/registry.mjs +120 -0
  74. package/lib/version.mjs +34 -0
  75. package/package.json +10 -3
  76. package/bin/ship.mjs +0 -68
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @elmundi/ship-cli
2
2
 
3
- **Ship** in your repository: agents get a standing policy to call the **methodology HTTP API** (semantic search, full doc fetch, catalog bodies, retro feedback) via the **`ship`** CLI and the URLs/snippets `ship init` writes.
3
+ **Ship** in your repository: agents get a standing policy to consume Ship **artifacts** (patterns, tools, workflows, collections, docs) via the **`shipctl`** CLI and the snippets `shipctl init` writes.
4
4
 
5
- Published as **`@elmundi/ship-cli`** under the [elmundi](https://www.npmjs.com/org/elmundi) org; the binary name is **`ship`**.
5
+ Published as **`@elmundi/ship-cli`** under the [elmundi](https://www.npmjs.com/org/elmundi) org; the binary name is **`shipctl`**.
6
6
 
7
7
  ## Requirements
8
8
 
@@ -18,15 +18,15 @@ npx @elmundi/ship-cli help
18
18
 
19
19
  ## Bring Ship into your project (main path)
20
20
 
21
- You **do not** need the Ship monorepo cloned for day-to-day use. Work in **your product repo** and wire agents to the same methodology API the CLI uses.
21
+ You **do not** need the Ship monorepo cloned for day-to-day use. Work in **your product repo** and wire agents to the same artifacts protocol the CLI uses.
22
22
 
23
23
  ### 1. Pick the API URL
24
24
 
25
25
  - **`SHIP_API_BASE`** — env var the CLI and injected snippets use (no trailing slash).
26
26
  - Or pass **`--base-url`** on each command.
27
- - Default matches other Ship tooling (public methodology host unless you override for local FastAPI).
27
+ - Default matches other Ship tooling (public host unless you override for local FastAPI).
28
28
 
29
- ### 2. Preview what `ship init` will change
29
+ ### 2. Preview what `shipctl init` will change
30
30
 
31
31
  From the **root of the repo** you want agents to use:
32
32
 
@@ -35,21 +35,42 @@ cd /path/to/your-product
35
35
  npx @elmundi/ship-cli init --dry-run
36
36
  ```
37
37
 
38
- `ship init` **detects what is already in the tree** and only plans injections for those stacks:
38
+ `shipctl init` **detects what is already in the tree** and only plans injections for those stacks:
39
39
 
40
- | If the repo has… | `ship init` can add… |
41
- |------------------|----------------------|
42
- | `.cursor/` | Cursor rule **`.cursor/rules/ship-methodology-api.mdc`** |
40
+ | If the repo has… | `shipctl init` can add… |
41
+ |------------------|-------------------------|
42
+ | `.cursor/` | Cursor rule **`.cursor/rules/ship-artifacts-protocol.mdc`** |
43
43
  | **`AGENTS.md`** | Appended section (Codex-style / generic agents file) |
44
44
  | **`CLAUDE.md`** | Appended section |
45
45
  | **`.codex/`** | **`SHIP_API.md`** under `.codex/` |
46
46
  | **`.github/copilot-instructions.md`** | Appended section |
47
+ | **`.aider.conf.yml`** / `AIDER.md` | **`AIDER.md`** section |
48
+ | **`.clinerules`** / `.rooignore` | **`.clinerules`** section |
49
+ | **`.continue/`** | **`.continue/ship.md`** side-file |
50
+ | **`.windsurfrules`** | **`.windsurfrules`** section |
51
+ | **`.zed/`** | **`.zed/ship.md`** |
52
+ | **`GEMINI.md`** / `.gemini/` | **`GEMINI.md`** section |
53
+ | **`.opencode/`** | **`.opencode/ship.md`** |
54
+ | **`.cursor/environments.json`** | Marker-guarded update to existing file |
47
55
 
48
56
  If **none** of the above exist, init offers a **standalone** **`SHIP_AGENT_API.md`** in the repo root so humans can copy the contract into whatever system you use later.
49
57
 
50
- Use **`--only cursor|agents-md|claude-md|codex|copilot`** to limit targets; **`--cwd <dir>`** to point at another root.
58
+ Use **`--agents <csv>`** to limit targets and **`--cwd <dir>`** to point at another root. Example:
51
59
 
52
- ### 3. Apply with confirmation (recommended)
60
+ ```bash
61
+ npx @elmundi/ship-cli init --agents cursor,codex,claude --dry-run
62
+ ```
63
+
64
+ ### 3. Stack hints
65
+
66
+ `init` also accepts `--tracker`, `--ci`, `--preset` (per RFC-0002 stack block). For now they land in the plan payload and are echoed back; a future release writes them into `.ship/config.yml` and triggers the bootstrap adapters (RFC-0004).
67
+
68
+ ```bash
69
+ npx @elmundi/ship-cli init --yes \
70
+ --agents cursor,codex --tracker linear --ci gh-actions --preset web-app
71
+ ```
72
+
73
+ ### 4. Apply with confirmation (recommended)
53
74
 
54
75
  Interactive run prints the plan and asks **Apply these changes? [y/N]**:
55
76
 
@@ -57,9 +78,9 @@ Interactive run prints the plan and asks **Apply these changes? [y/N]**:
57
78
  npx @elmundi/ship-cli init
58
79
  ```
59
80
 
60
- After you confirm **`y`**, it writes/updates the files above. Injected content tells agents to **use the Ship methodology API** (and the **`ship`** CLI from a dev shell): **search → fetch** workflow, **`ship docs fetch`** for documentation paths, **`ship pattern|tool|workflow|collection`** for catalog entries, and **`ship docs feedback`** for safe retro notes — so methodology and **tools/workflows** discovery stay consistent with the server.
81
+ After you confirm **`y`**, it writes/updates the files above. Injected content tells agents to **resolve Ship artifacts before use** via the CLI: **search → fetch → record `kind:id@version`** workflow, **`shipctl docs fetch`** for documentation paths, **`shipctl pattern|tool|workflow|collection`** for catalog bodies, and **`shipctl docs feedback`** for safe retro notes.
61
82
 
62
- ### 4. Non-interactive (CI or scripts)
83
+ ### 5. Non-interactive (CI or scripts)
63
84
 
64
85
  Only after you are happy with **`--dry-run`**:
65
86
 
@@ -69,23 +90,395 @@ npx @elmundi/ship-cli init --yes
69
90
 
70
91
  **`--force`** replaces blocks that were already injected (same marker). Without **`--force`**, existing injections are skipped.
71
92
 
93
+ ## Init flow
94
+
95
+ `shipctl init` is the primary adoption entrypoint. It composes four steps in one
96
+ command:
97
+
98
+ 1. **Config** — creates `.ship/config.yml` (via `DEFAULT_CONFIG`), seeds an
99
+ anonymous telemetry id, writes `.ship/state.json`, and appends
100
+ `.ship/cache/` to `.gitignore`. Existing configs are respected and only
101
+ updated with the flags/doctor proposal.
102
+ 2. **Telemetry** — prompts once for opt-in (default **OFF**). Non-TTY runs and
103
+ `--yes` default to OFF. `--telemetry on|off|ask` overrides the prompt.
104
+ 3. **Doctor (no network)** — runs the adapter detect-layer to propose
105
+ `tracker / ci / agents / language` values for gaps the user didn't set
106
+ explicitly via flags. Detection never overrides explicit flags.
107
+ 4. **Sync** — calls `syncArtifacts()` to pull the derived collection artifacts
108
+ (`collection/agent-rules-<agent>`, `collection/preset-<preset>`, optionally
109
+ `collection/adoption-playbook`) into `.ship/cache/`.
110
+
111
+ Optional post-steps:
112
+
113
+ - **`--copy-rules`** — for every agent in `stack.agents`, reads the cached
114
+ `agent-rules-<agent>` artifact, extracts `install_target` + `marker` from the
115
+ front-matter, and writes (or upserts) the rules file at the target path.
116
+ A `<!-- ship-cli: installed-from collection/agent-rules-<agent>@<version> -->`
117
+ footer is appended so re-runs can detect the installed version. Downgrades /
118
+ different versions are skipped unless `--force` is passed.
119
+ - **`--bootstrap`** — renders CI + tracker + secrets scaffolding. v1 special-
120
+ cases `mobile-app + gh-actions + linear` (writes `.github/workflows/ship-pilot.yml`
121
+ skeleton, `.ship/labels.yml`, and a `# --- ship-managed ---` block appended to
122
+ `.env.example`). All other combinations emit `SHIP_BOOTSTRAP_PLAN.md` with a
123
+ TODO checklist pointing at the preset artifact for full details.
124
+ - **`--copy-playbook`** — fetches `collection/adoption-playbook` when
125
+ published; a 404 is silently skipped (does not fail the command).
126
+
127
+ ### Three primary invocations
128
+
129
+ **MVP (dry-run preview, recommended first run):**
130
+
131
+ ```bash
132
+ shipctl init --dry-run --agents cursor --preset adoption-minimum --tracker none --ci manual
133
+ ```
134
+
135
+ **Pilot (apply, no scaffolding):**
136
+
137
+ ```bash
138
+ shipctl init --yes \
139
+ --agents cursor,claude-md \
140
+ --preset web-app --tracker linear --ci gh-actions \
141
+ --copy-rules --telemetry off
142
+ ```
143
+
144
+ **Full bootstrap (mobile-app, with scaffolding skeletons):**
145
+
146
+ ```bash
147
+ shipctl init --bootstrap --yes \
148
+ --agents cursor,claude-md,codex \
149
+ --tracker linear --ci gh-actions --preset mobile-app \
150
+ --copy-rules --telemetry off
151
+ ```
152
+
153
+ ### Full flag surface
154
+
155
+ ```
156
+ shipctl init
157
+ [--yes] [--force] [--dry-run] [--cwd DIR] [--json]
158
+ [--agents cursor,codex,claude-md] # preferred, csv
159
+ [--tracker linear] # linear|jira|github-issues|…|none
160
+ [--ci gh-actions] # gh-actions|gitlab-ci|…|manual
161
+ [--preset mobile-app] # web-app|api-backend|mobile-app|…
162
+ [--language ts] # ts|js|py|go|rust|…|multi
163
+ [--channel stable|edge] # override api.channel
164
+ [--copy-rules] # install agent-rules-<agent> files
165
+ [--copy-playbook] # fetch adoption-playbook into cache
166
+ [--bootstrap] # render CI/tracker scaffolding from preset
167
+ [--telemetry on|off|ask] # override the interactive prompt
168
+ ```
169
+
170
+ ### Example output
171
+
172
+ ```
173
+ Ship init complete
174
+ -----------------
175
+ Config: .ship/config.yml
176
+ Agents: cursor, claude-md
177
+ Tracker: linear
178
+ CI: gh-actions
179
+ Preset: mobile-app
180
+ Channel: stable
181
+ Telemetry: off
182
+
183
+ Installed rules:
184
+ - .cursor/rules/ship-artifacts-protocol.mdc (from collection/agent-rules-cursor@1.0.0) [wrote]
185
+ - CLAUDE.md (from collection/agent-rules-claude-md@1.0.0) [wrote]
186
+
187
+ Bootstrap (preset=mobile-app):
188
+ - wrote SHIP_BOOTSTRAP_PLAN.md
189
+ - wrote .github/workflows/ship-pilot.yml
190
+ - wrote .ship/labels.yml
191
+ - appended .env.example
192
+
193
+ Next:
194
+ shipctl sync # keep artifacts fresh
195
+ shipctl verify # check tracker labels, CI secrets, rules markers
196
+ shipctl feedback draft # submit improvement idea
197
+ ```
198
+
72
199
  ## Commands (quick reference)
73
200
 
74
201
  | Command | Role |
75
202
  |--------|------|
76
- | **`ship init`** | Inject agent-facing rules / sections with your **`SHIP_API_BASE`** (or **`--base-url`**). |
77
- | **`ship search …`** | Vector search over methodology corpus (`POST /search`). |
78
- | **`ship docs fetch …`**, **`ship docs feedback …`** | Documentation file fetch and retro feedback (`POST /fetch` with `path`, `POST /feedback`). |
79
- | **`ship pattern|tool|workflow|collection`** **`list` \| `show` \| `fetch` \| `search`** | Catalogs; hosted mode uses the same API (including **`fetch`** via `POST /fetch` with `kind` + `id`). Plural aliases (`patterns`, `tools`, …) work. |
203
+ | **`shipctl init`** | Inject agent-facing rules / sections with your **`SHIP_API_BASE`** (or **`--base-url`**). |
204
+ | **`shipctl search …`** | Vector search over methodology corpus (`POST /search`). |
205
+ | **`shipctl docs fetch …`**, **`shipctl docs feedback …`** | Documentation file fetch and retro feedback (`POST /fetch` with `path`, `POST /feedback`). |
206
+ | **`shipctl pattern\|tool\|workflow\|collection`** **`list` \| `show` \| `fetch` \| `search`** | Artifact bodies; hosted mode uses the same API (including **`fetch`** via `POST /fetch` with `kind` + `id` + optional `version`). Plural aliases (`patterns`, `tools`, …) work. |
207
+
208
+ **Maintainers / full Ship checkout:** if the current directory (or **`SHIP_REPO`**) is inside the Ship monorepo, **`list` / `show` / `fetch`** for catalogs can read manifests from **disk** instead of HTTP. **`shipctl search`** always uses HTTP.
209
+
210
+ Run **`shipctl help`** for full usage.
211
+
212
+ ## Doctor
213
+
214
+ **`shipctl doctor`** inspects a repository using the pluggable adapter registry
215
+ (`cli/lib/adapters/`) and proposes a best-guess Ship stack. It runs every
216
+ tracker / CI / language / agent adapter's `detect(cwd)` hook, scores the
217
+ findings, and infers a preset from repository structure (e.g. `pubspec.yaml`
218
+ or `react-native` in deps → `mobile-app`; `packages/` or `pnpm-workspace.yaml`
219
+ → `monorepo`). Nothing is ever written without **`--write-inventory`**.
220
+
221
+ ```bash
222
+ shipctl doctor # human report
223
+ shipctl doctor --json # machine-readable
224
+ shipctl doctor --cwd /path/to/other-repo # inspect elsewhere
225
+ shipctl doctor --write-inventory # persist .ship/inventory.json
226
+ ```
227
+
228
+ Example output (trimmed):
229
+
230
+ ```text
231
+ Ship doctor — inspecting /path/to/your-product
232
+
233
+ Tracker: linear (0.95) · evidence: .env (LINEAR_API_KEY), package.json (@linear/sdk)
234
+ CI: gh-actions (1.00) · evidence: .github/workflows/ (3 workflow(s))
235
+ Language: ts (1.00) · evidence: tsconfig.json (present)
236
+ Agents: cursor (1.00), claude-md (1.00)
237
+
238
+ Inferred preset: web-app (evidence: next.config.ts)
239
+
240
+ Existing Ship artifacts:
241
+ .ship/config.yml missing
242
+ .ship/cache/ missing
243
+ .ship/inventory.json missing
244
+ .cursor/rules/ship-* missing
245
+
246
+ Recommendations:
247
+ 1. shipctl config init
248
+ 2. shipctl init --bootstrap --tracker linear --ci gh-actions --agents cursor,claude-md --preset web-app
249
+ 3. shipctl sync
250
+ 4. shipctl verify
251
+ ```
252
+
253
+ Passing **`--write-inventory`** persists the findings to `.ship/inventory.json`
254
+ so that `shipctl init --bootstrap` can pick up the inferred stack without
255
+ re-running detection.
256
+
257
+ ## Config & Sync
258
+
259
+ Ship stores local state under **`.ship/`**. Methodology bodies never live in your
260
+ repo git history — `shipctl sync` caches them in `.ship/cache/`, which is
261
+ `.gitignore`d by default.
262
+
263
+ ### `.ship/` layout
264
+
265
+ ```
266
+ .ship/
267
+ ├── config.yml # RFC-0002 schema; committed
268
+ ├── state.json # last_sync_at, last_manifest_hash; gitignored
269
+ ├── cache/ # per-repo artifact cache (gitignored)
270
+ │ ├── pattern/<id>@<v>/
271
+ │ │ ├── ARTIFACT.md # full body (frontmatter + content), per RFC-0005
272
+ │ │ └── .meta.json # source, sha256, fetched_at, etc.
273
+ │ ├── tool/<id>@<v>/…
274
+ │ ├── workflow/<id>@<v>/…
275
+ │ ├── collection/<id>@<v>/…
276
+ │ └── doc/…
277
+ ├── telemetry-outbox.jsonl # buffered telemetry events (gitignored)
278
+ └── feedback-drafts/ # feedback draft markdowns (gitignored)
279
+ ```
280
+
281
+ ### `shipctl config`
282
+
283
+ ```bash
284
+ shipctl config init # bootstrap .ship/config.yml + state.json + cache/
285
+ shipctl config path # print absolute path to config.yml
286
+ shipctl config show # pretty-print effective YAML
287
+ shipctl config get <dotted.key> # e.g. shipctl config get api.channel
288
+ shipctl config set <k> <value> # atomic write, validates before saving
289
+ shipctl config validate # exit 10 on invalid enum / bad URL / bad pin key
290
+ ```
291
+
292
+ Value parsing for `config set`:
293
+
294
+ - Bare `true|false|null` → booleans / null.
295
+ - `-?\d+(.\d+)?` → number.
296
+ - `[a,b,c]` → array of strings (quotes optional).
297
+ - Anything else → string.
298
+
299
+ Dotted keys under `artifacts.pins` preserve the embedded slash:
300
+ `artifacts.pins.pattern/cloud-developer`.
301
+
302
+ ```bash
303
+ shipctl config set stack.agents [cursor,codex]
304
+ shipctl config set api.channel edge
305
+ shipctl config set artifacts.pins.pattern/cloud-developer 1.4.2
306
+ ```
307
+
308
+ ### `shipctl sync`
309
+
310
+ ```bash
311
+ shipctl sync # pull latest for this stack
312
+ shipctl sync --check-only # report changes without writing cache
313
+ shipctl sync --dry-run # --check-only + planned HTTP calls
314
+ shipctl sync --only pattern:cloud-developer [--only tool:gh-actions]
315
+ shipctl sync --channel edge
316
+ shipctl sync --force-unpin # temporarily ignore version pins
317
+ ```
318
+
319
+ Summary format:
320
+
321
+ ```
322
+ up_to_date: 12
323
+ updated: 3
324
+ skipped_pin: 2
325
+ deprecated: 1 (…)
326
+ yanked: 0
327
+ failed: 0
328
+ ```
329
+
330
+ Pins are honoured: an entry whose manifest version does not satisfy the pin is
331
+ reported as `skipped_pin` unless `--force-unpin` is set. After a successful
332
+ sync, `.ship/state.json` records `last_sync_at` and `last_manifest_hash`.
333
+
334
+ > Methodology docs never live in your repo. `shipctl sync` caches them in
335
+ > `.ship/cache/`, sealed by `content_sha256` from the Ship manifest.
336
+
337
+ ## Telemetry & Feedback
338
+
339
+ Anonymous telemetry is **opt-in and OFF by default** (RFC-0003). Nothing leaves
340
+ the repo until you explicitly flip the switch with `shipctl telemetry on`.
341
+ Events are first buffered to `.ship/telemetry-outbox.jsonl`; flushing to
342
+ `POST /telemetry` is a deliberate, retry-safe step.
343
+
344
+ ```bash
345
+ # inspect current state
346
+ shipctl telemetry status # share=..., anonymous_id=..., outbox_pending=N
347
+
348
+ # opt in, with a narrow scope, non-interactively
349
+ shipctl telemetry on --scope artifact_usage,improvement_drafts --yes
350
+
351
+ # look at what's queued locally before sending
352
+ shipctl telemetry buffer --limit 10
353
+
354
+ # send any queued events (batches of 100); succeeded lines are removed
355
+ shipctl telemetry flush
356
+ shipctl telemetry flush --dry-run # preview only
357
+
358
+ # data rights: export or delete by anonymous_id
359
+ shipctl telemetry export --out telemetry.json
360
+ shipctl telemetry delete-my-data # interactive confirmation required
361
+
362
+ # rotate identity (server treats the previous id as a separate adopter)
363
+ shipctl telemetry reset-id
80
364
 
81
- **Maintainers / full Ship checkout:** if the current directory (or **`SHIP_REPO`**) is inside the Ship monorepo, **`list` / `show` / `fetch`** for catalogs can read manifests from **disk** instead of HTTP. **`ship search`** always uses HTTP.
365
+ # fully disable
366
+ shipctl telemetry off
367
+ ```
368
+
369
+ Allowed event types: `artifact.fetch`, `artifact.use`, `artifact.sync`,
370
+ `feedback.submit`, `doctor.result`. Payload keys in the RFC-0003 denylist
371
+ (`path, code, diff, branch, remote, email`) are stripped client-side before
372
+ anything is appended to the outbox.
373
+
374
+ Feedback is always drafted locally as a markdown file before it is sent
375
+ anywhere:
376
+
377
+ ```bash
378
+ # create a draft
379
+ shipctl feedback draft --kind pattern --id cloud-developer --version 1.4.2 \
380
+ --title "Missing mobile preview step" \
381
+ --summary "Evidence checklist misses mobile preview" \
382
+ --recommendation "Add a bullet under Evidence"
383
+
384
+ # review / edit (uses $EDITOR)
385
+ shipctl feedback list
386
+ shipctl feedback show .ship/feedback-drafts/2026-04-17-11-30-15-pattern-cloud-developer.md
387
+ shipctl feedback edit .ship/feedback-drafts/2026-04-17-11-30-15-pattern-cloud-developer.md
388
+
389
+ # submit → POST /feedback → GitHub issue URL; draft moves to sent/
390
+ shipctl feedback submit .ship/feedback-drafts/2026-04-17-11-30-15-pattern-cloud-developer.md --yes
391
+ ```
392
+
393
+ Submission requires `kind`, `id`, `title`, and `summary`; missing fields fail
394
+ with exit 1 (nothing sent). When `telemetry.share=true` and
395
+ `scope.improvement_drafts=true`, a `feedback.submit` event is appended to the
396
+ telemetry outbox on successful submission.
397
+
398
+ ## New & Verify
399
+
400
+ ### `shipctl new <name>`
401
+
402
+ Scaffolds a fresh repo with the Ship wiring already in place: creates the
403
+ target directory, runs `git init -q`, drops a minimal `README.md`, seeds
404
+ `.ship/config.yml` via `shipctl config init`, applies the provided stack
405
+ flags via `shipctl config set`, and (when `--agents` is supplied) runs
406
+ `shipctl init --yes --copy-rules …` to install the agent rule files.
407
+
408
+ ```bash
409
+ shipctl new pharma-pilot \
410
+ --preset mobile-app --tracker linear --ci gh-actions \
411
+ --agents cursor,claude,codex --yes
412
+ cd pharma-pilot
413
+ shipctl verify --no-network
414
+ ```
82
415
 
83
- Run **`ship help`** for full usage.
416
+ Common flags:
84
417
 
85
- ## Versioning (until the CLI stabilizes)
418
+ - `--here` initialize in the current directory instead of creating `<name>/`.
419
+ - `--preset / --tracker / --ci / --agents / --language / --channel` —
420
+ forwarded to `config set` + `init`.
421
+ - `--yes` — non-interactive (required for CI / dry-run).
422
+ - `--dry-run` — describe the plan without touching disk.
423
+ - `--json` — machine-readable summary of created files.
86
424
 
87
- Releases **bump the patch (third) number only** (e.g. `0.8.0` `0.8.1`) while the command surface and docs are still settling. Minor/major bumps resume once things are stable.
425
+ ### `shipctl verify`
426
+
427
+ Post-adoption liveness check. A collection of independent checks under
428
+ `cli/lib/verify/checks/` grouped by category:
429
+
430
+ - **local** — `.ship/config.yml` present, `.gitignore` excludes
431
+ `.ship/cache/`, agent rule files have the
432
+ `<!-- ship-cli: artifacts-protocol v1 -->` marker and `installed-from`
433
+ footer, cached artifacts match their `.meta.json` sha256, bootstrap
434
+ scaffolding (mobile-app + gh-actions + linear) carries
435
+ `ship-managed` markers.
436
+ - **config** — `stack.*` enum re-validation, declared agents have
437
+ on-disk signals.
438
+ - **network** (skip with `--no-network`) — `/health` (or `/patterns` as a
439
+ fallback) reachable, local cache matches the channel catalog aggregated
440
+ across `/patterns`, `/tools`, `/workflows`, `/collections`, Linear labels
441
+ exist (needs `LINEAR_API_KEY`), every `${{ secrets.X }}` reference in
442
+ gh-actions workflows is declared in `.env.example`.
443
+
444
+ Exit `0` when no check returned `fail`; warnings do not fail.
445
+
446
+ ```bash
447
+ shipctl verify # full run
448
+ shipctl verify --no-network # skip HTTP + Linear calls
449
+ shipctl verify --check rules-markers,cache-integrity
450
+ shipctl verify --severity warn # hide pass rows
451
+ shipctl verify --json # { checks:[…], summary:{…}, exit_code }
452
+ ```
453
+
454
+ ## Versioning
455
+
456
+ `shipctl --version` (or `shipctl version`) prints the running release. The
457
+ version is part of every outbound `User-Agent` header so the methodology API
458
+ can correlate adoption metrics with the client release.
459
+
460
+ This package follows the **monorepo-wide** Ship version: a single `VERSION`
461
+ file at the repo root drives `cli/package.json`, `landing/package.json`,
462
+ `backend/app/main.py`, and the root `package.json` in lockstep via
463
+ `scripts/version.mjs` (`npm run version:bump -- patch|minor|major|x.y.z`).
464
+ See the root [`README.md`](../README.md#versioning--releases) for the full
465
+ release recipe.
88
466
 
89
467
  ## Publishing (maintainers)
90
468
 
91
- GitHub Action **Publish @elmundi/ship-cli to npm** (tag **`cli-v<version>`** must match **`cli/package.json`**, or use **workflow_dispatch**). Repository secret **`NPM_TOKEN`** required. Publish from monorepo root: **`npm publish -w @elmundi/ship-cli`**, not `npm publish --prefix cli` (root package is private).
469
+ The GitHub Action **Publish @elmundi/ship-cli to npm** is triggered by either
470
+ the unified `v<x.y.z>` tag (preferred — bumps every component in lockstep) or
471
+ the legacy `cli-v<x.y.z>` tag (escape hatch for CLI-only patches). It runs
472
+ `scripts/version.mjs check` before publishing so an out-of-sync tree never
473
+ ships to npm.
474
+
475
+ ```bash
476
+ npm run version:bump -- minor # 0.10.0 → 0.11.0, syncs everything
477
+ git commit -am "release v$(cat VERSION)"
478
+ git tag "v$(cat VERSION)"
479
+ git push --follow-tags # publish workflow picks up v0.11.0
480
+ ```
481
+
482
+ Repository secret **`NPM_TOKEN`** is required. Publish from the monorepo
483
+ root: **`npm publish -w @elmundi/ship-cli`** — not `npm publish --prefix cli`
484
+ (the root package is private).
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ import { extractGlobalArgv } from "../lib/config.mjs";
3
+ import { docsCommand } from "../lib/commands/docs.mjs";
4
+ import { searchCommand } from "../lib/commands/search.mjs";
5
+ import { patternCommand } from "../lib/commands/patterns.mjs";
6
+ import { resourceManifestCommand } from "../lib/commands/manifest-catalog.mjs";
7
+ import { printHelp } from "../lib/commands/help.mjs";
8
+ import { initCommand } from "../lib/commands/init.mjs";
9
+ import { doctorCommand } from "../lib/commands/doctor.mjs";
10
+ import { getCliVersion } from "../lib/version.mjs";
11
+
12
+ const raw = process.argv.slice(2);
13
+
14
+ /* `--version` / `-v` / `version` short-circuit before normal arg parsing —
15
+ * any tool worth its salt prints its version without the rest of the parser
16
+ * having to work. We only fire when the version flag is the *first* token,
17
+ * so that subcommand args like `feedback draft --version 1.0.0` are not
18
+ * mistaken for a request to print our own version. */
19
+ if (raw[0] === "--version" || raw[0] === "-v" || raw[0] === "version") {
20
+ console.log(getCliVersion());
21
+ process.exit(0);
22
+ }
23
+
24
+ const { _, ...g } = extractGlobalArgv(raw);
25
+ const ctx = {
26
+ baseUrl: g.baseUrl,
27
+ json: g.json,
28
+ yes: g.yes,
29
+ force: g.force,
30
+ dryRun: g.dryRun,
31
+ };
32
+
33
+ const [cmd, ...rest] = _;
34
+
35
+ try {
36
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
37
+ printHelp();
38
+ process.exit(0);
39
+ }
40
+
41
+ if (cmd === "search") {
42
+ await searchCommand(ctx, rest);
43
+ process.exit(0);
44
+ }
45
+
46
+ if (cmd === "docs") {
47
+ await docsCommand(ctx, rest);
48
+ process.exit(0);
49
+ }
50
+
51
+ if (cmd === "pattern" || cmd === "patterns") {
52
+ await patternCommand(ctx, rest);
53
+ process.exit(0);
54
+ }
55
+
56
+ if (cmd === "tool" || cmd === "tools") {
57
+ await resourceManifestCommand("tool", ctx, rest);
58
+ process.exit(0);
59
+ }
60
+
61
+ if (cmd === "collection" || cmd === "collections") {
62
+ await resourceManifestCommand("collection", ctx, rest);
63
+ process.exit(0);
64
+ }
65
+
66
+ if (cmd === "init") {
67
+ await initCommand(ctx, rest);
68
+ process.exit(0);
69
+ }
70
+
71
+ if (cmd === "doctor") {
72
+ await doctorCommand(ctx, rest);
73
+ process.exit(0);
74
+ }
75
+
76
+ if (cmd === "config") {
77
+ const { configCommand } = await import("../lib/commands/config.mjs");
78
+ await configCommand(ctx, rest);
79
+ process.exit(0);
80
+ }
81
+
82
+ if (cmd === "sync") {
83
+ const { syncCommand } = await import("../lib/commands/sync.mjs");
84
+ await syncCommand(ctx, rest);
85
+ process.exit(0);
86
+ }
87
+
88
+ if (cmd === "doctor") {
89
+ const { doctorCommand } = await import("../lib/commands/doctor.mjs");
90
+ await doctorCommand(ctx, rest);
91
+ process.exit(0);
92
+ }
93
+
94
+ if (cmd === "verify") {
95
+ const { verifyCommand } = await import("../lib/commands/verify.mjs");
96
+ await verifyCommand(ctx, rest);
97
+ process.exit(0);
98
+ }
99
+
100
+ if (cmd === "telemetry") {
101
+ const { telemetryCommand } = await import("../lib/commands/telemetry.mjs");
102
+ await telemetryCommand(ctx, rest);
103
+ process.exit(0);
104
+ }
105
+
106
+ if (cmd === "feedback") {
107
+ const { feedbackCommand } = await import("../lib/commands/feedback.mjs");
108
+ await feedbackCommand(ctx, rest);
109
+ process.exit(0);
110
+ }
111
+
112
+ if (cmd === "new") {
113
+ const { newCommand } = await import("../lib/commands/new.mjs");
114
+ await newCommand(ctx, rest);
115
+ process.exit(0);
116
+ }
117
+
118
+ if (cmd === "bootstrap") {
119
+ const { bootstrapCommand } = await import("../lib/commands/bootstrap.mjs");
120
+ await bootstrapCommand(ctx, rest);
121
+ process.exit(0);
122
+ }
123
+
124
+ if (cmd === "callback") {
125
+ const { callbackCommand } = await import("../lib/commands/callback.mjs");
126
+ await callbackCommand(ctx, rest);
127
+ process.exit(0);
128
+ }
129
+
130
+ if (cmd === "kickoff") {
131
+ const { kickoffCommand } = await import("../lib/commands/kickoff.mjs");
132
+ await kickoffCommand(ctx, rest);
133
+ process.exit(0);
134
+ }
135
+
136
+ if (cmd === "knowledge") {
137
+ const { knowledgeCommand } = await import("../lib/commands/knowledge.mjs");
138
+ await knowledgeCommand(ctx, rest);
139
+ process.exit(0);
140
+ }
141
+
142
+ if (cmd === "migrate") {
143
+ const { migrateCommand } = await import("../lib/commands/migrate.mjs");
144
+ await migrateCommand(ctx, rest);
145
+ process.exit(0);
146
+ }
147
+
148
+ if (cmd === "run") {
149
+ const { runCommand } = await import("../lib/commands/run.mjs");
150
+ await runCommand(ctx, rest);
151
+ process.exit(0);
152
+ }
153
+
154
+ if (cmd === "lanes") {
155
+ const { lanesCommand } = await import("../lib/commands/lanes.mjs");
156
+ await lanesCommand(ctx, rest);
157
+ process.exit(0);
158
+ }
159
+
160
+ console.error(`Unknown command: ${cmd}\nRun: shipctl help`);
161
+ process.exit(1);
162
+ } catch (err) {
163
+ console.error(err instanceof Error ? err.message : err);
164
+ process.exit(1);
165
+ }