@elmundi/ship-cli 0.8.0 → 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 +456 -32
  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,60 +1,484 @@
1
1
  # @elmundi/ship-cli
2
2
 
3
- Command-line entry to the Ship methodology: **one HTTP API** (FastAPI) for **search, fetch, feedback, patterns, tools, workflows, collections** or read catalogs from disk inside a Ship clone / `SHIP_REPO` — plus **`ship init`** to inject API usage into agent configs.
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 under the npm org **[elmundi](https://www.npmjs.com/org/elmundi)**; the binary name remains **`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
 
9
- - **Node.js 20+** (matches Ship CI and typical adopters).
9
+ - **Node.js 20+**
10
10
 
11
11
  ## Install
12
12
 
13
- After the package is [published to npm](https://www.npmjs.com/package/@elmundi/ship-cli):
14
-
15
13
  ```bash
16
14
  npm install -g @elmundi/ship-cli
17
- # or, without a global install:
15
+ # or one-off:
18
16
  npx @elmundi/ship-cli help
19
17
  ```
20
18
 
21
- From a full **Ship** monorepo clone you can still run `npm run ship -- …` from the repo root (workspace).
19
+ ## Bring Ship into your project (main path)
22
20
 
23
- ## Adopt without cloning the whole monorepo
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.
24
22
 
25
- 1. Install the CLI (`npm i -g @elmundi/ship-cli` or use `npx @elmundi/ship-cli`).
26
- 2. From **any** directory, point **`SHIP_API_BASE`** at the **deployed methodology API** and list patterns or catalogs (same server as search):
23
+ ### 1. Pick the API URL
27
24
 
28
- ```bash
29
- SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli pattern list
30
- SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli tool list
31
- ```
25
+ - **`SHIP_API_BASE`** — env var the CLI and injected snippets use (no trailing slash).
26
+ - Or pass **`--base-url`** on each command.
27
+ - Default matches other Ship tooling (public host unless you override for local FastAPI).
32
28
 
33
- 3. Optional: work from a **local** Ship checkout (or **`SHIP_REPO`**) to read manifests from disk without calling the API.
29
+ ### 2. Preview what `shipctl init` will change
34
30
 
35
- 4. In your **product** repository, wire agents to the methodology API:
31
+ From the **root of the repo** you want agents to use:
36
32
 
37
- ```bash
38
- cd /path/to/your-product
39
- npx @elmundi/ship-cli init --yes
40
- ```
33
+ ```bash
34
+ cd /path/to/your-product
35
+ npx @elmundi/ship-cli init --dry-run
36
+ ```
41
37
 
42
- Use **`--dry-run`** first to preview; **`--yes`** skips prompts and writes files see `ship init help`.
38
+ `shipctl init` **detects what is already in the tree** and only plans injections for those stacks:
43
39
 
44
- ## Which commands need what
40
+ | If the repo has… | `shipctl init` can add… |
41
+ |------------------|-------------------------|
42
+ | `.cursor/` | Cursor rule **`.cursor/rules/ship-artifacts-protocol.mdc`** |
43
+ | **`AGENTS.md`** | Appended section (Codex-style / generic agents file) |
44
+ | **`CLAUDE.md`** | Appended section |
45
+ | **`.codex/`** | **`SHIP_API.md`** under `.codex/` |
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 |
45
55
 
46
- | Command | Needs |
47
- |--------|--------|
48
- | `ship pattern|tool|workflow|collection …` (plural aliases) | Same **`SHIP_API_BASE`** as search/docs when not on disk. **Local:** cwd inside Ship or **`SHIP_REPO`**. |
49
- | `ship search`, `ship docs fetch|feedback` | **`SHIP_API_BASE`** (default public methodology URL; override locally) or `--base-url`. |
50
- | `ship init` | Target repo cwd; **`SHIP_API_BASE` / `--base-url`** is the API URL written into snippets. |
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.
51
57
 
52
- ## Publishing (maintainers)
58
+ Use **`--agents <csv>`** to limit targets and **`--cwd <dir>`** to point at another root. Example:
59
+
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)
74
+
75
+ Interactive run prints the plan and asks **Apply these changes? [y/N]**:
76
+
77
+ ```bash
78
+ npx @elmundi/ship-cli init
79
+ ```
80
+
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.
82
+
83
+ ### 5. Non-interactive (CI or scripts)
84
+
85
+ Only after you are happy with **`--dry-run`**:
86
+
87
+ ```bash
88
+ npx @elmundi/ship-cli init --yes
89
+ ```
90
+
91
+ **`--force`** replaces blocks that were already injected (same marker). Without **`--force`**, existing injections are skipped.
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
+
199
+ ## Commands (quick reference)
200
+
201
+ | Command | Role |
202
+ |--------|------|
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
53
353
 
54
- Releases are published via GitHub Actions (**Publish @elmundi/ship-cli to npm**): `npm publish -w @elmundi/ship-cli` from the monorepo root (not `npm publish --prefix cli`, which would try to publish the private root package). Configure the **`NPM_TOKEN`** repository secret. On npmjs.com use either a **Granular Access Token** with **Publish** on **`@elmundi/ship-cli`** (or the **elmundi** org) and **“Bypass two-factor authentication”** enabled for automation, or a classic **Automation** token — classic **Publish** tokens often cannot publish from CI when 2FA is on (`E403` *Two-factor authentication or granular access token with bypass 2fa…*).
354
+ # send any queued events (batches of 100); succeeded lines are removed
355
+ shipctl telemetry flush
356
+ shipctl telemetry flush --dry-run # preview only
55
357
 
56
- The root monorepo `package.json` stays **`private`: true**; only **`@elmundi/ship-cli`** is intended for the public registry.
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
57
361
 
58
- ## Semver
362
+ # rotate identity (server treats the previous id as a separate adopter)
363
+ shipctl telemetry reset-id
364
+
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
+ ```
415
+
416
+ Common flags:
417
+
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.
424
+
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.
466
+
467
+ ## Publishing (maintainers)
468
+
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
+ ```
59
481
 
60
- Package version lives in **`cli/package.json`**. Bump it for each npm release following [semver](https://semver.org/).
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
+ }