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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +19 -0
- package/README.md +4 -4
- package/bin/install.mjs +204 -19
- package/bin/reconcile.mjs +38 -14
- package/commands/reconcile.md +3 -0
- package/commands/ship.md +5 -49
- package/lib/git-state.mjs +24 -5
- package/lib/plugin-metadata.mjs +2 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/plugins/beginner.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/bin/reconcile.mjs +38 -14
- package/plugins/continuous-improvement/commands/reconcile.md +3 -0
- package/plugins/continuous-improvement/commands/ship.md +5 -49
- package/plugins/continuous-improvement/lib/git-state.mjs +24 -5
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +2 -2
- package/plugins/continuous-improvement/skills/README.md +1 -0
- package/plugins/continuous-improvement/skills/reconcile/SKILL.md +6 -2
- package/plugins/continuous-improvement/skills/ship/SKILL.md +139 -0
- package/plugins/expert.json +1 -1
- package/skills/README.md +1 -0
- package/skills/reconcile.md +6 -2
- package/skills/ship.md +139 -0
|
@@ -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
|
|
11
|
-
"version": "3.
|
|
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 (
|
|
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
|
|
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 **
|
|
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
|
|
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
|
|
286
|
+
return shipOutcome;
|
|
134
287
|
}
|
|
135
288
|
catch (error) {
|
|
136
289
|
console.error(` ✗ Install failed: ${getErrorMessage(error)}`);
|
|
137
|
-
return
|
|
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
|
|
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
|
|
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 +
|
|
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 +
|
|
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
|
|
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
|
-
|
|
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.
|
|
662
|
-
4.
|
|
663
|
-
5.
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
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
|
|
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
|
package/commands/reconcile.md
CHANGED
|
@@ -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: "
|
|
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
|
-
|
|
9
|
+
Invoke the native `ship` skill with this complete defect request:
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
$ARGUMENTS
|
|
11
12
|
|
|
12
|
-
|
|
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`.
|