@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.
- package/README.md +415 -22
- package/bin/shipctl.mjs +165 -0
- package/lib/adapters/_fs.mjs +165 -0
- package/lib/adapters/agents/index.mjs +26 -0
- package/lib/adapters/ci/azure-pipelines.mjs +23 -0
- package/lib/adapters/ci/buildkite.mjs +24 -0
- package/lib/adapters/ci/circleci.mjs +23 -0
- package/lib/adapters/ci/gh-actions.mjs +29 -0
- package/lib/adapters/ci/gitlab-ci.mjs +23 -0
- package/lib/adapters/ci/jenkins.mjs +23 -0
- package/lib/adapters/ci/manual.mjs +18 -0
- package/lib/adapters/index.mjs +122 -0
- package/lib/adapters/language/dart.mjs +23 -0
- package/lib/adapters/language/go.mjs +23 -0
- package/lib/adapters/language/java.mjs +27 -0
- package/lib/adapters/language/js.mjs +32 -0
- package/lib/adapters/language/kotlin.mjs +48 -0
- package/lib/adapters/language/py.mjs +34 -0
- package/lib/adapters/language/rust.mjs +23 -0
- package/lib/adapters/language/swift.mjs +37 -0
- package/lib/adapters/language/ts.mjs +35 -0
- package/lib/adapters/trackers/azure-boards.mjs +49 -0
- package/lib/adapters/trackers/clickup.mjs +43 -0
- package/lib/adapters/trackers/github-issues.mjs +52 -0
- package/lib/adapters/trackers/jira.mjs +72 -0
- package/lib/adapters/trackers/linear.mjs +62 -0
- package/lib/adapters/trackers/none.mjs +18 -0
- package/lib/adapters/trackers/spreadsheet.mjs +28 -0
- package/lib/artifacts/fs-index.mjs +230 -0
- package/lib/bootstrap/render.mjs +373 -0
- package/lib/cache/store.mjs +422 -0
- package/lib/commands/bootstrap.mjs +4 -0
- package/lib/commands/callback.mjs +302 -0
- package/lib/commands/config.mjs +257 -0
- package/lib/commands/docs.mjs +1 -1
- package/lib/commands/doctor.mjs +583 -0
- package/lib/commands/feedback.mjs +355 -0
- package/lib/commands/help.mjs +96 -21
- package/lib/commands/init.mjs +830 -158
- package/lib/commands/kickoff.mjs +192 -0
- package/lib/commands/knowledge.mjs +368 -0
- package/lib/commands/lanes.mjs +502 -0
- package/lib/commands/manifest-catalog.mjs +102 -38
- package/lib/commands/migrate.mjs +204 -0
- package/lib/commands/new.mjs +452 -0
- package/lib/commands/patterns.mjs +9 -43
- package/lib/commands/run.mjs +617 -0
- package/lib/commands/sync.mjs +749 -0
- package/lib/commands/telemetry.mjs +390 -0
- package/lib/commands/verify.mjs +187 -0
- package/lib/config/io.mjs +232 -0
- package/lib/config/migrate.mjs +215 -0
- package/lib/config/schema.mjs +650 -0
- package/lib/detect.mjs +162 -19
- package/lib/feedback/drafts.mjs +129 -0
- package/lib/find-ship-root.mjs +16 -10
- package/lib/http.mjs +237 -11
- package/lib/state/idempotency.mjs +183 -0
- package/lib/state/lockfile.mjs +180 -0
- package/lib/telemetry/outbox.mjs +224 -0
- package/lib/templates.mjs +53 -65
- package/lib/verify/checks/agents-on-disk.mjs +58 -0
- package/lib/verify/checks/api-reachable.mjs +39 -0
- package/lib/verify/checks/artifacts-up-to-date.mjs +78 -0
- package/lib/verify/checks/bootstrap-files.mjs +67 -0
- package/lib/verify/checks/cache-integrity.mjs +51 -0
- package/lib/verify/checks/ci-secrets.mjs +86 -0
- package/lib/verify/checks/config-present.mjs +39 -0
- package/lib/verify/checks/gitignore-cache.mjs +51 -0
- package/lib/verify/checks/rules-markers.mjs +135 -0
- package/lib/verify/checks/stack-enums.mjs +33 -0
- package/lib/verify/checks/tracker-labels.mjs +91 -0
- package/lib/verify/registry.mjs +120 -0
- package/lib/version.mjs +34 -0
- package/package.json +10 -3
- 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
|
|
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 **`
|
|
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
|
|
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
|
|
27
|
+
- Default matches other Ship tooling (public host unless you override for local FastAPI).
|
|
28
28
|
|
|
29
|
-
### 2. Preview what `
|
|
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
|
-
`
|
|
38
|
+
`shipctl init` **detects what is already in the tree** and only plans injections for those stacks:
|
|
39
39
|
|
|
40
|
-
| If the repo has… | `
|
|
41
|
-
|
|
42
|
-
| `.cursor/` | Cursor rule **`.cursor/rules/ship-
|
|
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 **`--
|
|
58
|
+
Use **`--agents <csv>`** to limit targets and **`--cwd <dir>`** to point at another root. Example:
|
|
51
59
|
|
|
52
|
-
|
|
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 **
|
|
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
|
-
###
|
|
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
|
-
| **`
|
|
77
|
-
| **`
|
|
78
|
-
| **`
|
|
79
|
-
| **`
|
|
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
|
-
|
|
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
|
-
|
|
416
|
+
Common flags:
|
|
84
417
|
|
|
85
|
-
|
|
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
|
-
|
|
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**
|
|
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).
|
package/bin/shipctl.mjs
ADDED
|
@@ -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
|
+
}
|