@elmundi/ship-cli 0.8.1 → 0.12.0

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 (78) hide show
  1. package/README.md +651 -25
  2. package/bin/shipctl.mjs +168 -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 +422 -0
  31. package/lib/cache/store.mjs +422 -0
  32. package/lib/commands/bootstrap.mjs +4 -0
  33. package/lib/commands/callback.mjs +742 -0
  34. package/lib/commands/config.mjs +257 -0
  35. package/lib/commands/docs.mjs +4 -4
  36. package/lib/commands/doctor.mjs +583 -0
  37. package/lib/commands/feedback.mjs +355 -0
  38. package/lib/commands/help.mjs +159 -24
  39. package/lib/commands/init.mjs +830 -158
  40. package/lib/commands/kickoff.mjs +192 -0
  41. package/lib/commands/knowledge.mjs +562 -0
  42. package/lib/commands/lanes.mjs +527 -0
  43. package/lib/commands/manifest-catalog.mjs +106 -42
  44. package/lib/commands/migrate.mjs +204 -0
  45. package/lib/commands/new.mjs +452 -0
  46. package/lib/commands/patterns.mjs +14 -48
  47. package/lib/commands/run.mjs +857 -0
  48. package/lib/commands/search.mjs +2 -2
  49. package/lib/commands/sync.mjs +824 -0
  50. package/lib/commands/telemetry.mjs +390 -0
  51. package/lib/commands/trigger.mjs +196 -0
  52. package/lib/commands/verify.mjs +187 -0
  53. package/lib/config/io.mjs +232 -0
  54. package/lib/config/migrate.mjs +223 -0
  55. package/lib/config/schema.mjs +901 -0
  56. package/lib/detect.mjs +162 -19
  57. package/lib/feedback/drafts.mjs +129 -0
  58. package/lib/find-ship-root.mjs +16 -10
  59. package/lib/http.mjs +237 -11
  60. package/lib/state/idempotency.mjs +183 -0
  61. package/lib/state/lockfile.mjs +180 -0
  62. package/lib/telemetry/outbox.mjs +224 -0
  63. package/lib/templates.mjs +53 -65
  64. package/lib/verify/checks/agents-on-disk.mjs +58 -0
  65. package/lib/verify/checks/api-reachable.mjs +39 -0
  66. package/lib/verify/checks/artifacts-up-to-date.mjs +78 -0
  67. package/lib/verify/checks/bootstrap-files.mjs +67 -0
  68. package/lib/verify/checks/cache-integrity.mjs +51 -0
  69. package/lib/verify/checks/ci-secrets.mjs +86 -0
  70. package/lib/verify/checks/config-present.mjs +39 -0
  71. package/lib/verify/checks/gitignore-cache.mjs +51 -0
  72. package/lib/verify/checks/rules-markers.mjs +135 -0
  73. package/lib/verify/checks/stack-enums.mjs +33 -0
  74. package/lib/verify/checks/tracker-labels.mjs +91 -0
  75. package/lib/verify/registry.mjs +120 -0
  76. package/lib/version.mjs +34 -0
  77. package/package.json +10 -3
  78. package/bin/ship.mjs +0 -68
