continuous-improvement 3.22.0 → 3.23.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.
@@ -7,8 +7,8 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "continuous-improvement",
10
- "description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 28 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
11
- "version": "3.22.0",
10
+ "description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 29 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
11
+ "version": "3.23.0",
12
12
  "source": "./plugins/continuous-improvement",
13
13
  "author": {
14
14
  "name": "naimkatiman"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this skill are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.23.0] — 2026-08-15
8
+
9
+ ### Added
10
+
11
+ - **`ship` is now a native tier-1 global skill** — the full single-defect workflow lives in `skills/ship.md`: detect `main` or `master` from the live remote, preserve unrelated dirty work in an owner-locked clean worktree, enforce RED/GREEN verification, open one PR, and return only eligible clean checkouts to the default branch. The legacy `/ship` command is now a thin argument-forwarding compatibility entry point, and the npm installer writes the user-invocable skill to `~/.claude/skills/ship/SKILL.md` without overwriting or removing an unowned skill at that generic path. Brings the bundle to 29 skills.
12
+
13
+ ## [3.22.1] — 2026-08-04
14
+
15
+ ### Fixed
16
+
17
+ - **`ci-reconcile` blocks an unborn HEAD instead of treating it as a usable baseline** — `git init` with no commit yet is a real git repository, but `git rev-parse HEAD` fails there. The runner accepted that as a pinnable state, and `--snapshot` emitted an empty `head` field that reads like success. Now reported as a `head-commit` blocker, with `--snapshot` emitting `head: "unborn"` and `blocked: true`. (#291)
18
+ - **`isSafeRefName` rejects ref shapes git itself rejects** — empty path components (`a//b`), dot-prefixed or dot-suffixed components (`a/.b`, `a/b.`), and a per-component `.lock` suffix (`a/b.lock/c`) previously passed. Validation is now component-by-component, closer to `git check-ref-format`. (#291)
19
+ - **`parseRevListCounts` uses `Number.isSafeInteger`** — `Number.isInteger` accepts values beyond `MAX_SAFE_INTEGER`, where arithmetic silently loses precision. Out-of-range counts now read as `unknown`, consistent with the module's fail-closed stance. (#291)
20
+ - **The CLI rejects malformed argument combinations instead of resolving them silently** — a repeated `--verify-push` or `--cwd` used to be last-wins, an empty or whitespace-only `--cwd` fell back to the process working directory, and `--snapshot` / `--explain` / `--verify-push` together picked one arbitrarily. Each is now an explicit error. (#291)
21
+
22
+ ### Changed
23
+
24
+ - **`--snapshot` follows the same exit contract as the default mode** (`0` clear / `1` blocked / `2` not a repository) where it previously always exited `0`, and its envelope gains `blocked` and `blockers`. It still writes the JSON on a blocker, so a `set -e` script capturing a baseline must tolerate exit 1. Documented in the skill and command, along with the unborn-HEAD row. (#291)
25
+
7
26
  ## [3.22.0] — 2026-08-02
8
27
 
9
28
  ### Added
package/README.md CHANGED
@@ -270,7 +270,7 @@ Hooks capture every tool call. After ~20 observations Claude analyzes patterns a
270
270
  ## Slash commands
271
271
 
272
272
  <details>
273
- <summary><b>All 29 commands (Beginner gets every one)</b></summary>
273
+ <summary><b>All 29 commands (marketplace bundle)</b></summary>
274
274
 
275
275
  `/seven-laws` is the canonical reflect-and-learn command. `/continuous-improvement` is kept as an alias for backward compatibility — both run the same workflow.
276
276
 
@@ -306,7 +306,7 @@ Hooks capture every tool call. After ~20 observations Claude analyzes patterns a
306
306
  /swarm Fan-out coordination across parallel sub-agents
307
307
  ```
308
308
 
309
- All 29 ship in the marketplace bundle. The Beginner install gets all of them with one caveat: `/learn-eval`, `/harvest`, and `/distill` only produce useful output once Mulahazah has accumulated observation history (~20 observations), so running them on day 1 returns an empty result, not a broken command. `/swarm` and `/release-train` are orchestration commands aimed at larger multi-agent or multi-PR work. In Expert (`npx`) mode, the installer mirrors the full set into `~/.claude/commands/` and additionally exposes the planning workflow through the MCP tools `ci_plan_init` (initialize `task_plan.md`, `findings.md`, `progress.md` in the project root) and `ci_plan_status` (summarize their current contents).
309
+ All 29 legacy commands ship in the marketplace bundle. The marketplace Beginner install gets all of them, with one caveat: `/learn-eval`, `/harvest`, and `/distill` only produce useful output once Mulahazah has accumulated observation history (~20 observations), so running them on day 1 returns an empty result, not a broken command. `/swarm` and `/release-train` are orchestration commands aimed at larger multi-agent or multi-PR work. The npm installer writes its curated command set and installs the native `ship` skill at `~/.claude/skills/ship/SKILL.md`; that user-invocable skill supplies `/ship` without creating a duplicate personal command. Expert npm mode additionally exposes the planning workflow through the MCP tools `ci_plan_init` (initialize `task_plan.md`, `findings.md`, `progress.md` in the project root) and `ci_plan_status` (summarize their current contents).
310
310
 
311
311
  </details>
312
312
 
@@ -314,7 +314,7 @@ All 29 ship in the marketplace bundle. The Beginner install gets all of them —
314
314
 
315
315
  ## Skills
316
316
 
317
- The plugin ships **28 skills** — 1 core + 1 featured + 6 tier-1 + 17 tier-2 + 3 always-bundled. Beginner install gets tier-1, featured, and the always-bundled companion; Expert adds tier-2, the MCP server, and observation hooks. Full catalog with per-skill descriptions, Law tagging, and drop-in single-file install: [docs/skills.md](docs/skills.md). Adding a 29th skill: [CONTRIBUTING.md § Evolution — adding a new skill](CONTRIBUTING.md#evolution--adding-a-new-skill).
317
+ The plugin ships **29 skills** — 1 core + 1 featured + 7 tier-1 + 17 tier-2 + 3 always-bundled. Beginner install gets tier-1, featured, and the always-bundled companion; Expert adds tier-2, the MCP server, and observation hooks. The tier-1 `ship` skill is the single source for the `/ship` workflow and the npm installer makes it global across Claude Code projects. Full catalog with per-skill descriptions, Law tagging, and drop-in single-file install: [docs/skills.md](docs/skills.md). Adding a 30th skill: [CONTRIBUTING.md § Evolution — adding a new skill](CONTRIBUTING.md#evolution--adding-a-new-skill).
318
318
 
319
319
  ---
320
320
 
@@ -357,7 +357,7 @@ Proof-format templates ship in [templates/](templates/): `release_receipt_templa
357
357
 
358
358
  - [QUICKSTART.md](QUICKSTART.md) — 2-minute setup
359
359
  - [SKILL.md](SKILL.md) — full 7 Laws spec
360
- - [docs/skills.md](docs/skills.md) — full 28-skill catalog
360
+ - [docs/skills.md](docs/skills.md) — full 29-skill catalog
361
361
  - [examples/](examples/) — bug fix, feature build, refactor walkthroughs
362
362
  - [templates/insights-claude-md.md](templates/insights-claude-md.md) — paste-in CLAUDE.md blocks for verification discipline, environment notes, think-before-acting, and git/deploy workflow (sourced from the 28-day usage report)
363
363
  - [CONTRIBUTING.md](CONTRIBUTING.md) — architecture, repo internals, adding a new skill
package/bin/install.mjs CHANGED
@@ -9,10 +9,10 @@
9
9
  * npx continuous-improvement install --target gemini,codex # skill into other agents' rules files
10
10
  * npx continuous-improvement install --uninstall # remove everything
11
11
  */
12
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
12
+ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
13
13
  import { execSync } from "node:child_process";
14
14
  import { homedir } from "node:os";
15
- import { dirname, join } from "node:path";
15
+ import { dirname, isAbsolute, join, relative, sep } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { createHash } from "node:crypto";
18
18
  import { PACKAGE_NAME, VERSION, getToolNames } from "../lib/plugin-metadata.mjs";
@@ -21,6 +21,7 @@ import { evaluateUpdateCheck, fetchLatestNpmVersion, isThrottled, pendingNotice,
21
21
  const __filename = fileURLToPath(import.meta.url);
22
22
  const __dirname = dirname(__filename);
23
23
  const SKILL_SOURCE = join(__dirname, "..", "SKILL.md");
24
+ const SHIP_SKILL_SOURCE = join(__dirname, "..", "skills", "ship.md");
24
25
  const SKILL_NAME = "continuous-improvement";
25
26
  const REPO_ROOT = join(__dirname, "..");
26
27
  const COMMAND_FILES = [
@@ -124,17 +125,169 @@ const modeIndex = rawArgs.indexOf("--mode");
124
125
  const requestedMode = modeIndex !== -1 ? rawArgs[modeIndex + 1] : undefined;
125
126
  const INSTALL_MODE = isInstallMode(requestedMode) ? requestedMode : "beginner";
126
127
  const SKILL_DIR = join(getHomeDir(), ".claude", "skills", SKILL_NAME);
128
+ const SHIP_SKILL_DIR = join(getHomeDir(), ".claude", "skills", "ship");
129
+ const SHIP_SKILLS_DIR = dirname(SHIP_SKILL_DIR);
130
+ const SHIP_STAGING_ROOT = join(getHomeDir(), ".claude", ".continuous-improvement-staging");
131
+ const SHIP_SKILL_OWNER_FILE = join(SHIP_SKILL_DIR, ".continuous-improvement-owner");
132
+ const SHIP_SKILL_OWNER = `${PACKAGE_NAME}\n`;
133
+ function pathEntryExists(path) {
134
+ try {
135
+ lstatSync(path);
136
+ return true;
137
+ }
138
+ catch (error) {
139
+ if (error.code === "ENOENT")
140
+ return false;
141
+ throw error;
142
+ }
143
+ }
144
+ function isOwnedShipSkill() {
145
+ try {
146
+ const dir = lstatSync(SHIP_SKILL_DIR);
147
+ const owner = lstatSync(SHIP_SKILL_OWNER_FILE);
148
+ if (!dir.isDirectory() || dir.isSymbolicLink())
149
+ return false;
150
+ if (!owner.isFile() || owner.isSymbolicLink() || owner.nlink !== 1)
151
+ return false;
152
+ return readFileSync(SHIP_SKILL_OWNER_FILE, "utf8") === SHIP_SKILL_OWNER;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ function isSameOrChildPath(parent, candidate) {
159
+ const relativePath = relative(parent, candidate);
160
+ return (relativePath === "" ||
161
+ (relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath)));
162
+ }
163
+ function getSafeShipStagingRoot() {
164
+ mkdirSync(dirname(SHIP_STAGING_ROOT), { recursive: true });
165
+ try {
166
+ mkdirSync(SHIP_STAGING_ROOT);
167
+ }
168
+ catch (error) {
169
+ if (error.code !== "EEXIST")
170
+ throw error;
171
+ }
172
+ const stagingEntry = lstatSync(SHIP_STAGING_ROOT);
173
+ if (!stagingEntry.isDirectory() || stagingEntry.isSymbolicLink()) {
174
+ throw new Error(`Ship staging root must be a plain directory, not a link or junction: ${SHIP_STAGING_ROOT}`);
175
+ }
176
+ const stagingRoot = realpathSync(SHIP_STAGING_ROOT);
177
+ const skillsRoot = realpathSync(SHIP_SKILLS_DIR);
178
+ if (isSameOrChildPath(skillsRoot, stagingRoot)) {
179
+ throw new Error(`Ship staging root resolves inside skill discovery: ${stagingRoot}`);
180
+ }
181
+ return { stagingRoot, skillsRoot };
182
+ }
183
+ function stageShipSkill() {
184
+ mkdirSync(SHIP_SKILLS_DIR, { recursive: true });
185
+ const { stagingRoot, skillsRoot } = getSafeShipStagingRoot();
186
+ const createdStagingDir = mkdtempSync(join(stagingRoot, "ship-"));
187
+ try {
188
+ const stagingEntry = lstatSync(createdStagingDir);
189
+ const stagingDir = realpathSync(createdStagingDir);
190
+ if (!stagingEntry.isDirectory() ||
191
+ stagingEntry.isSymbolicLink() ||
192
+ !isSameOrChildPath(stagingRoot, stagingDir) ||
193
+ isSameOrChildPath(skillsRoot, stagingDir)) {
194
+ throw new Error(`Ship staging directory escaped its safe root: ${stagingDir}`);
195
+ }
196
+ copyFileSync(SHIP_SKILL_SOURCE, join(stagingDir, "SKILL.md"));
197
+ writeFileSync(join(stagingDir, ".continuous-improvement-owner"), SHIP_SKILL_OWNER);
198
+ return stagingDir;
199
+ }
200
+ catch (error) {
201
+ rmSync(createdStagingDir, { recursive: true, force: true });
202
+ throw error;
203
+ }
204
+ }
205
+ function installShipSkill() {
206
+ const shipSkillExists = pathEntryExists(SHIP_SKILL_DIR);
207
+ if (shipSkillExists && !isOwnedShipSkill()) {
208
+ console.warn(` ! Preserved existing unowned ship skill at ${SHIP_SKILL_DIR}`);
209
+ return "preserved";
210
+ }
211
+ let stagingDir = "";
212
+ let preserveStagingDir = false;
213
+ try {
214
+ stagingDir = stageShipSkill();
215
+ if (shipSkillExists) {
216
+ const skillPath = join(SHIP_SKILL_DIR, "SKILL.md");
217
+ const previousSkillPath = join(stagingDir, "previous-SKILL.md");
218
+ let previousSkillMoved = false;
219
+ if (pathEntryExists(skillPath)) {
220
+ const skillEntry = lstatSync(skillPath);
221
+ if (skillEntry.isDirectory()) {
222
+ throw new Error(`Refusing to replace directory at ${skillPath}`);
223
+ }
224
+ renameSync(skillPath, previousSkillPath);
225
+ previousSkillMoved = true;
226
+ }
227
+ try {
228
+ renameSync(join(stagingDir, "SKILL.md"), skillPath);
229
+ }
230
+ catch (error) {
231
+ if (previousSkillMoved) {
232
+ try {
233
+ renameSync(previousSkillPath, skillPath);
234
+ }
235
+ catch (restoreError) {
236
+ preserveStagingDir = true;
237
+ let recoveryRenameError = "";
238
+ const stagedSkillPath = join(stagingDir, "SKILL.md");
239
+ if (pathEntryExists(stagedSkillPath)) {
240
+ try {
241
+ renameSync(stagedSkillPath, join(stagingDir, "new-SKILL.md"));
242
+ }
243
+ catch (recoveryError) {
244
+ recoveryRenameError = ` Recovery artifact rename also failed (${getErrorMessage(recoveryError)}).`;
245
+ }
246
+ }
247
+ throw new Error(`Ship skill replacement failed (${getErrorMessage(error)}) and rollback failed (${getErrorMessage(restoreError)}).${recoveryRenameError} Recovery files retained outside skill discovery at ${stagingDir}`);
248
+ }
249
+ }
250
+ throw error;
251
+ }
252
+ rmSync(stagingDir, { recursive: true });
253
+ stagingDir = "";
254
+ }
255
+ else {
256
+ renameSync(stagingDir, SHIP_SKILL_DIR);
257
+ stagingDir = "";
258
+ }
259
+ console.log(` ✓ Global ship skill → ${SHIP_SKILL_DIR}/SKILL.md`);
260
+ return "installed";
261
+ }
262
+ catch (error) {
263
+ console.error(` ✗ Global ship skill install failed: ${getErrorMessage(error)}`);
264
+ return "failed";
265
+ }
266
+ finally {
267
+ if (stagingDir && !preserveStagingDir) {
268
+ try {
269
+ if (pathEntryExists(stagingDir)) {
270
+ rmSync(stagingDir, { recursive: true, force: true });
271
+ }
272
+ }
273
+ catch (cleanupError) {
274
+ console.error(` ! Staged ship skill cleanup failed at ${stagingDir}: ${getErrorMessage(cleanupError)}`);
275
+ }
276
+ }
277
+ }
278
+ }
127
279
  function installSkill() {
128
280
  try {
129
281
  mkdirSync(SKILL_DIR, { recursive: true });
130
282
  copyFileSync(SKILL_SOURCE, join(SKILL_DIR, "SKILL.md"));
131
283
  console.log(` ✓ Claude Code skill → ${SKILL_DIR}/SKILL.md`);
284
+ const shipOutcome = installShipSkill();
132
285
  setupMulahazah();
133
- return true;
286
+ return shipOutcome;
134
287
  }
135
288
  catch (error) {
136
289
  console.error(` ✗ Install failed: ${getErrorMessage(error)}`);
137
- return false;
290
+ return "failed";
138
291
  }
139
292
  }
140
293
  function setupMulahazah() {
@@ -326,9 +479,10 @@ function patchClaudeSettings(observePath) {
326
479
  }
327
480
  }
328
481
  function uninstallAll() {
329
- console.log("\nUninstalling continuous-improvement skill...\n");
482
+ console.log("\nUninstalling continuous-improvement skills...\n");
330
483
  const home = getHomeDir();
331
484
  let removed = 0;
485
+ let failed = false;
332
486
  if (existsSync(SKILL_DIR)) {
333
487
  try {
334
488
  rmSync(SKILL_DIR, { recursive: true });
@@ -337,6 +491,23 @@ function uninstallAll() {
337
491
  }
338
492
  catch (error) {
339
493
  console.error(` ✗ Skill removal failed: ${getErrorMessage(error)}`);
494
+ failed = true;
495
+ }
496
+ }
497
+ if (existsSync(SHIP_SKILL_DIR)) {
498
+ if (!isOwnedShipSkill()) {
499
+ console.warn(` ! Preserved existing unowned ship skill at ${SHIP_SKILL_DIR}`);
500
+ }
501
+ else {
502
+ try {
503
+ rmSync(SHIP_SKILL_DIR, { recursive: true });
504
+ console.log(" ✓ Removed global ship skill");
505
+ removed++;
506
+ }
507
+ catch (error) {
508
+ console.error(` ✗ Global ship skill removal failed: ${getErrorMessage(error)}`);
509
+ failed = true;
510
+ }
340
511
  }
341
512
  }
342
513
  for (const commandName of COMMAND_FILES) {
@@ -350,6 +521,7 @@ function uninstallAll() {
350
521
  }
351
522
  catch (error) {
352
523
  console.error(` ✗ ${commandName}: ${getErrorMessage(error)}`);
524
+ failed = true;
353
525
  }
354
526
  }
355
527
  for (const hookFile of ["observe.sh", "session.sh", "session.mjs"]) {
@@ -363,6 +535,7 @@ function uninstallAll() {
363
535
  }
364
536
  catch (error) {
365
537
  console.error(` ✗ ${hookFile}: ${getErrorMessage(error)}`);
538
+ failed = true;
366
539
  }
367
540
  }
368
541
  // Remove the Node observer artifacts deployed alongside observe.sh.
@@ -379,6 +552,7 @@ function uninstallAll() {
379
552
  }
380
553
  catch (error) {
381
554
  console.error(` ✗ ${observerFile}: ${getErrorMessage(error)}`);
555
+ failed = true;
382
556
  }
383
557
  }
384
558
  const settingsPath = join(home, ".claude", "settings.json");
@@ -435,12 +609,17 @@ function uninstallAll() {
435
609
  }
436
610
  else {
437
611
  console.warn(" ! Could not clean settings.json — remove hooks manually");
612
+ failed = true;
438
613
  }
439
614
  }
440
615
  const desktopConfigPath = join(home, ".claude", "claude_desktop_config.json");
441
616
  if (existsSync(desktopConfigPath)) {
442
617
  const desktopConfig = readJsonFile(desktopConfigPath);
443
- if (desktopConfig?.mcpServers?.["continuous-improvement"]) {
618
+ if (!desktopConfig) {
619
+ console.warn(" ! Could not clean claude_desktop_config.json — remove the MCP server manually");
620
+ failed = true;
621
+ }
622
+ else if (desktopConfig.mcpServers?.["continuous-improvement"]) {
444
623
  delete desktopConfig.mcpServers["continuous-improvement"];
445
624
  writeFileSync(desktopConfigPath, JSON.stringify(desktopConfig, null, 2) + "\n");
446
625
  console.log(" ✓ Removed MCP server from Claude Desktop config");
@@ -451,6 +630,7 @@ function uninstallAll() {
451
630
  }
452
631
  console.log("\n Note: Instinct data in ~/.claude/instincts/ was preserved.\n" +
453
632
  " To remove learned data too: rm -rf ~/.claude/instincts/\n");
633
+ return !failed;
454
634
  }
455
635
  function printUsage() {
456
636
  console.log(`
@@ -463,11 +643,11 @@ Subcommands:
463
643
 
464
644
  Options for 'install':
465
645
  --mode <mode> Installation mode:
466
- beginner — hooks + skill + commands (default)
646
+ beginner — hooks + skills + commands (default)
467
647
  expert — beginner + MCP server + session hooks
468
648
  --pack <name> Load a starter instinct pack (react, python, go, meta)
469
649
  --target <names> Comma-separated platform list (default: claude):
470
- claude — full install: hooks + skill + commands
650
+ claude — full install: hooks + skills + commands
471
651
  gemini — GEMINI.md (Gemini CLI)
472
652
  codex — AGENTS.md (Codex CLI / agents.md standard)
473
653
  cursor — .cursor/rules/continuous-improvement.mdc
@@ -534,8 +714,7 @@ if (command === "backfill") {
534
714
  process.exit(0);
535
715
  }
536
716
  if (args.includes("--uninstall")) {
537
- uninstallAll();
538
- process.exit(0);
717
+ process.exit(uninstallAll() ? 0 : 1);
539
718
  }
540
719
  // --target <names>: comma-separated platform list, default claude-only.
541
720
  // Non-claude targets receive the skill text in their platform's rules file
@@ -597,7 +776,9 @@ Research → Plan → Execute → Verify → Reflect → Learn → Iterate
597
776
  `);
598
777
  warnOnMarketplaceCollision();
599
778
  console.log("Installing to Claude Code...\n");
600
- const installed = installSkill() ? 1 : 0;
779
+ const installOutcome = installSkill();
780
+ if (installOutcome === "failed")
781
+ process.exitCode = 1;
601
782
  const modeInfo = {
602
783
  beginner: "Hooks are capturing silently. System auto-levels as you use it.",
603
784
  expert: `Full plugin active: hooks + MCP server + session hooks. ${getToolNames("expert").length} tools available.`,
@@ -651,19 +832,23 @@ if (packIndex !== -1 && rawArgs[packIndex + 1]) {
651
832
  console.error(` ✗ Unknown pack: ${packName}. Available: ${available.join(", ")}`);
652
833
  }
653
834
  }
654
- console.log(`
655
- ${installed === 1 ? "Done." : "Failed."}
656
- ${modeInfo[INSTALL_MODE]}
835
+ const installedNextSteps = `${modeInfo[INSTALL_MODE]}
657
836
 
658
837
  Next steps:
659
838
  1. Start a new Claude Code session
660
839
  2. Say: "Use the continuous-improvement framework to [your task]"
661
- 3. If a task needs persistent planning, run: /planning-with-files
662
- 4. After your first task, run: /continuous-improvement
663
- 5. Try: /discipline for quick reference, /dashboard for instinct health
840
+ 3. For one defect, run: /ship [one-line defect description]
841
+ 4. If a task needs persistent planning, run: /planning-with-files
842
+ 5. After your first task, run: /continuous-improvement
843
+ 6. Try: /discipline for quick reference, /dashboard for instinct health
664
844
  ${INSTALL_MODE === "expert" ? `\nMCP tools available (${getToolNames("expert").length}): ${getToolNames("expert").join(", ")}` : ""}
665
- Available instinct packs: npx continuous-improvement install --pack react|python|go|meta
666
- `);
845
+ Available instinct packs: npx continuous-improvement install --pack react|python|go|meta`;
846
+ const installSummary = installOutcome === "installed"
847
+ ? `Done.\n${installedNextSteps}`
848
+ : installOutcome === "preserved"
849
+ ? `Done with warning.\n${modeInfo[INSTALL_MODE]}\n\nPackage /ship was not installed because an existing unowned ship skill was preserved. Resolve that collision, then reinstall before using the package workflow.`
850
+ : "Failed.\nInstallation incomplete. Fix the errors above and rerun the installer. Do not assume hooks, skills, or commands are ready.";
851
+ console.log(`\n${installSummary}\n`);
667
852
  // Update-available nudge for the npm/CLI install path (the marketplace path is
668
853
  // covered by Claude Code's native plugin auto-update). Reads the public npm
669
854
  // registry only — no telemetry. Throttled, default-on, off via
package/bin/reconcile.mjs CHANGED
@@ -54,6 +54,7 @@ function parseArgs(args) {
54
54
  verifyPush: null,
55
55
  root: cwd(),
56
56
  };
57
+ let cwdProvided = false;
57
58
  for (let i = 0; i < args.length; i++) {
58
59
  const arg = args[i] ?? "";
59
60
  if (arg === "--json")
@@ -63,6 +64,9 @@ function parseArgs(args) {
63
64
  else if (arg === "--explain")
64
65
  options.explain = true;
65
66
  else if (arg === "--verify-push") {
67
+ if (options.verifyPush !== null) {
68
+ throw new Error("--verify-push may only be provided once");
69
+ }
66
70
  const value = args[i + 1];
67
71
  if (value === undefined || value.startsWith("--")) {
68
72
  throw new Error("--verify-push requires a branch name");
@@ -74,11 +78,15 @@ function parseArgs(args) {
74
78
  i++;
75
79
  }
76
80
  else if (arg === "--cwd") {
81
+ if (cwdProvided) {
82
+ throw new Error("--cwd may only be provided once");
83
+ }
77
84
  const value = args[i + 1];
78
- if (value === undefined || value.startsWith("--")) {
85
+ if (value === undefined || value.startsWith("--") || value.trim().length === 0) {
79
86
  throw new Error("--cwd requires a directory");
80
87
  }
81
88
  options.root = value;
89
+ cwdProvided = true;
82
90
  i++;
83
91
  }
84
92
  else if (arg === "--help" || arg === "-h") {
@@ -88,6 +96,14 @@ function parseArgs(args) {
88
96
  throw new Error(`unknown argument: ${arg}`);
89
97
  }
90
98
  }
99
+ const actions = [
100
+ options.snapshot ? "--snapshot" : null,
101
+ options.explain ? "--explain" : null,
102
+ options.verifyPush !== null ? "--verify-push" : null,
103
+ ].filter((action) => action !== null);
104
+ if (actions.length > 1) {
105
+ throw new Error(`mutually exclusive action modes: ${actions.join(", ")}`);
106
+ }
91
107
  return options;
92
108
  }
93
109
  /** Probe each in-progress marker through `--git-path`, which is worktree-correct. */
@@ -160,20 +176,35 @@ function main() {
160
176
  const status = git(["status", "--porcelain=v1"], options.root);
161
177
  const drift = git(["diff", "--name-only", "--ignore-all-space"], options.root);
162
178
  const dirty = accountDirty(status.code === 0 ? status.stdout : null, drift.code === 0 ? drift.stdout : null);
179
+ const inProgress = probeInProgress(options.root);
180
+ const baseFindings = assessGitState({
181
+ headCommit: headSha.code === 0 ? headSha.stdout.trim() : null,
182
+ head,
183
+ upstreamRef,
184
+ counts,
185
+ inProgress,
186
+ dirty,
187
+ });
163
188
  if (options.snapshot) {
164
- // Field-compatible with scripts/git-state-snapshot.sh, plus `contentDrift`
165
- // and `inProgress`, which the shell version cannot report.
189
+ // Field-compatible with scripts/git-state-snapshot.sh, plus `contentDrift`,
190
+ // `inProgress`, and a fail-closed blocker summary the shell version cannot
191
+ // report. An unborn HEAD is a git repo, but not a usable mutation baseline;
192
+ // do not emit an empty `head` field that looks like success.
166
193
  const upstreamSha = upstreamRef === null ? "none" : git(["rev-parse", "--short", "@{u}"], options.root).stdout.trim() || "none";
194
+ const shortHead = headSha.code === 0 ? git(["rev-parse", "--short", "HEAD"], options.root).stdout.trim() : "";
195
+ const blockers = baseFindings.filter((finding) => finding.severity === "blocker").map((finding) => finding.id);
167
196
  stdout.write(`${JSON.stringify({
168
- head: git(["rev-parse", "--short", "HEAD"], options.root).stdout.trim(),
197
+ head: shortHead.length > 0 ? shortHead : "unborn",
169
198
  upstream: upstreamSha,
170
199
  dirty: dirty.reported,
171
200
  root: repoRoot,
172
201
  branch: head.branch ?? "detached",
173
202
  contentDrift: dirty.contentDrift,
174
- inProgress: probeInProgress(options.root),
203
+ inProgress,
204
+ blocked: blockers.length > 0,
205
+ blockers,
175
206
  })}\n`);
176
- exit(0);
207
+ exit(blockers.length > 0 ? 1 : 0);
177
208
  return;
178
209
  }
179
210
  if (options.verifyPush !== null) {
@@ -191,14 +222,7 @@ function main() {
191
222
  exit(verdict.verdict === "landed" ? 0 : 1);
192
223
  return;
193
224
  }
194
- const inProgress = probeInProgress(options.root);
195
- const findings = assessGitState({
196
- head,
197
- upstreamRef,
198
- counts,
199
- inProgress,
200
- dirty,
201
- });
225
+ const findings = [...baseFindings];
202
226
  const stashes = git(["stash", "list"], options.root);
203
227
  const stashCount = stashes.code === 0
204
228
  ? stashes.stdout.split(/\r?\n/).filter((line) => line.trim().length > 0).length
@@ -42,6 +42,9 @@ Four boundaries where the obvious command lies:
42
42
  - **Detached HEAD** — the `--show-current` form of `git branch` prints "" and exits 0, indistinguishable from success. `symbolic-ref --quiet` exits non-zero instead. Detached blocks.
43
43
  - **Linked worktree** — `.git` is a *file* there, so a `.git/`-relative marker probe exits 2 exactly as it does on a clean tree: a real conflicted merge reads as clean. Use `rev-parse --git-path`.
44
44
  - **autocrlf** — `git status` overstates drift. Stage by explicit filename, never `git add -A`.
45
+ - **Unborn HEAD** — `git init` with no commit is a real repo with no usable baseline; `rev-parse HEAD` fails. Reported as `head: "unborn"` and blocked, never as an empty field that reads like success.
46
+
47
+ `--snapshot` shares the exit contract (`0` clear / `1` blocked / `2` not a repo) and adds `contentDrift`, `inProgress`, `blocked`, `blockers` to the shell script's envelope. It still writes the JSON on a blocker, so tolerate exit 1 when capturing a baseline under `set -e`.
45
48
 
46
49
  ## Then act, with gates
47
50
 
package/commands/ship.md CHANGED
@@ -1,57 +1,13 @@
1
1
  ---
2
2
  name: ship
3
- description: "Single-defect fast path walk one bug from ground-truth audit through a TDD fix, full verification, a single-concern commit, and an open PR, then stop. Never auto-merges, never deploys. For multi-PR rollouts use /release-train instead."
3
+ description: "Compatibility entry point for the native ship skill. Passes one defect request through unchanged."
4
+ argument-hint: "[one-line defect description]"
4
5
  ---
5
6
 
6
7
  # /ship
7
8
 
8
- The one-defect fast path. `/release-train` is for stacked multi-PR rollouts and `/proceed-with-the-recommendation` walks an arbitrary recommendation list; `/ship` is the common case: fix one defect, open one PR, hand it back for review.
9
+ Invoke the native `ship` skill with this complete defect request:
9
10
 
10
- Pure routing over existing skills. It adds no new orchestration logic and it does NOT bypass branch protection, force-push, auto-merge, or deploy.
11
+ $ARGUMENTS
11
12
 
12
- ## Usage
13
-
14
- ```
15
- /ship <one-line description of the defect>
16
- ```
17
-
18
- If the description is ambiguous or names more than one concern, `/ship` halts and asks you to narrow it — one defect per run.
19
-
20
- ## Behavior
21
-
22
- In order, for the single defect:
23
-
24
- 1. **Ground truth** — `reconcile` (or its inline fallback): confirm the working tree is clean and on a feature branch cut from an up-to-date `origin/<base>`. If on a protected branch or a stale base, halt and ask.
25
- 2. **Reproduce (RED)** — `tdd-workflow`: write a failing test that reproduces the defect; watch it fail. Pre-test implementation code is deleted, not kept.
26
- 3. **Fix (GREEN)** — write the minimal change that makes the test pass; watch it pass. One concern only.
27
- 4. **Verify** — `verification-loop`: run the project's verify ladder (build, types, tests). Build-green is evidence of mechanism, not of the fix — confirm the defect itself no longer reproduces.
28
- 5. **Commit** — one commit, one concern, staged by explicit filename (never `git add -A`). Use a Windows-safe commit message: a single-line `-m` (repeat `-m` for paragraphs) or `git commit -F <tempfile>` — no multi-line here-docs/here-strings.
29
- 6. **Open PR** — `commit-commands:commit-push-pr` (or `gh pr create`): push the branch and open a single-concern PR that cites the plan or issue. **Stop here.** The merge is yours.
30
- 7. **Deploy receipt (advisory)** — after you merge, `deploy-receipt` verifies the deployed SHA matches the merge SHA. Advisory only; `/ship` does not deploy.
31
-
32
- ## Hard stops (halt and ask, never improvise)
33
-
34
- - Ambiguous or multi-concern defect description.
35
- - Working tree not clean, or branch is protected / cut from a stale base.
36
- - Any verification step fails with a non-obvious fix.
37
- - The fix would touch more than 15 non-generated files (that is no longer one concern — split it, or use `/release-train`).
38
- - Push would target a protected branch.
39
-
40
- ## Anti-patterns this command refuses
41
-
42
- - **Auto-merge.** Never merges the PR it opens, even when CI is green.
43
- - **Deploy.** Never runs a deploy; `deploy-receipt` only verifies after you merge.
44
- - **Bypass.** No `--admin`, `--force`, `--no-verify`.
45
- - **Bundled concerns.** Will not fold an unrelated fix into the same commit; logs it as a deferred follow-up instead.
46
-
47
- ## Composition
48
-
49
- Routes through, in order: `reconcile` → `tdd-workflow` → `verification-loop` → `commit-commands:commit-push-pr` → `deploy-receipt`. Each step falls back to its inline behavior when the preferred skill is not installed.
50
-
51
- ## Example
52
-
53
- ```
54
- /ship registration form accepts a negative deposit amount
55
- ```
56
-
57
- Reconciles git state, writes a failing test asserting deposits must be positive, implements the guard, runs the verify ladder, commits one concern with a single-line message, opens the PR, and stops for your review.
13
+ Follow that skill exactly. Do not reconstruct or duplicate its Git, worktree, TDD, return, cleanup, merge, or deploy rules here. If the native skill is unavailable, stop and tell the operator to update or reinstall `continuous-improvement`.