package/README.md CHANGED
@@ -1,8 +1,31 @@
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.
4
-
5
- Published as **`@elmundi/ship-cli`** under the [elmundi](https://www.npmjs.com/org/elmundi) org; the binary name is **`ship`**.
3
+ `shipctl` is the command-line interface to **Ship**. Three jobs:
4
+
5
+ 1. **Bootstrap a repo** so its agents (Cursor, Codex, Claude, Aider, Cline,
6
+ Continue, Windsurf, Zed, Gemini, OpenCode, Copilot, …) can consume Ship
7
+ artifacts the same way every other client does.
8
+ 2. **Sync the catalog** of `pattern` / `tool` / `collection` artifacts into
9
+ `.ship/cache/` and pin versions for reproducible runs.
10
+ 3. **Run lanes** (one-shot dispatch + GitHub Actions wrappers) and
11
+ **report Runs** so the operator console can render outcomes and route any
12
+ escalations into the Inbox.
13
+
14
+ Published as **`@elmundi/ship-cli`** under the [elmundi](https://www.npmjs.com/org/elmundi) org; the binary name is **`shipctl`**.
15
+
16
+ > **Vocabulary.** The CLI speaks the protocol layer, the operator console
17
+ > speaks the product layer. Both are correct; they refer to the same things.
18
+ >
19
+ > | CLI / YAML / API (literal) | Operator console (prose) |
20
+ > |----------------------------|--------------------------|
21
+ > | `lanes:` entries in `.ship/config.yml`, `--lane <id>` | **Automations** |
22
+ > | `pattern:` artifacts (RFC-0001) | **Plays** |
23
+ > | `pipeline_runs` rows + `shipctl callback` payloads | **Runs** |
24
+ > | clarifications / improvements / approvals queue | **Inbox** items |
25
+ >
26
+ > The CLI keeps `lanes:` / `pattern:` / `--lane` literal forever; we are
27
+ > never going to break the YAML and flag surface. Help text and prose
28
+ > reach for the operator nouns when describing what users see.
6
29
 
7
30
  ## Requirements
8
31
 
@@ -18,15 +41,15 @@ npx @elmundi/ship-cli help
18
41
 
19
42
  ## Bring Ship into your project (main path)
20
43
 
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.
44
+ 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
45
 
23
46
  ### 1. Pick the API URL
24
47
 
25
48
  - **`SHIP_API_BASE`** — env var the CLI and injected snippets use (no trailing slash).
26
49
  - Or pass **`--base-url`** on each command.
27
- - Default matches other Ship tooling (public methodology host unless you override for local FastAPI).
50
+ - Default matches other Ship tooling (public host unless you override for local FastAPI).
28
51
 
29
- ### 2. Preview what `ship init` will change
52
+ ### 2. Preview what `shipctl init` will change
30
53
 
31
54
  From the **root of the repo** you want agents to use:
32
55
 
@@ -35,21 +58,42 @@ cd /path/to/your-product
35
58
  npx @elmundi/ship-cli init --dry-run
36
59
  ```
37
60
 
38
- `ship init` **detects what is already in the tree** and only plans injections for those stacks:
61
+ `shipctl init` **detects what is already in the tree** and only plans injections for those stacks:
39
62
 
40
- | If the repo has… | `ship init` can add… |
41
- |------------------|----------------------|
42
- | `.cursor/` | Cursor rule **`.cursor/rules/ship-methodology-api.mdc`** |
63
+ | If the repo has… | `shipctl init` can add… |
64
+ |------------------|-------------------------|
65
+ | `.cursor/` | Cursor rule **`.cursor/rules/ship-artifacts-protocol.mdc`** |
43
66
  | **`AGENTS.md`** | Appended section (Codex-style / generic agents file) |
44
67
  | **`CLAUDE.md`** | Appended section |
45
68
  | **`.codex/`** | **`SHIP_API.md`** under `.codex/` |
46
69
  | **`.github/copilot-instructions.md`** | Appended section |
70
+ | **`.aider.conf.yml`** / `AIDER.md` | **`AIDER.md`** section |
71
+ | **`.clinerules`** / `.rooignore` | **`.clinerules`** section |
72
+ | **`.continue/`** | **`.continue/ship.md`** side-file |
73
+ | **`.windsurfrules`** | **`.windsurfrules`** section |
74
+ | **`.zed/`** | **`.zed/ship.md`** |
75
+ | **`GEMINI.md`** / `.gemini/` | **`GEMINI.md`** section |
76
+ | **`.opencode/`** | **`.opencode/ship.md`** |
77
+ | **`.cursor/environments.json`** | Marker-guarded update to existing file |
47
78
 
48
79
  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
80
 
50
- Use **`--only cursor|agents-md|claude-md|codex|copilot`** to limit targets; **`--cwd <dir>`** to point at another root.
81
+ Use **`--agents <csv>`** to limit targets and **`--cwd <dir>`** to point at another root. Example:
82
+
83
+ ```bash
84
+ npx @elmundi/ship-cli init --agents cursor,codex,claude --dry-run
85
+ ```
86
+
87
+ ### 3. Stack hints
51
88
 
52
- ### 3. Apply with confirmation (recommended)
89
+ `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).
90
+
91
+ ```bash
92
+ npx @elmundi/ship-cli init --yes \
93
+ --agents cursor,codex --tracker linear --ci gh-actions --preset web-app
94
+ ```
95
+
96
+ ### 4. Apply with confirmation (recommended)
53
97
 
54
98
  Interactive run prints the plan and asks **Apply these changes? [y/N]**:
55
99
 
@@ -57,9 +101,9 @@ Interactive run prints the plan and asks **Apply these changes? [y/N]**:
57
101
  npx @elmundi/ship-cli init
58
102
  ```
59
103
 
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.
104
+ 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|collection`** for catalog bodies, and **`shipctl docs feedback`** for safe retro notes.
61
105
 
62
- ### 4. Non-interactive (CI or scripts)
106
+ ### 5. Non-interactive (CI or scripts)
63
107
 
64
108
  Only after you are happy with **`--dry-run`**:
65
109
 
@@ -69,23 +113,605 @@ npx @elmundi/ship-cli init --yes
69
113
 
70
114
  **`--force`** replaces blocks that were already injected (same marker). Without **`--force`**, existing injections are skipped.
71
115
 
116
+ ## Init flow
117
+
118
+ `shipctl init` is the primary adoption entrypoint. It composes four steps in one
119
+ command:
120
+
121
+ 1. **Config** — creates `.ship/config.yml` (via `DEFAULT_CONFIG`), seeds an
122
+ anonymous telemetry id, writes `.ship/state.json`, and appends
123
+ `.ship/cache/` to `.gitignore`. Existing configs are respected and only
124
+ updated with the flags/doctor proposal.
125
+ 2. **Telemetry** — prompts once for opt-in (default **OFF**). Non-TTY runs and
126
+ `--yes` default to OFF. `--telemetry on|off|ask` overrides the prompt.
127
+ 3. **Doctor (no network)** — runs the adapter detect-layer to propose
128
+ `tracker / ci / agents / language` values for gaps the user didn't set
129
+ explicitly via flags. Detection never overrides explicit flags.
130
+ 4. **Sync** — calls `syncArtifacts()` to pull the derived collection artifacts
131
+ (`collection/agent-rules-<agent>`, `collection/preset-<preset>`, optionally
132
+ `collection/adoption-playbook`) into `.ship/cache/`.
133
+
134
+ Optional post-steps:
135
+
136
+ - **`--copy-rules`** — for every agent in `stack.agents`, reads the cached
137
+ `agent-rules-<agent>` artifact, extracts `install_target` + `marker` from the
138
+ front-matter, and writes (or upserts) the rules file at the target path.
139
+ A `<!-- ship-cli: installed-from collection/agent-rules-<agent>@<version> -->`
140
+ footer is appended so re-runs can detect the installed version. Downgrades /
141
+ different versions are skipped unless `--force` is passed.
142
+ - **`--bootstrap`** — renders CI + tracker + secrets scaffolding. v1 special-
143
+ cases `mobile-app + gh-actions + linear` (writes `.github/workflows/ship-pilot.yml`
144
+ skeleton, `.ship/labels.yml`, and a `# --- ship-managed ---` block appended to
145
+ `.env.example`). All other combinations emit `SHIP_BOOTSTRAP_PLAN.md` with a
146
+ TODO checklist pointing at the preset artifact for full details.
147
+ - **`--copy-playbook`** — fetches `collection/adoption-playbook` when
148
+ published; a 404 is silently skipped (does not fail the command).
149
+
150
+ ### Three primary invocations
151
+
152
+ **MVP (dry-run preview, recommended first run):**
153
+
154
+ ```bash
155
+ shipctl init --dry-run --agents cursor --preset adoption-minimum --tracker none --ci manual
156
+ ```
157
+
158
+ **Pilot (apply, no scaffolding):**
159
+
160
+ ```bash
161
+ shipctl init --yes \
162
+ --agents cursor,claude-md \
163
+ --preset web-app --tracker linear --ci gh-actions \
164
+ --copy-rules --telemetry off
165
+ ```
166
+
167
+ **Full bootstrap (mobile-app, with scaffolding skeletons):**
168
+
169
+ ```bash
170
+ shipctl init --bootstrap --yes \
171
+ --agents cursor,claude-md,codex \
172
+ --tracker linear --ci gh-actions --preset mobile-app \
173
+ --copy-rules --telemetry off
174
+ ```
175
+
176
+ ### Full flag surface
177
+
178
+ ```
179
+ shipctl init
180
+ [--yes] [--force] [--dry-run] [--cwd DIR] [--json]
181
+ [--agents cursor,codex,claude-md] # preferred, csv
182
+ [--tracker linear] # linear|jira|github-issues|…|none
183
+ [--ci gh-actions] # gh-actions|gitlab-ci|…|manual
184
+ [--preset mobile-app] # web-app|api-backend|mobile-app|…
185
+ [--language ts] # ts|js|py|go|rust|…|multi
186
+ [--channel stable|edge] # override api.channel
187
+ [--copy-rules] # install agent-rules-<agent> files
188
+ [--copy-playbook] # fetch adoption-playbook into cache
189
+ [--bootstrap] # render CI/tracker scaffolding from preset
190
+ [--telemetry on|off|ask] # override the interactive prompt
191
+ ```
192
+
193
+ ### Example output
194
+
195
+ ```
196
+ Ship init complete
197
+ -----------------
198
+ Config: .ship/config.yml
199
+ Agents: cursor, claude-md
200
+ Tracker: linear
201
+ CI: gh-actions
202
+ Preset: mobile-app
203
+ Channel: stable
204
+ Telemetry: off
205
+
206
+ Installed rules:
207
+ - .cursor/rules/ship-artifacts-protocol.mdc (from collection/agent-rules-cursor@1.0.0) [wrote]
208
+ - CLAUDE.md (from collection/agent-rules-claude-md@1.0.0) [wrote]
209
+
210
+ Bootstrap (preset=mobile-app):
211
+ - wrote SHIP_BOOTSTRAP_PLAN.md
212
+ - wrote .github/workflows/ship-pilot.yml
213
+ - wrote .ship/labels.yml
214
+ - appended .env.example
215
+
216
+ Next:
217
+ shipctl sync # keep artifacts fresh
218
+ shipctl verify # check tracker labels, CI secrets, rules markers
219
+ shipctl feedback draft # submit improvement idea
220
+ ```
221
+
72
222
  ## Commands (quick reference)
73
223
 
74
- | Command | Role |
75
- |--------|------|
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. |
224
+ | Command | Role | Manual |
225
+ |---------|------|--------|
226
+ | **`shipctl init`** | Bootstrap an existing repo: agent rules, `.ship/config.yml`, optional CI scaffolding. | `documentation/discovery.md` |
227
+ | **`shipctl new`** | Greenfield: `git init` + minimal README + `init`. | this README, `New & Verify` |
228
+ | **`shipctl doctor`** | Inspect repo, propose stack, optionally write `.ship/inventory.json`. | this README, `Doctor` |
229
+ | **`shipctl config`** | Safe edits to `.ship/config.yml` (`init` / `get` / `set` / `validate` / `show` / `path`). | `documentation/configuration.md` |
230
+ | **`shipctl search`** | Vector search over docs + prompts (`POST /search`). | `documentation/discovery.md` |
231
+ | **`shipctl docs fetch`**, **`shipctl docs feedback`** | Documentation file fetch and retro feedback. | `documentation/discovery.md` |
232
+ | **`shipctl pattern\|tool\|collection`** **`list \| show \| fetch \| search`** | Versioned artifact bodies; plural aliases (`patterns`, `tools`, `collections`) work. | `documentation/authoring.md` |
233
+ | **`shipctl sync`** | Pull artifacts into `.ship/cache/`; with `--lock` writes `.ship/shipctl.lock.json` covering every Play the declared lanes depend on. | this README, `Config & Sync` |
234
+ | **`shipctl run`** | One-shot dispatch entry point. `kind: once` runs locally; other lane kinds are queued for the workspace runner. Reports its terminal status via the callback URL Ship injected. | `documentation/automations.md`, this README |
235
+ | **`shipctl lanes`** | Generate / inspect / delete the `.github/workflows/ship-<lane>.yml` thin wrappers (`install` / `list` / `remove`). | `documentation/automations.md`, this README |
236
+ | **`shipctl kickoff`** | Print a Play's pattern body for piping into the customer's agent in CI. | `documentation/automations.md` |
237
+ | **`shipctl callback`** | Pattern-side: report a Run's terminal status + RunSummary outcome so Ship can render the row and route escalations into the Inbox. | this README |
238
+ | **`shipctl knowledge init`** | Open a PR that seeds `.ship/knowledge/*.md` starter buckets. | `documentation/knowledge-buckets.md` |
239
+ | **`shipctl telemetry`** | Opt-in anonymous usage events (default OFF). | this README, `Telemetry & Feedback` |
240
+ | **`shipctl feedback`** | Local markdown drafts → POST `/feedback` → GitHub issue. | this README |
241
+ | **`shipctl verify`** | Post-adoption liveness checks (local + config + network). | this README, `New & Verify` |
242
+ | **`shipctl migrate`** | Upgrade `.ship/config.yml` from v1 to v2 (lanes-as-config). | `documentation/configuration.md` |
243
+ | **`shipctl help`** | Top-level command list with the same vocabulary callout. | — |
244
+
245
+ **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.
246
+
247
+ Run **`shipctl help`** for the operator-first overview with the same vocabulary callout printed at the top.
248
+
249
+ ## Doctor
250
+
251
+ **`shipctl doctor`** inspects a repository using the pluggable adapter registry
252
+ (`cli/lib/adapters/`) and proposes a best-guess Ship stack. It runs every
253
+ tracker / CI / language / agent adapter's `detect(cwd)` hook, scores the
254
+ findings, and infers a preset from repository structure (e.g. `pubspec.yaml`
255
+ or `react-native` in deps → `mobile-app`; `packages/` or `pnpm-workspace.yaml`
256
+ → `monorepo`). Nothing is ever written without **`--write-inventory`**.
257
+
258
+ ```bash
259
+ shipctl doctor # human report
260
+ shipctl doctor --json # machine-readable
261
+ shipctl doctor --cwd /path/to/other-repo # inspect elsewhere
262
+ shipctl doctor --write-inventory # persist .ship/inventory.json
263
+ ```
264
+
265
+ Example output (trimmed):
266
+
267
+ ```text
268
+ Ship doctor — inspecting /path/to/your-product
269
+
270
+ Tracker: linear (0.95) · evidence: .env (LINEAR_API_KEY), package.json (@linear/sdk)
271
+ CI: gh-actions (1.00) · evidence: .github/workflows/ (3 workflow(s))
272
+ Language: ts (1.00) · evidence: tsconfig.json (present)
273
+ Agents: cursor (1.00), claude-md (1.00)
274
+
275
+ Inferred preset: web-app (evidence: next.config.ts)
276
+
277
+ Existing Ship artifacts:
278
+ .ship/config.yml missing
279
+ .ship/cache/ missing
280
+ .ship/inventory.json missing
281
+ .cursor/rules/ship-* missing
282
+
283
+ Recommendations:
284
+ 1. shipctl config init
285
+ 2. shipctl init --bootstrap --tracker linear --ci gh-actions --agents cursor,claude-md --preset web-app
286
+ 3. shipctl sync
287
+ 4. shipctl verify
288
+ ```
289
+
290
+ Passing **`--write-inventory`** persists the findings to `.ship/inventory.json`
291
+ so that `shipctl init --bootstrap` can pick up the inferred stack without
292
+ re-running detection.
293
+
294
+ ## Config & Sync
295
+
296
+ Ship stores local state under **`.ship/`**. Methodology bodies never live in your
297
+ repo git history — `shipctl sync` caches them in `.ship/cache/`, which is
298
+ `.gitignore`d by default.
299
+
300
+ ### `.ship/` layout
301
+
302
+ ```
303
+ .ship/
304
+ ├── config.yml # RFC-0002 schema; committed. `lanes:` entries
305
+ │ # are what the operator console renders as
306
+ │ # Automations.
307
+ ├── state.json # last_sync_at, last_manifest_hash; gitignored
308
+ ├── shipctl.lock.json # set by `shipctl sync --lock`; pins every
309
+ │ # pattern the declared lanes depend on
310
+ ├── cache/ # per-repo artifact cache (gitignored)
311
+ │ ├── pattern/<id>@<v>/
312
+ │ │ ├── ARTIFACT.md # full body (frontmatter + content), per RFC-0005
313
+ │ │ └── .meta.json # source, sha256, fetched_at, etc.
314
+ │ ├── tool/<id>@<v>/…
315
+ │ ├── collection/<id>@<v>/…
316
+ │ └── doc/…
317
+ ├── knowledge/ # operator-edited markdown buckets, committed
318
+ ├── telemetry-outbox.jsonl # buffered telemetry events (gitignored)
319
+ └── feedback-drafts/ # feedback draft markdowns (gitignored)
320
+ ```
321
+
322
+ ### `shipctl config`
323
+
324
+ ```bash
325
+ shipctl config init # bootstrap .ship/config.yml + state.json + cache/
326
+ shipctl config path # print absolute path to config.yml
327
+ shipctl config show # pretty-print effective YAML
328
+ shipctl config get <dotted.key> # e.g. shipctl config get api.channel
329
+ shipctl config set <k> <value> # atomic write, validates before saving
330
+ shipctl config validate # exit 10 on invalid enum / bad URL / bad pin key
331
+ ```
332
+
333
+ Value parsing for `config set`:
334
+
335
+ - Bare `true|false|null` → booleans / null.
336
+ - `-?\d+(.\d+)?` → number.
337
+ - `[a,b,c]` → array of strings (quotes optional).
338
+ - Anything else → string.
339
+
340
+ Dotted keys under `artifacts.pins` preserve the embedded slash:
341
+ `artifacts.pins.pattern/role-developer`.
342
+
343
+ ```bash
344
+ shipctl config set stack.agents [cursor,codex]
345
+ shipctl config set api.channel edge
346
+ shipctl config set artifacts.pins.pattern/role-developer 1.4.2
347
+ ```
348
+
349
+ ### `shipctl sync`
80
350
 
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.
351
+ ```bash
352
+ shipctl sync # pull latest for this stack
353
+ shipctl sync --check-only # report changes without writing cache
354
+ shipctl sync --dry-run # --check-only + planned HTTP calls
355
+ shipctl sync --only pattern:role-developer [--only tool:gh-actions]
356
+ shipctl sync --channel edge
357
+ shipctl sync --force-unpin # temporarily ignore version pins
358
+ shipctl sync --lock # write .ship/shipctl.lock.json after sync
359
+ ```
360
+
361
+ Summary format:
362
+
363
+ ```
364
+ up_to_date: 12
365
+ updated: 3
366
+ skipped_pin: 2
367
+ deprecated: 1 (…)
368
+ yanked: 0
369
+ failed: 0
370
+ ```
371
+
372
+ Pins are honoured: an entry whose manifest version does not satisfy the pin is
373
+ reported as `skipped_pin` unless `--force-unpin` is set. After a successful
374
+ sync, `.ship/state.json` records `last_sync_at` and `last_manifest_hash`. With
375
+ `--lock`, `shipctl sync` also writes `.ship/shipctl.lock.json` covering every
376
+ Play that the declared lanes depend on, so subsequent `shipctl run` invocations
377
+ can refuse to drift off the pinned set.
378
+
379
+ > Methodology docs never live in your repo. `shipctl sync` caches them in
380
+ > `.ship/cache/`, sealed by `content_sha256` from the Ship manifest.
82
381
 
83
- Run **`ship help`** for full usage.
382
+ ## Run
84
383
 
85
- ## Versioning (until the CLI stabilizes)
384
+ `shipctl run` is the **one-shot dispatch entry point** for an Automation
385
+ (YAML key: `lanes:`). What it does depends on the lane's `kind`:
86
386
 
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.
387
+ | `kind:` value | Behaviour of `shipctl run` |
388
+ |---------------|----------------------------|
389
+ | `once` | Executes the lane fully on the local machine (the pattern body is fed to the configured agent; the result is reported back via `shipctl callback`). |
390
+ | `lane` / `event` / `schedule` | Refuses to execute locally; the workspace's GitHub Actions runner picks the lane up via `.github/workflows/run-agent.yml`. The CLI exits with a clear message naming the lane id and its kind. |
391
+
392
+ ```bash
393
+ # preview which patterns + parameters the lane would dispatch with
394
+ shipctl run --lane pr-self-review --dry-run
395
+
396
+ # fanout: the same pattern over every repo in the workspace
397
+ shipctl run --lane fleet-mobile-knowledge-refresh \
398
+ --pattern fleet-knowledge-pack \
399
+ --fanout matrix \
400
+ --trigger event \
401
+ --json
402
+
403
+ # CI usage: shipctl injects --ship-run-id / --ship-callback-url / --ship-run-token
404
+ # automatically; you only set them by hand when running outside the workspace runner
405
+ shipctl run --lane release-cut \
406
+ --ship-run-id "$SHIP_RUN_ID" \
407
+ --ship-callback-url "$SHIP_CALLBACK_URL" \
408
+ --ship-run-token "$SHIP_RUN_TOKEN"
409
+ ```
410
+
411
+ Important flags:
412
+
413
+ - `--pattern <id>` — override the lane's default pattern (a composite Play may
414
+ declare several; this lets you target one specifically).
415
+ - `--fanout matrix|sequential|concurrent` — only meaningful for fleet-scope
416
+ lanes that target multiple repos.
417
+ - `--trigger event|schedule|manual|once` — the trigger the lane was wired for;
418
+ used to choose the payload shape.
419
+ - `--offline` — skip every HTTP probe (resolves patterns from `.ship/cache/`
420
+ only); useful for hermetic CI.
421
+ - `--dry-run` — print the dispatch plan and exit 0; no agent invocation.
422
+
423
+ A full Run lifecycle in production looks like: console (or schedule) creates a
424
+ `pipeline_run` row → workspace runner is dispatched → runner calls
425
+ `shipctl run --lane <id>` for `kind: once` lanes (or invokes the agent
426
+ directly for `kind: lane`) → the pattern calls `shipctl callback` with the
427
+ RunSummary → console renders the outcome row in `/runs` and any escalations
428
+ land in `/inbox`.
429
+
430
+ ## Lanes
431
+
432
+ `shipctl lanes` manages the **thin GitHub Actions wrappers** that delegate to
433
+ the reusable `run-agent.yml` workflow. Each lane in `.ship/config.yml`
434
+ generates one `.github/workflows/ship-<lane>.yml` file. The file itself is a
435
+ ~12-line yaml that does nothing more than `uses: ./.github/workflows/run-agent.yml`
436
+ with the right `lane:` input — all the logic lives in the reusable workflow,
437
+ so wrappers can be regenerated without touching execution semantics.
438
+
439
+ ```bash
440
+ # write workflow files for every lane in config.yml
441
+ shipctl lanes install --dry-run
442
+ shipctl lanes install --yes
443
+
444
+ # only one lane (or a few)
445
+ shipctl lanes install --only pr-self-review,release-cut
446
+
447
+ # wire to a specific shipctl version pin
448
+ shipctl lanes install --shipctl-version 0.12.0 \
449
+ --owner elmundi --repo ship --ref v0.12.0
450
+
451
+ # inspect what's on disk vs config.yml
452
+ shipctl lanes list --json
453
+
454
+ # remove generated wrappers (does NOT touch lanes: config)
455
+ shipctl lanes remove --dry-run
456
+ shipctl lanes remove --only deprecated-lane --yes
457
+ ```
458
+
459
+ Notes:
460
+
461
+ - `lanes install` writes files **only** for lanes with a configured trigger
462
+ (`on.push`, `on.schedule`, `on.workflow_dispatch`). `kind: once` lanes
463
+ intended to run only via `shipctl run` don't get a wrapper.
464
+ - The same wrapper covers a fleet-scope lane (one workflow file in your
465
+ pilot repo, fanout happens server-side via the matrix Ship dispatches).
466
+ - Removing a wrapper does **not** remove the lane from `.ship/config.yml`;
467
+ to fully retire an Automation, drop the `lanes.<id>` block from the YAML
468
+ and re-run `shipctl lanes install` so the file is reconciled.
469
+
470
+ ## Callback
471
+
472
+ `shipctl callback` is what a Play's pattern calls to **close the loop on a
473
+ Run**. It POSTs a structured payload to the URL Ship injected into the
474
+ runner (`SHIP_CALLBACK_URL` env or `--callback-url`) so the operator console
475
+ can render an outcome-first row in `/runs` and route any escalations into
476
+ `/inbox`.
477
+
478
+ The flag surface is **protocol-stable** (the workspace API depends on it);
479
+ the help is grouped by intent.
480
+
481
+ ### Identity (one of these is required)
482
+
483
+ ```
484
+ --run-id <uuid> # falls back to SHIP_RUN_ID
485
+ --callback-url <url> # falls back to SHIP_CALLBACK_URL
486
+ SHIP_RUN_TOKEN=<jwt> # bearer for the callback URL (CI sets this)
487
+ SHIP_API_BASE=<url> # base for {api}/v1/runs/<id>/callback when only --run-id is set
488
+ ```
489
+
490
+ ### Status & summary
491
+
492
+ ```
493
+ --status ok|fail|cancelled # required terminal state
494
+ --summary "Free text" # short human readout (kept in addition to outcome)
495
+ --metric key=value # repeatable; persisted as-is
496
+ ```
497
+
498
+ ### RunSummary outcome (Phase 3)
499
+
500
+ These map 1:1 to the `outcome:` JSONB column on `pipeline_runs` and to what
501
+ the console renders:
502
+
503
+ ```
504
+ --outcome-text "Reviewed PR · 3 suggestions · 1 fix applied"
505
+ --findings-count 3
506
+ --severity high=1 --severity medium=2 # repeatable; map of sev → count
507
+ --artifact pr:"Auto-fix: typo":"https://...": # repeatable; type:title[:ref]
508
+ --artifact comment:"Self-review summary":"https://github.com/.../pull/42#…"
509
+ --escalation clarification:"agent_low_confidence" # repeatable; type:reason
510
+ --requires-approval # toggle the approval gate
511
+ --approval-payload '{"...": "..."}' # JSON forwarded to the Inbox item
512
+ ```
513
+
514
+ You can also pass the full RunSummary as JSON via:
515
+
516
+ - `SHIP_RUN_OUTCOME=$JSON_STRING`
517
+ - `SHIP_RUN_OUTCOME_FILE=/path/to/outcome.json`
518
+
519
+ Flags merge on top of the env / file payload (CLI wins).
520
+
521
+ ### Example (canonical pattern recipe)
522
+
523
+ ```bash
524
+ shipctl callback --status ok \
525
+ --outcome-text "Reviewed PR · 3 suggestions · 1 fix applied" \
526
+ --findings-count 3 \
527
+ --severity high=1 --severity medium=2 \
528
+ --artifact comment:"PR self-review summary":"https://github.com/elmundi/ship/pull/42#issuecomment-…" \
529
+ --artifact pr:"Auto-fix: typo in README":"https://github.com/elmundi/ship/pull/43"
530
+ ```
531
+
532
+ The same `## Reporting` block lives at the bottom of every top-Play pattern
533
+ (see `artifacts/patterns/flow-pr-self-review/ARTIFACT.md` for the canonical
534
+ example). When you author a new Play, copy that block as the contract you
535
+ expect runners to honour.
536
+
537
+ ## Knowledge
538
+
539
+ `shipctl knowledge init` opens a PR in the target repo that seeds the
540
+ `.ship/knowledge/` starter buckets (e.g. `code-style.md`, `ui-runbook.md`).
541
+ The PR is intentionally minimal — operators are expected to fill the buckets
542
+ in over time.
543
+
544
+ ```bash
545
+ # requires SHIP_API_TOKEN; targets the workspace's wired GitHub installation
546
+ shipctl knowledge init \
547
+ --workspace 11111111-1111-1111-1111-111111111111 \
548
+ --repo elmundi/ship-pilot \
549
+ --only code-style,ui-runbook \
550
+ --json
551
+ ```
552
+
553
+ Behind the scenes, this hits the workspace API which uses the GitHub App
554
+ installation to open the PR. The buckets the Plays read at runtime live
555
+ under the same `.ship/knowledge/` tree the PR seeds.
556
+
557
+ ## Telemetry & Feedback
558
+
559
+ Anonymous telemetry is **opt-in and OFF by default** (RFC-0003). Nothing leaves
560
+ the repo until you explicitly flip the switch with `shipctl telemetry on`.
561
+ Events are first buffered to `.ship/telemetry-outbox.jsonl`; flushing to
562
+ `POST /telemetry` is a deliberate, retry-safe step.
563
+
564
+ ```bash
565
+ # inspect current state
566
+ shipctl telemetry status # share=..., anonymous_id=..., outbox_pending=N
567
+
568
+ # opt in, with a narrow scope, non-interactively
569
+ shipctl telemetry on --scope artifact_usage,improvement_drafts --yes
570
+
571
+ # look at what's queued locally before sending
572
+ shipctl telemetry buffer --limit 10
573
+
574
+ # send any queued events (batches of 100); succeeded lines are removed
575
+ shipctl telemetry flush
576
+ shipctl telemetry flush --dry-run # preview only
577
+
578
+ # data rights: export or delete by anonymous_id
579
+ shipctl telemetry export --out telemetry.json
580
+ shipctl telemetry delete-my-data # interactive confirmation required
581
+
582
+ # rotate identity (server treats the previous id as a separate adopter)
583
+ shipctl telemetry reset-id
584
+
585
+ # fully disable
586
+ shipctl telemetry off
587
+ ```
588
+
589
+ Allowed event types: `artifact.fetch`, `artifact.use`, `artifact.sync`,
590
+ `feedback.submit`, `doctor.result`. Payload keys in the RFC-0003 denylist
591
+ (`path, code, diff, branch, remote, email`) are stripped client-side before
592
+ anything is appended to the outbox.
593
+
594
+ Feedback is always drafted locally as a markdown file before it is sent
595
+ anywhere:
596
+
597
+ ```bash
598
+ # create a draft
599
+ shipctl feedback draft --kind pattern --id role-developer --version 1.4.2 \
600
+ --title "Missing mobile preview step" \
601
+ --summary "Evidence checklist misses mobile preview" \
602
+ --recommendation "Add a bullet under Evidence"
603
+
604
+ # review / edit (uses $EDITOR)
605
+ shipctl feedback list
606
+ shipctl feedback show .ship/feedback-drafts/2026-04-17-11-30-15-pattern-role-developer.md
607
+ shipctl feedback edit .ship/feedback-drafts/2026-04-17-11-30-15-pattern-role-developer.md
608
+
609
+ # submit → POST /feedback → GitHub issue URL; draft moves to sent/
610
+ shipctl feedback submit .ship/feedback-drafts/2026-04-17-11-30-15-pattern-role-developer.md --yes
611
+ ```
612
+
613
+ Submission requires `kind`, `id`, `title`, and `summary`; missing fields fail
614
+ with exit 1 (nothing sent). When `telemetry.share=true` and
615
+ `scope.improvement_drafts=true`, a `feedback.submit` event is appended to the
616
+ telemetry outbox on successful submission.
617
+
618
+ ## New & Verify
619
+
620
+ ### `shipctl new <name>`
621
+
622
+ Scaffolds a fresh repo with the Ship wiring already in place: creates the
623
+ target directory, runs `git init -q`, drops a minimal `README.md`, seeds
624
+ `.ship/config.yml` via `shipctl config init`, applies the provided stack
625
+ flags via `shipctl config set`, and (when `--agents` is supplied) runs
626
+ `shipctl init --yes --copy-rules …` to install the agent rule files.
627
+
628
+ ```bash
629
+ shipctl new pharma-pilot \
630
+ --preset mobile-app --tracker linear --ci gh-actions \
631
+ --agents cursor,claude,codex --yes
632
+ cd pharma-pilot
633
+ shipctl verify --no-network
634
+ ```
635
+
636
+ Common flags:
637
+
638
+ - `--here` — initialize in the current directory instead of creating `<name>/`.
639
+ - `--preset / --tracker / --ci / --agents / --language / --channel` —
640
+ forwarded to `config set` + `init`.
641
+ - `--yes` — non-interactive (required for CI / dry-run).
642
+ - `--dry-run` — describe the plan without touching disk.
643
+ - `--json` — machine-readable summary of created files.
644
+
645
+ ### `shipctl verify`
646
+
647
+ Post-adoption liveness check. A collection of independent checks under
648
+ `cli/lib/verify/checks/` grouped by category:
649
+
650
+ - **local** — `.ship/config.yml` present, `.gitignore` excludes
651
+ `.ship/cache/`, agent rule files have the
652
+ `<!-- ship-cli: artifacts-protocol v1 -->` marker and `installed-from`
653
+ footer, cached artifacts match their `.meta.json` sha256, bootstrap
654
+ scaffolding (mobile-app + gh-actions + linear) carries
655
+ `ship-managed` markers.
656
+ - **config** — `stack.*` enum re-validation, declared agents have
657
+ on-disk signals.
658
+ - **network** (skip with `--no-network`) — `/health` (or `/patterns` as a
659
+ fallback) reachable, local cache matches the channel catalog aggregated
660
+ across `/patterns`, `/tools`, `/collections`, Linear labels exist (needs
661
+ `LINEAR_API_KEY`), every `${{ secrets.X }}` reference in gh-actions
662
+ workflows is declared in `.env.example`.
663
+
664
+ Exit `0` when no check returned `fail`; warnings do not fail.
665
+
666
+ ```bash
667
+ shipctl verify # full run
668
+ shipctl verify --no-network # skip HTTP + Linear calls
669
+ shipctl verify --check rules-markers,cache-integrity
670
+ shipctl verify --severity warn # hide pass rows
671
+ shipctl verify --json # { checks:[…], summary:{…}, exit_code }
672
+ ```
673
+
674
+ ## Versioning
675
+
676
+ `shipctl --version` (or `shipctl version`) prints the running release. The
677
+ version is part of every outbound `User-Agent` header so the methodology API
678
+ can correlate adoption metrics with the client release.
679
+
680
+ This package follows the **monorepo-wide** Ship version: a single `VERSION`
681
+ file at the repo root drives `cli/package.json`, `landing/package.json`,
682
+ `backend/app/main.py`, and the root `package.json` in lockstep via
683
+ `scripts/version.mjs` (`npm run version:bump -- patch|minor|major|x.y.z`).
684
+ See the root [`README.md`](../README.md#versioning--releases) for the full
685
+ release recipe.
88
686
 
89
687
  ## Publishing (maintainers)
90
688
 
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).
689
+ The GitHub Action **Publish @elmundi/ship-cli to npm** is triggered by either
690
+ the unified `v<x.y.z>` tag (preferred — bumps every component in lockstep) or
691
+ the legacy `cli-v<x.y.z>` tag (escape hatch for CLI-only patches). It runs
692
+ `scripts/version.mjs check` before publishing so an out-of-sync tree never
693
+ ships to npm.
694
+
695
+ ```bash
696
+ npm run version:bump -- minor # 0.10.0 → 0.11.0, syncs everything
697
+ git commit -am "release v$(cat VERSION)"
698
+ git tag "v$(cat VERSION)"
699
+ git push --follow-tags # publish workflow picks up v0.11.0
700
+ ```
701
+
702
+ Repository secret **`NPM_TOKEN`** is required. Publish from the monorepo
703
+ root: **`npm publish -w @elmundi/ship-cli`** — not `npm publish --prefix cli`
704
+ (the root package is private).
705
+
706
+ ## Reference
707
+
708
+ Protocol-stable surfaces:
709
+
710
+ - **Artifacts protocol** (`POST /search`, `POST /fetch`) — RFC-0001.
711
+ - **Stack block + presets + adapters** — RFCs 0002 / 0004 / 0006.
712
+ - **Telemetry** — RFC-0003.
713
+ - **Operator IA** (Plays / Automations / Runs / Inbox) — RFC-0010.
714
+ - **HTTP schemas live next to the source**: `artifacts/tools/methodology-api/ARTIFACT.md`.
715
+
716
+ Every consumed artifact should be recorded in the PR or commit log as
717
+ `<kind>:<id>@<version>` so reviewers can replay what the agent saw.