continuous-improvement 3.24.0 → 3.25.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.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "continuous-improvement",
10
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.24.0",
11
+ "version": "3.25.0",
12
12
  "source": "./plugins/continuous-improvement",
13
13
  "author": {
14
14
  "name": "naimkatiman"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this skill are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.25.0] — 2026-09-07
8
+
9
+ ### Added
10
+
11
+ - **Tagged releases now deploy continuous-improvement.dev** — after npm publish, `release.yml` deploys `docs/landing` to the Cloudflare Pages project and re-reads the live domain so a missed deploy fails the release instead of turning `landing-drift.yml` red the next morning. Gated on `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`: without them the step warns and skips, so a fork still gets a complete publish. Last in the job, so a Cloudflare outage cannot block npm. (#307)
12
+ - **`verify:test-count`** — every surface that states the suite size must agree, and CI tees `npm test` then asserts the claimed count against the real run. The filesystem cannot derive the number (generated cases in loops), so the docs stay the source of truth and the live run is the check. (#306)
13
+ - **`verify:invariant-count`** — derives the invariant list from the `verify:all` script and fails if `CLAUDE.md`, `AGENTS.md`, or `docs/RELEASING.md` claim a different count or a different ordered name list. (#312)
14
+
15
+ ### Changed
16
+
17
+ - **oh-my-claudecode snapshot 4.13.6 → 5.3.0** — `ultrawork` is gone upstream; the autonomous-run row now routes to `ultragoal`. The never-existent `oh-my-claudecode:retrospective` target is dropped rather than substituted. (#308, #309)
18
+ - **obra/superpowers snapshot 5.1.0 → 6.3.0**. (#304)
19
+ - **`verify:routing-targets` checks the vendored snapshot**, not just the declaration in `optional-companions.json`. A declared `oh-my-claudecode:` / `superpowers:` / `agent-skills:` / `ruflo-swarm:` target must exist at `third-party/<snapshot>/skills/<name>/`. (#311)
20
+
21
+ ### Fixed
22
+
23
+ - **`refresh-third-party.mjs` keeps `OUR_NOTES.md` and `.fork-only-skills.txt`** across the wipe, and reports how many of ours were preserved. (#310)
24
+ - **Refresh aborts before the wipe** if upstream removed a skill our flat source still routes to, naming the skills and files. Bypass is `--allow-stale-refs`, not `--force`. (#313)
25
+ - **Brainstorming dispatcher wording** now describes the three-path router, not just the architectural path. (#305)
26
+
7
27
  ## [3.24.0] — 2026-09-06
8
28
 
9
29
  ### Added
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Invariant Count Check
4
+ *
5
+ * `CLAUDE.md` and `AGENTS.md` tell an agent, as a hard instruction, to run
6
+ * `npm run verify:all` and state how many invariants that is plus the full
7
+ * ordered list. `docs/RELEASING.md` states the count in its release checklist.
8
+ * All three are hand-maintained prose, and all three drifted: on 2026-09-06
9
+ * CLAUDE.md and RELEASING.md said 16 against an actual 17, and AGENTS.md — the
10
+ * file Codex reads as its contract — said **12** and listed an incomplete set,
11
+ * having missed skill-count-prose, command-count, landing-version and
12
+ * reconcile-parity entirely.
13
+ *
14
+ * That is worse than a stale number. An agent told "12 invariants" that sees 17
15
+ * OK lines has no way to know whether it ran too much or the doc is wrong, and
16
+ * an agent reading the list as authoritative will not notice an invariant it was
17
+ * never told about.
18
+ *
19
+ * Source of truth: the `verify:all` script in package.json. Everything before
20
+ * the first non-`verify:` step is a content invariant; the rest (today just
21
+ * `typecheck`) is the trailing tail the docs name after the colon. Derived, not
22
+ * hardcoded, so adding an invariant to the chain is the only edit needed to make
23
+ * this check demand the docs follow.
24
+ *
25
+ * Two claim shapes are recognised:
26
+ * long — "(17 content invariants + typecheck: a, b, …, typecheck)" count + ordered names
27
+ * short — "(17 invariants + typecheck)" count only
28
+ *
29
+ * Fail-closed: a scanned file with no claim at all is a violation, not a pass.
30
+ *
31
+ * Usage:
32
+ * node bin/check-invariant-count.mjs # Check the current repo
33
+ * node bin/check-invariant-count.mjs <repo-root> # Check a specific repo root
34
+ *
35
+ * Exit codes:
36
+ * 0 — every claim matches the verify:all chain
37
+ * 1 — a count is stale, a name list is wrong or out of order, or a file states none
38
+ */
39
+ import { readFileSync } from "node:fs";
40
+ import { join } from "node:path";
41
+ import { argv, cwd, exit } from "node:process";
42
+ /** Files that carry a verify:all claim. Explicit, so dropping one is deliberate. */
43
+ export const CLAIM_FILES = ["CLAUDE.md", "AGENTS.md", join("docs", "RELEASING.md")];
44
+ const LONG_RE = /\((\d+) content invariants \+ typecheck:\s*([^)]+)\)/g;
45
+ const SHORT_RE = /\((\d+) invariants \+ typecheck\)/g;
46
+ /** Split the verify:all chain into `verify:*` invariants and the trailing steps. */
47
+ export function parseVerifyAllChain(chainScript) {
48
+ const steps = [...chainScript.matchAll(/npm run ([A-Za-z0-9:_-]+)/g)].map((m) => m[1]);
49
+ const invariants = [];
50
+ const trailing = [];
51
+ for (const step of steps) {
52
+ if (step.startsWith("verify:"))
53
+ invariants.push(step.slice("verify:".length));
54
+ else
55
+ trailing.push(step);
56
+ }
57
+ return { invariants, trailing };
58
+ }
59
+ export function parseClaims(content) {
60
+ const claims = [];
61
+ for (const m of content.matchAll(LONG_RE)) {
62
+ claims.push({
63
+ count: Number(m[1]),
64
+ names: m[2].split(",").map((s) => s.trim()).filter(Boolean),
65
+ });
66
+ }
67
+ for (const m of content.matchAll(SHORT_RE)) {
68
+ claims.push({ count: Number(m[1]), names: null });
69
+ }
70
+ return claims;
71
+ }
72
+ export function findViolations(chain, claimsByFile) {
73
+ const violations = [];
74
+ const expectedCount = chain.invariants.length;
75
+ const expectedNames = [...chain.invariants, ...chain.trailing];
76
+ for (const [file, claims] of Object.entries(claimsByFile)) {
77
+ if (claims.length === 0) {
78
+ violations.push(`${file}: states no "verify:all" invariant-count claim. Expected "(${expectedCount} invariants + typecheck)" or the long form with the full list.`);
79
+ continue;
80
+ }
81
+ for (const claim of claims) {
82
+ if (claim.count !== expectedCount) {
83
+ violations.push(`${file}: claims ${claim.count} invariants but verify:all runs ${expectedCount}.`);
84
+ }
85
+ if (claim.names === null)
86
+ continue;
87
+ if (claim.names.length !== expectedNames.length) {
88
+ const missing = expectedNames.filter((n) => !claim.names.includes(n));
89
+ const extra = claim.names.filter((n) => !expectedNames.includes(n));
90
+ violations.push(`${file}: the listed steps do not match verify:all.` +
91
+ (missing.length ? ` Missing: ${missing.join(", ")}.` : "") +
92
+ (extra.length ? ` Not in the chain: ${extra.join(", ")}.` : ""));
93
+ }
94
+ else if (claim.names.some((n, i) => n !== expectedNames[i])) {
95
+ violations.push(`${file}: the listed steps are out of order. Expected the chain order: ${expectedNames.join(", ")}.`);
96
+ }
97
+ }
98
+ }
99
+ return violations;
100
+ }
101
+ export function checkInvariantCount(repoRoot) {
102
+ const pkgRaw = readFileSync(join(repoRoot, "package.json"), "utf8");
103
+ const pkg = JSON.parse(pkgRaw);
104
+ const chainScript = pkg.scripts?.["verify:all"];
105
+ if (typeof chainScript !== "string" || chainScript.length === 0) {
106
+ return {
107
+ chain: { invariants: [], trailing: [] },
108
+ violations: [`package.json has no "verify:all" script, so no claim can be checked.`],
109
+ };
110
+ }
111
+ const chain = parseVerifyAllChain(chainScript);
112
+ const claimsByFile = {};
113
+ for (const rel of CLAIM_FILES) {
114
+ let content;
115
+ try {
116
+ content = readFileSync(join(repoRoot, rel), "utf8");
117
+ }
118
+ catch {
119
+ claimsByFile[rel] = [];
120
+ continue;
121
+ }
122
+ claimsByFile[rel] = parseClaims(content);
123
+ }
124
+ return { chain, violations: findViolations(chain, claimsByFile) };
125
+ }
126
+ function main() {
127
+ const repoRoot = argv[2] ?? cwd();
128
+ const { chain, violations } = checkInvariantCount(repoRoot);
129
+ if (violations.length === 0) {
130
+ console.log(`OK invariant-count: all ${CLAIM_FILES.length} doc surface(s) state ${chain.invariants.length} invariants + ${chain.trailing.join(", ")}, matching the verify:all chain.`);
131
+ exit(0);
132
+ }
133
+ console.error(`FAIL invariant-count: ${violations.length} stale claim(s) about verify:all.\n`);
134
+ for (const v of violations)
135
+ console.error(` ${v}`);
136
+ console.error(`\nFix: verify:all currently runs ${chain.invariants.length} invariants — ` +
137
+ `${[...chain.invariants, ...chain.trailing].join(", ")}. ` +
138
+ `Update the claim in each file above to match. The chain in package.json is the source of truth.`);
139
+ exit(1);
140
+ }
141
+ const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].replace(/\\/g, "/"));
142
+ if (invokedDirectly || argv[1]?.endsWith("check-invariant-count.mjs")) {
143
+ main();
144
+ }
@@ -8,6 +8,13 @@
8
8
  * (a) ships bundled at plugins/continuous-improvement/skills/<name>/SKILL.md, or
9
9
  * (b) is declared in the root-level optional-companions.json file.
10
10
  *
11
+ * And, for the prefixes we vendor (oh-my-claudecode, superpowers, agent-skills,
12
+ * ruflo-swarm), that every declared target actually EXISTS in its snapshot under
13
+ * third-party/<snapshot>/skills/<name>/. Declaring a target only asserted intent;
14
+ * nothing checked the destination. oh-my-claudecode:ultrawork was declared after
15
+ * upstream deleted it, and oh-my-claudecode:retrospective was declared for months
16
+ * having never existed upstream at all — both with this check green.
17
+ *
11
18
  * Catches: a routing-table row that names a skill the bundle does not ship and
12
19
  * the maintainer has not declared as an optional companion. Without this gate,
13
20
  * such drift only surfaces at runtime when the orchestrator routes to a target
@@ -20,15 +27,60 @@
20
27
  *
21
28
  * Exit codes:
22
29
  * 0 — every routing target is accounted for (bundled or optional-declared)
23
- * 1 — at least one routing target is unaccounted for
30
+ * 1 — at least one routing target is unaccounted for, or a declared vendored
31
+ * target has no directory in its snapshot
24
32
  */
25
- import { readdirSync, readFileSync, statSync } from "node:fs";
33
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
26
34
  import { join } from "node:path";
27
35
  import { argv, cwd, exit } from "node:process";
28
36
  const ORCHESTRATOR_SKILL_PATH = "skills/proceed-with-the-recommendation.md";
29
37
  const OPTIONAL_COMPANIONS_PATH = "optional-companions.json";
30
38
  const PLUGIN_SKILLS_DIR = "plugins/continuous-improvement/skills";
31
39
  const SECTION_HEADER = "### Routing Table (with Inline Fallbacks)";
40
+ /**
41
+ * Routing-target prefix -> vendored snapshot directory under third-party/.
42
+ *
43
+ * Declaring a target in optional-companions.json only asserted that we MEANT to
44
+ * route somewhere; nothing checked the destination existed. So when
45
+ * oh-my-claudecode 5.x deleted `ultrawork`, this check stayed green while the
46
+ * routing table named a skill upstream no longer ships (#308, fixed in #309) —
47
+ * and `oh-my-claudecode:retrospective` had been declared for months without ever
48
+ * existing upstream at all. For prefixes we vendor, the snapshot is ground truth.
49
+ *
50
+ * Prefixes NOT listed here (host built-ins, `frontend-design:`, `commit-commands:`)
51
+ * are deliberately unchecked: we have no local copy to check them against, and
52
+ * guessing would make this fail closed on things it cannot see.
53
+ */
54
+ export const VENDORED_PREFIXES = {
55
+ "oh-my-claudecode": "oh-my-claudecode",
56
+ superpowers: "superpowers",
57
+ "agent-skills": "addy-agent-skills",
58
+ "ruflo-swarm": "ruflo-swarm",
59
+ };
60
+ /** Repo-relative skill path a vendored target must resolve to, or null if unvendored. */
61
+ export function resolveVendoredSkillPath(target) {
62
+ const idx = target.indexOf(":");
63
+ if (idx < 0)
64
+ return null;
65
+ const dir = VENDORED_PREFIXES[target.slice(0, idx)];
66
+ if (!dir)
67
+ return null;
68
+ // Everything after the FIRST colon is the skill name, colons included.
69
+ return `third-party/${dir}/skills/${target.slice(idx + 1)}`;
70
+ }
71
+ /** Vendored targets with no directory in their snapshot. */
72
+ export function findMissingVendoredTargets(repoRoot, targets) {
73
+ const missing = [];
74
+ for (const target of targets) {
75
+ const rel = resolveVendoredSkillPath(target);
76
+ if (rel === null)
77
+ continue;
78
+ const abs = join(repoRoot, rel);
79
+ if (!existsSync(abs))
80
+ missing.push({ target, expectedPath: abs });
81
+ }
82
+ return missing;
83
+ }
32
84
  export function discoverBundledSkills(repoRoot) {
33
85
  const dir = join(repoRoot, PLUGIN_SKILLS_DIR);
34
86
  let entries;
@@ -119,19 +171,37 @@ export function checkRoutingTargets(repoRoot) {
119
171
  continue;
120
172
  drifts.push(t);
121
173
  }
174
+ // Declared is not the same as existing: for prefixes we vendor, the snapshot
175
+ // is ground truth. Checks the declared companion set, so a target that is
176
+ // declared but never referenced is still caught.
177
+ const missingVendored = findMissingVendoredTargets(repoRoot, optional);
122
178
  return {
123
179
  targets,
124
180
  drifts,
125
181
  bundledCount: bundled.size,
126
182
  optionalCount: optional.size,
183
+ missingVendored,
127
184
  };
128
185
  }
129
186
  function main() {
130
187
  const repoRoot = argv[2] ?? cwd();
131
- const { targets, drifts, bundledCount, optionalCount } = checkRoutingTargets(repoRoot);
188
+ const { targets, drifts, bundledCount, optionalCount, missingVendored } = checkRoutingTargets(repoRoot);
189
+ if (missingVendored.length > 0) {
190
+ console.error(`FAIL routing-targets: ${missingVendored.length} declared target(s) do not exist in the vendored snapshot.\n`);
191
+ for (const m of missingVendored) {
192
+ console.error(` - "${m.target}"`);
193
+ console.error(` Declared in ${OPTIONAL_COMPANIONS_PATH}, but no directory at:`);
194
+ console.error(` ${m.expectedPath}`);
195
+ }
196
+ console.error(`\nEither upstream removed or renamed the skill (retarget the routing rows and this ` +
197
+ `declaration), or the snapshot is stale (refresh it with ` +
198
+ `'node bin/refresh-third-party.mjs <name>'). Declaring a target does not make it exist.`);
199
+ exit(1);
200
+ }
132
201
  if (drifts.length === 0) {
133
202
  console.log(`OK routing-targets: all ${targets.length} routing target(s) accounted for ` +
134
- `(${bundledCount} bundled skill(s), ${optionalCount} optional companion(s) declared).`);
203
+ `(${bundledCount} bundled skill(s), ${optionalCount} optional companion(s) declared, ` +
204
+ `every vendored target present in its snapshot).`);
135
205
  exit(0);
136
206
  }
137
207
  console.error(`FAIL routing-targets: ${drifts.length} unaccounted target(s) in ${ORCHESTRATOR_SKILL_PATH}.\n`);
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test Count Check
4
+ *
5
+ * The landing page states the size of the test suite in two places: the spec
6
+ * strip ("1,271 / Tests in the suite") and the verification-loop panel
7
+ * ("tests PASS 1271/1271"). Both are hand-maintained and both drifted
8
+ * twice in a single day on 2026-09-06 — 1,137 to 1,249 to 1,271 — because every
9
+ * PR that adds a test moves the real number and nothing guarded the claim. The
10
+ * skill and command counts are guarded (check-skill-count, check-command-count);
11
+ * this closes the same hole for tests.
12
+ *
13
+ * Unlike skills and commands, the real number cannot be derived from the
14
+ * filesystem: 45 test files generate cases in loops, so counting `it(` / `test(`
15
+ * declarations reports 1,066 against an actual 1,271. Node's runner has no
16
+ * collect-without-running mode either — filtering by name pattern reports the
17
+ * file count, not the test count. The only honest source is an actual run.
18
+ *
19
+ * So the check runs in two layers, and the docs are the source of truth:
20
+ *
21
+ * 1. Locally, inside `verify:all`, it asserts that every surface states the
22
+ * SAME number. Fast, no suite run, and it catches the two-surfaces-disagree
23
+ * case that a single-number edit produces.
24
+ * 2. In CI, after `npm test` has already run, `--actual-from <file>` parses
25
+ * the runner's "tests N" summary out of the captured output and asserts the
26
+ * claimed number equals reality. No second suite run.
27
+ *
28
+ * A claim that drifts from reality fails CI; two claims that drift apart fail
29
+ * locally. Fail-closed: a missing surface is a violation, not a silent pass.
30
+ *
31
+ * Usage:
32
+ * node bin/check-test-count.mjs # Claims agree with each other
33
+ * node bin/check-test-count.mjs --actual-from out.txt # ...and with an actual run
34
+ * node bin/check-test-count.mjs <repo-root> [--actual-from out.txt]
35
+ *
36
+ * Exit codes:
37
+ * 0 — every stated test count agrees (and matches the run, when supplied)
38
+ * 1 — a surface is missing, surfaces disagree, or a claim is stale
39
+ */
40
+ import { readFileSync } from "node:fs";
41
+ import { join } from "node:path";
42
+ import { argv, cwd, exit } from "node:process";
43
+ const LANDING = join("docs", "landing", "index.html");
44
+ /** The spec strip cell: <span class="v">1,271</span><span class="k">Tests in the suite</span> */
45
+ const STRIP_RE = /<span\b[^>]*class=["'][^"']*\bv\b[^"']*["'][^>]*>\s*([\d,]+)\s*<\/span>\s*<span\b[^>]*class=["'][^"']*\bk\b[^"']*["'][^>]*>\s*Tests in the suite\s*<\/span>/gi;
46
+ /** The verification-loop panel line: tests PASS 1271/1271 */
47
+ const LADDER_RE = /^tests\s+PASS\s+([\d,]+)\/([\d,]+)\s*$/gim;
48
+ /** The node test runner summary: "ℹ tests 1271" */
49
+ const ACTUAL_RE = /^\W*tests\s+(\d+)\s*$/im;
50
+ const toNumber = (raw) => Number(raw.replace(/,/g, ""));
51
+ export function extractClaims(html) {
52
+ const strip = [...html.matchAll(STRIP_RE)].map((m) => toNumber(m[1]));
53
+ const ladder = [...html.matchAll(LADDER_RE)].flatMap((m) => [toNumber(m[1]), toNumber(m[2])]);
54
+ return { strip, ladder };
55
+ }
56
+ export function parseActualCount(runOutput) {
57
+ const m = ACTUAL_RE.exec(runOutput);
58
+ return m ? Number(m[1]) : null;
59
+ }
60
+ export function findViolations(claims, actual) {
61
+ const violations = [];
62
+ if (claims.strip.length === 0) {
63
+ violations.push(`the spec strip states no test count (expected a <span class="v">N</span> cell labelled "Tests in the suite").`);
64
+ }
65
+ if (claims.ladder.length === 0) {
66
+ violations.push(`the verification-loop ladder states no test count (expected a "tests PASS N/N" line).`);
67
+ }
68
+ if (violations.length > 0)
69
+ return violations;
70
+ const stated = [...claims.strip, ...claims.ladder];
71
+ const distinct = [...new Set(stated)].sort((a, b) => a - b);
72
+ if (distinct.length > 1) {
73
+ // Surfaces disagree with each other. Comparing a contested number to the run
74
+ // would emit a second, derivative violation; fix the disagreement first.
75
+ return [`the stated test counts disagree with each other: ${distinct.join(" vs ")}.`];
76
+ }
77
+ const claimed = distinct[0];
78
+ if (actual !== null && actual !== claimed) {
79
+ violations.push(`every surface states ${claimed} tests but the suite actually ran ${actual}.`);
80
+ }
81
+ return violations;
82
+ }
83
+ function main() {
84
+ const args = argv.slice(2);
85
+ const actualIdx = args.indexOf("--actual-from");
86
+ const actualPath = actualIdx === -1 ? null : args[actualIdx + 1];
87
+ if (actualIdx !== -1 && !actualPath) {
88
+ console.error("FAIL test-count: --actual-from needs a path to a captured test run.");
89
+ exit(1);
90
+ }
91
+ const repoRoot = args.find((a) => !a.startsWith("--") && a !== actualPath) ?? cwd();
92
+ let html;
93
+ try {
94
+ html = readFileSync(join(repoRoot, LANDING), "utf8");
95
+ }
96
+ catch {
97
+ console.error(`FAIL test-count: cannot read ${LANDING} at ${repoRoot}.`);
98
+ exit(1);
99
+ }
100
+ let actual = null;
101
+ if (actualPath) {
102
+ let runOutput;
103
+ try {
104
+ runOutput = readFileSync(actualPath, "utf8");
105
+ }
106
+ catch {
107
+ console.error(`FAIL test-count: cannot read the captured test run at ${actualPath}.`);
108
+ exit(1);
109
+ }
110
+ actual = parseActualCount(runOutput);
111
+ if (actual === null) {
112
+ console.error(`FAIL test-count: ${actualPath} has no "tests N" summary line, so the claim cannot be checked against a real run.`);
113
+ exit(1);
114
+ }
115
+ }
116
+ const claims = extractClaims(html);
117
+ const violations = findViolations(claims, actual);
118
+ if (violations.length === 0) {
119
+ const claimed = [...claims.strip, ...claims.ladder][0];
120
+ const against = actual === null ? "each other" : `each other and the actual run`;
121
+ console.log(`OK test-count: all ${claims.strip.length + claims.ladder.length} claim(s) state ${claimed} tests, matching ${against}.`);
122
+ exit(0);
123
+ }
124
+ console.error(`FAIL test-count: ${violations.length} issue(s) with the stated test count.`);
125
+ console.error("");
126
+ for (const v of violations)
127
+ console.error(` ${v}`);
128
+ console.error("");
129
+ console.error(`Fix: run the suite, take the "tests N" total, and update both surfaces in ${LANDING} (the spec strip and the verification-loop panel).`);
130
+ exit(1);
131
+ }
132
+ const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].replace(/\\/g, "/"));
133
+ if (invokedDirectly || argv[1]?.endsWith("check-test-count.mjs")) {
134
+ main();
135
+ }
@@ -22,7 +22,10 @@
22
22
  * wipe the local path, recreate it, copy the selective surface
23
23
  * verbatim, strip every CLAUDE.md inside the snapshot, print a diff
24
24
  * stat. Aborts if the local snapshot path has uncommitted changes
25
- * unless --force is passed.
25
+ * unless --force is passed, and aborts BEFORE the wipe if upstream
26
+ * removed a skill this repo still routes to, unless --allow-stale-refs
27
+ * is passed. The two flags are separate on purpose: a dirty-tree
28
+ * refresh must not silently disable the stale-reference gate.
26
29
  *
27
30
  * node bin/refresh-third-party.mjs --all
28
31
  * node bin/refresh-third-party.mjs --all --check
@@ -38,8 +41,9 @@
38
41
  */
39
42
  import { spawnSync } from "node:child_process";
40
43
  import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
44
+ import { readdirSync, readFileSync, statSync } from "node:fs";
41
45
  import { tmpdir } from "node:os";
42
- import { dirname, join, resolve } from "node:path";
46
+ import { dirname, join, relative, resolve } from "node:path";
43
47
  import { argv, exit, stderr, stdout } from "node:process";
44
48
  import { fileURLToPath } from "node:url";
45
49
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
@@ -56,6 +60,7 @@ const SNAPSHOTS = [
56
60
  manifestHeading: "oh-my-claudecode",
57
61
  upstream: "https://github.com/Yeachan-Heo/oh-my-claudecode.git",
58
62
  localPath: "third-party/oh-my-claudecode",
63
+ routingPrefix: "oh-my-claudecode",
59
64
  selectiveDirs: [
60
65
  "agents",
61
66
  "skills",
@@ -94,6 +99,7 @@ const SNAPSHOTS = [
94
99
  manifestHeading: "obra/superpowers",
95
100
  upstream: "https://github.com/obra/superpowers.git",
96
101
  localPath: "third-party/superpowers",
102
+ routingPrefix: "superpowers",
97
103
  selectiveDirs: ["skills", "hooks", "docs", "assets", ".claude-plugin"],
98
104
  selectiveFiles: [
99
105
  "LICENSE",
@@ -116,7 +122,7 @@ function usage() {
116
122
  "Usage:",
117
123
  " node bin/refresh-third-party.mjs --list",
118
124
  " node bin/refresh-third-party.mjs <name> --check",
119
- " node bin/refresh-third-party.mjs <name> [--force]",
125
+ " node bin/refresh-third-party.mjs <name> [--force] [--allow-stale-refs]",
120
126
  " node bin/refresh-third-party.mjs --all [--check] [--force]",
121
127
  " node bin/refresh-third-party.mjs --help",
122
128
  "",
@@ -198,6 +204,7 @@ function localPathDirty(relPath) {
198
204
  }
199
205
  async function deleteClaudeMdRecursive(root) {
200
206
  // Node-only equivalent of `find <root> -name CLAUDE.md -type f -delete`.
207
+ // (helpers for our own in-snapshot annotations are defined below)
201
208
  const { readdir } = await import("node:fs/promises");
202
209
  const stack = [root];
203
210
  let deleted = 0;
@@ -239,7 +246,130 @@ async function checkOne(snapshot) {
239
246
  await rm(dir, { recursive: true, force: true });
240
247
  }
241
248
  }
242
- async function refreshOne(snapshot, { force }) {
249
+ /**
250
+ * Flat-source files and directories that can name a companion skill. Deliberately
251
+ * excludes `third-party/` — scanning the snapshot being replaced would match the
252
+ * very copy we are about to overwrite and report every removal as still-referenced.
253
+ * The `plugins/` tree is excluded too: it is a build mirror of these files.
254
+ */
255
+ export const REFERENCE_SOURCES = [
256
+ "optional-companions.json",
257
+ "skills",
258
+ "commands",
259
+ "scripts",
260
+ join("src", "hooks"),
261
+ ];
262
+ /** Skill directory names inside a snapshot, sorted. Empty if there is no skills/ dir. */
263
+ export function listSkillDirs(snapshotRoot) {
264
+ try {
265
+ return readdirSync(join(snapshotRoot, "skills"), { withFileTypes: true })
266
+ .filter((e) => e.isDirectory())
267
+ .map((e) => e.name)
268
+ .sort();
269
+ }
270
+ catch {
271
+ return [];
272
+ }
273
+ }
274
+ /** Skills the old snapshot had that the incoming one does not. */
275
+ export function findRemovedSkills(oldSkills, newSkills) {
276
+ const incoming = new Set(newSkills);
277
+ return oldSkills.filter((s) => !incoming.has(s));
278
+ }
279
+ function walkFiles(abs, out) {
280
+ let entries;
281
+ try {
282
+ entries = readdirSync(abs, { withFileTypes: true });
283
+ }
284
+ catch {
285
+ return;
286
+ }
287
+ for (const e of entries) {
288
+ const full = join(abs, e.name);
289
+ if (e.isDirectory())
290
+ walkFiles(full, out);
291
+ else
292
+ out.push(full);
293
+ }
294
+ }
295
+ /**
296
+ * Removed skills that our own source still routes to, with the files naming them.
297
+ *
298
+ * This is the check that turns a refresh into a loud failure instead of a silent
299
+ * one: when OMC 5.x deleted `ultrawork`, the snapshot became honest while our
300
+ * routing table kept pointing at it, and every gate stayed green. Matching is on
301
+ * the prefixed `<plugin>:<skill>` form with a boundary, so a bare English word
302
+ * ("release", "review") in prose is not a false positive and `omc:ultrawork-plus`
303
+ * does not match `omc:ultrawork`.
304
+ */
305
+ export function findStaleReferences(repoRoot, routingPrefix, removedSkills) {
306
+ if (removedSkills.length === 0)
307
+ return [];
308
+ const files = [];
309
+ for (const rel of REFERENCE_SOURCES) {
310
+ const abs = join(repoRoot, rel);
311
+ let isDir = false;
312
+ try {
313
+ isDir = statSync(abs).isDirectory();
314
+ }
315
+ catch {
316
+ continue;
317
+ }
318
+ if (isDir)
319
+ walkFiles(abs, files);
320
+ else
321
+ files.push(abs);
322
+ }
323
+ const stale = [];
324
+ for (const skill of removedSkills) {
325
+ const re = new RegExp(`${escapeRegExp(routingPrefix)}:${escapeRegExp(skill)}(?![A-Za-z0-9_-])`);
326
+ const hits = [];
327
+ for (const file of files) {
328
+ let text;
329
+ try {
330
+ text = readFileSync(file, "utf8");
331
+ }
332
+ catch {
333
+ continue;
334
+ }
335
+ if (re.test(text))
336
+ hits.push(relative(repoRoot, file));
337
+ }
338
+ if (hits.length > 0)
339
+ stale.push({ skill, files: hits.sort() });
340
+ }
341
+ return stale;
342
+ }
343
+ function escapeRegExp(s) {
344
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
345
+ }
346
+ /**
347
+ * Files inside a snapshot directory that we author, not upstream. The refresh
348
+ * wipes the directory wholesale, so these have to be carried across it by hand.
349
+ * `OUR_NOTES.md` is the drift radar the vendoring contract requires;
350
+ * `.fork-only-skills.txt` is the allowlist the Skills Drift Check subtracts.
351
+ */
352
+ export const OUR_FILES = ["OUR_NOTES.md", ".fork-only-skills.txt"];
353
+ /** Read whichever of OUR_FILES exist under `localAbs`. Absent files are skipped. */
354
+ export async function readOurFiles(localAbs) {
355
+ const saved = new Map();
356
+ for (const name of OUR_FILES) {
357
+ try {
358
+ saved.set(name, await readFile(join(localAbs, name)));
359
+ }
360
+ catch {
361
+ // Not every snapshot carries every one of these; absence is normal.
362
+ }
363
+ }
364
+ return saved;
365
+ }
366
+ /** Write the captured files back under `localAbs`. Returns how many were restored. */
367
+ export async function restoreOurFiles(localAbs, saved) {
368
+ for (const [name, body] of saved)
369
+ await writeFile(join(localAbs, name), body);
370
+ return saved.size;
371
+ }
372
+ async function refreshOne(snapshot, { force, allowStaleRefs }) {
243
373
  const pinned = await readPinnedSha(snapshot);
244
374
  const localAbs = join(REPO_ROOT, snapshot.localPath);
245
375
  if (!force && (await pathExists(localAbs))) {
@@ -254,6 +384,31 @@ async function refreshOne(snapshot, { force }) {
254
384
  throw new Error(`[${snapshot.name}] upstream HEAD ${headSha} != pinned ${pinned}; ` +
255
385
  `bump the pinned SHA in third-party/MANIFEST.md first`);
256
386
  }
387
+ // Refuse to land a snapshot that would make our own routing table lie.
388
+ // When OMC 5.x deleted `ultrawork`, refreshing made the snapshot honest while
389
+ // optional-companions.json, two skills/superpowers.md tables and the
390
+ // companion-preference OVERRIDES map kept naming it — and every gate stayed
391
+ // green, because nothing compared the two. Checked BEFORE the wipe, so a
392
+ // failure leaves the snapshot untouched and the operator retargets first.
393
+ const removedSkills = findRemovedSkills(listSkillDirs(localAbs), listSkillDirs(dir));
394
+ const staleRefs = findStaleReferences(REPO_ROOT, snapshot.routingPrefix, removedSkills);
395
+ if (staleRefs.length > 0) {
396
+ const detail = staleRefs
397
+ .map((r) => ` ${snapshot.routingPrefix}:${r.skill}\n ${r.files.join("\n ")}`)
398
+ .join("\n");
399
+ const message = `[${snapshot.name}] upstream removed ${staleRefs.length} skill(s) this repo still routes to:\n${detail}\n` +
400
+ ` Retarget or drop these references first, then re-run the refresh. ` +
401
+ `Pass --allow-stale-refs to land the snapshot anyway and fix them afterwards.`;
402
+ if (!allowStaleRefs)
403
+ throw new Error(message);
404
+ err(` WARNING: ${message}`);
405
+ }
406
+ // Our own annotations live inside the snapshot directory, so the wipe below
407
+ // destroys them along with the upstream copy. That silently deleted
408
+ // OUR_NOTES.md on both the superpowers (#304) and oh-my-claudecode (#308)
409
+ // refreshes — the drift radar, removed by the tool whose drift it records.
410
+ // Capture before the wipe, put back after the copy.
411
+ const ourFiles = await readOurFiles(localAbs);
257
412
  // Wipe + recreate destination.
258
413
  await rm(localAbs, { recursive: true, force: true });
259
414
  await mkdir(localAbs, { recursive: true });
@@ -279,6 +434,10 @@ async function refreshOne(snapshot, { force }) {
279
434
  }
280
435
  // Strip every CLAUDE.md (auto-loads as session context).
281
436
  const stripped = await deleteClaudeMdRecursive(localAbs);
437
+ // Put our annotations back. After the CLAUDE.md strip, so a snapshot that
438
+ // ever carries an OUR_* named CLAUDE.md is not re-deleted; before the diff
439
+ // stat, so the printed diff reflects what actually landed on disk.
440
+ const preserved = await restoreOurFiles(localAbs, ourFiles);
282
441
  // Defense in depth: forcibly delete excludePostCopy paths from the
283
442
  // local snapshot, regardless of whether they were copied. Guards
284
443
  // against silent regressions if selectiveDirs is later edited to
@@ -322,7 +481,7 @@ async function refreshOne(snapshot, { force }) {
322
481
  log(` upstream : ${snapshot.upstream}`);
323
482
  log(` pinned SHA : ${pinned}`);
324
483
  log(` upstream HEAD: ${headSha}`);
325
- log(` status : updated (${copiedDirs} dirs, ${copiedFiles} files, ${stripped} CLAUDE.md stripped, ${excluded} excluded paths removed, ${patchedKeys} json keys patched)`);
484
+ log(` status : updated (${copiedDirs} dirs, ${copiedFiles} files, ${stripped} CLAUDE.md stripped, ${preserved} of ours preserved, ${excluded} excluded paths removed, ${patchedKeys} json keys patched)`);
326
485
  if (String(diffStat.stdout).trim()) {
327
486
  log(" diff --stat :");
328
487
  for (const line of String(diffStat.stdout).trimEnd().split(/\r?\n/)) {
@@ -368,6 +527,10 @@ async function main() {
368
527
  exit(0);
369
528
  }
370
529
  const force = args.includes("--force");
530
+ // Deliberately NOT --force: that means "my tree is dirty, proceed", and a
531
+ // routine dirty-tree refresh must not silently switch off the stale-reference
532
+ // gate as a side effect.
533
+ const allowStaleRefs = args.includes("--allow-stale-refs");
371
534
  const check = args.includes("--check");
372
535
  const all = args.includes("--all");
373
536
  const positional = args.filter((a) => !a.startsWith("--"));
@@ -397,7 +560,7 @@ async function main() {
397
560
  allUpToDate = false;
398
561
  }
399
562
  else {
400
- await refreshOne(snap, { force });
563
+ await refreshOne(snap, { force, allowStaleRefs });
401
564
  }
402
565
  }
403
566
  catch (e) {
@@ -412,7 +575,14 @@ async function main() {
412
575
  exit(1);
413
576
  exit(0);
414
577
  }
415
- main().catch((e) => {
416
- err(`fatal: ${e.stack || e.message}`);
417
- exit(1);
418
- });
578
+ // Only run the CLI when invoked as one. Without this, importing the module to
579
+ // test its helpers would start a refresh. Same shape as the other bin/ scripts:
580
+ // the endsWith fallback covers Windows, where argv[1] is a backslash path and
581
+ // never equals the forward-slash file:// URL.
582
+ const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].replace(/\\/g, "/"));
583
+ if (invokedDirectly || argv[1]?.endsWith("refresh-third-party.mjs")) {
584
+ main().catch((e) => {
585
+ err(`fatal: ${e.stack || e.message}`);
586
+ exit(1);
587
+ });
588
+ }
@@ -17,7 +17,7 @@ The 7 Laws define *what* discipline must be applied. `/superpowers` decides *whi
17
17
  | `obra/superpowers` (Jesse Vincent) | vendored at `third-party/superpowers/`, pinned SHA `f2cbfbe` (v5.1.0) | `superpowers:brainstorming`, `:writing-plans`, `:executing-plans`, `:test-driven-development`, `:systematic-debugging`, `:requesting-code-review`, `:receiving-code-review`, `:verification-before-completion`, `:dispatching-parallel-agents`, `:using-git-worktrees`, `:finishing-a-development-branch`, `:subagent-driven-development`, `:writing-skills`, `:using-superpowers` |
18
18
  | `addyosmani/agent-skills` | vendored at `third-party/addy-agent-skills/`, pinned SHA `742dca5` (v1.0.0) | `spec-driven-development`, `source-driven-development`, `context-engineering`, `idea-refine`, `incremental-implementation`, `code-review-and-quality`, `code-simplification`, `security-and-hardening`, `debugging-and-error-recovery`, `performance-optimization`, `api-and-interface-design`, `frontend-ui-engineering`, `browser-testing-with-devtools`, `ci-cd-and-automation`, `deprecation-and-migration`, `documentation-and-adrs`, `git-workflow-and-versioning`, `planning-and-task-breakdown`, `shipping-and-launch` |
19
19
  | `ruflo-swarm` (ruvnet) | vendored at `third-party/ruflo-swarm/`, pinned SHA `addb5cd` (v0.2.0) | `swarm-init`, `monitor-stream`; `swarm_*` and `agent_*` MCP tools; `/swarm`, `/watch` |
20
- | `oh-my-claudecode` (Yeachan-Heo) | vendored at `third-party/oh-my-claudecode/`, pinned SHA `aacde3e` (v4.13.6) | 38 skills + 19 agents — `release`, `ultrawork`, `ultraqa`, `team`, `trace`, `visual-verdict`, `debug`, `deep-dive`, `deep-interview`, `autopilot`, `autoresearch`, plus a separate `ralph` (overlaps with our `/ralph` — see "Distinct variants" below) |
20
+ | `oh-my-claudecode` (Yeachan-Heo) | vendored at `third-party/oh-my-claudecode/`, pinned SHA `4820f56` (v5.3.0) | 37 skills + 19 agents — `release`, `ultragoal`, `launch`, `team`, `trace`, `visual-verdict`, `debug`, `review`, `research`, `deep-interview`, `autopilot`, `autoresearch`, plus a separate `ralph` (overlaps with our `/ralph` — see "Distinct variants" below) |
21
21
 
22
22
  PM coverage (product-management skills) lives **outside** this marketplace. If you need it, install [`phuryn/pm-skills`](https://github.com/phuryn/pm-skills) separately via Claude Code's host marketplace; the dispatcher names it as a routing target without pre-resolving the namespace. See [docs/THIRD_PARTY.md § Routing in /superpowers](../docs/THIRD_PARTY.md#routing-in-superpowers).
23
23
 
@@ -48,11 +48,7 @@ const OVERRIDES = {
48
48
  plugin: "agent-skills",
49
49
  },
50
50
  ralph: {
51
- companion: "oh-my-claudecode:ultrawork",
52
- plugin: "oh-my-claudecode",
53
- },
54
- "learn-eval": {
55
- companion: "oh-my-claudecode:retrospective",
51
+ companion: "oh-my-claudecode:ultragoal",
56
52
  plugin: "oh-my-claudecode",
57
53
  },
58
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "description": "Claude Code that gets sharper every session: the persistent-memory and runtime-discipline layer built on the 7 Laws of AI Agent Discipline. It 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. 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. Beginner: one /plugin install command. Expert: adds MCP tools and session hooks.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -52,6 +52,8 @@
52
52
  "verify:skill-count": "node bin/check-skill-count.mjs",
53
53
  "verify:skill-count-prose": "node bin/check-skill-count-prose.mjs",
54
54
  "verify:command-count": "node bin/check-command-count.mjs",
55
+ "verify:test-count": "node bin/check-test-count.mjs",
56
+ "verify:invariant-count": "node bin/check-invariant-count.mjs",
55
57
  "verify:docs-substrings": "node bin/check-docs-substrings.mjs",
56
58
  "verify:everything-mirror": "node bin/check-everything-mirror.mjs",
57
59
  "verify:routing-targets": "node bin/check-routing-targets.mjs",
@@ -62,7 +64,7 @@
62
64
  "verify:third-party-shape": "node bin/check-third-party-shape.mjs",
63
65
  "verify:tool-count": "node bin/check-tool-count.mjs",
64
66
  "verify:reconcile-parity": "node bin/check-reconcile-parity.mjs",
65
- "verify:all": "npm run verify:skill-mirror && npm run verify:skill-tiers && npm run verify:skill-law-tag && npm run verify:skill-count && npm run verify:skill-count-prose && npm run verify:command-count && npm run verify:docs-substrings && npm run verify:everything-mirror && npm run verify:routing-targets && npm run verify:doc-runtime-claims && npm run verify:test-imports-only && npm run verify:landing-version && npm run verify:scripts-citation-drift && npm run verify:third-party-shape && npm run verify:tool-count && npm run verify:reconcile-parity && npm run typecheck"
67
+ "verify:all": "npm run verify:skill-mirror && npm run verify:skill-tiers && npm run verify:skill-law-tag && npm run verify:skill-count && npm run verify:skill-count-prose && npm run verify:command-count && npm run verify:test-count && npm run verify:invariant-count && npm run verify:docs-substrings && npm run verify:everything-mirror && npm run verify:routing-targets && npm run verify:doc-runtime-claims && npm run verify:test-imports-only && npm run verify:landing-version && npm run verify:scripts-citation-drift && npm run verify:third-party-shape && npm run verify:tool-count && npm run verify:reconcile-parity && npm run typecheck"
66
68
  },
67
69
  "files": [
68
70
  ".claude-plugin/",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "mode": "beginner",
5
5
  "description": "Beginner mode: see what your agent learned, list its instincts, and request a session reflection. Bundles the ship fast path plus grounding skills (gateguard, tdd-workflow, verification-loop) so one-defect delivery, research, tests, and verification happen by default — every edit starts from facts, not guesses.",
6
6
  "tools": [
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "continuous-improvement",
10
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.24.0",
11
+ "version": "3.25.0",
12
12
  "source": "./",
13
13
  "author": {
14
14
  "name": "naimkatiman"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "naimkatiman",
@@ -17,7 +17,7 @@ The 7 Laws define *what* discipline must be applied. `/superpowers` decides *whi
17
17
  | `obra/superpowers` (Jesse Vincent) | vendored at `third-party/superpowers/`, pinned SHA `f2cbfbe` (v5.1.0) | `superpowers:brainstorming`, `:writing-plans`, `:executing-plans`, `:test-driven-development`, `:systematic-debugging`, `:requesting-code-review`, `:receiving-code-review`, `:verification-before-completion`, `:dispatching-parallel-agents`, `:using-git-worktrees`, `:finishing-a-development-branch`, `:subagent-driven-development`, `:writing-skills`, `:using-superpowers` |
18
18
  | `addyosmani/agent-skills` | vendored at `third-party/addy-agent-skills/`, pinned SHA `742dca5` (v1.0.0) | `spec-driven-development`, `source-driven-development`, `context-engineering`, `idea-refine`, `incremental-implementation`, `code-review-and-quality`, `code-simplification`, `security-and-hardening`, `debugging-and-error-recovery`, `performance-optimization`, `api-and-interface-design`, `frontend-ui-engineering`, `browser-testing-with-devtools`, `ci-cd-and-automation`, `deprecation-and-migration`, `documentation-and-adrs`, `git-workflow-and-versioning`, `planning-and-task-breakdown`, `shipping-and-launch` |
19
19
  | `ruflo-swarm` (ruvnet) | vendored at `third-party/ruflo-swarm/`, pinned SHA `addb5cd` (v0.2.0) | `swarm-init`, `monitor-stream`; `swarm_*` and `agent_*` MCP tools; `/swarm`, `/watch` |
20
- | `oh-my-claudecode` (Yeachan-Heo) | vendored at `third-party/oh-my-claudecode/`, pinned SHA `aacde3e` (v4.13.6) | 38 skills + 19 agents — `release`, `ultrawork`, `ultraqa`, `team`, `trace`, `visual-verdict`, `debug`, `deep-dive`, `deep-interview`, `autopilot`, `autoresearch`, plus a separate `ralph` (overlaps with our `/ralph` — see "Distinct variants" below) |
20
+ | `oh-my-claudecode` (Yeachan-Heo) | vendored at `third-party/oh-my-claudecode/`, pinned SHA `4820f56` (v5.3.0) | 37 skills + 19 agents — `release`, `ultragoal`, `launch`, `team`, `trace`, `visual-verdict`, `debug`, `review`, `research`, `deep-interview`, `autopilot`, `autoresearch`, plus a separate `ralph` (overlaps with our `/ralph` — see "Distinct variants" below) |
21
21
 
22
22
  PM coverage (product-management skills) lives **outside** this marketplace. If you need it, install [`phuryn/pm-skills`](https://github.com/phuryn/pm-skills) separately via Claude Code's host marketplace; the dispatcher names it as a routing target without pre-resolving the namespace. See [docs/THIRD_PARTY.md § Routing in /superpowers](../docs/THIRD_PARTY.md#routing-in-superpowers).
23
23
 
@@ -48,11 +48,7 @@ const OVERRIDES = {
48
48
  plugin: "agent-skills",
49
49
  },
50
50
  ralph: {
51
- companion: "oh-my-claudecode:ultrawork",
52
- plugin: "oh-my-claudecode",
53
- },
54
- "learn-eval": {
55
- companion: "oh-my-claudecode:retrospective",
51
+ companion: "oh-my-claudecode:ultragoal",
56
52
  plugin: "oh-my-claudecode",
57
53
  },
58
54
  };
@@ -191,14 +191,14 @@
191
191
  {
192
192
  "name": "Multi-session retrospective across a sprint",
193
193
  "patterns": ["retrospective", "sprint review", "multi.session review", "what worked.*what failed"],
194
- "preferred": ["oh-my-claudecode:retrospective", "learn-eval"],
194
+ "preferred": ["learn-eval"],
195
195
  "fallback": "What worked / what failed / what to do differently / 3 ranked next moves.",
196
196
  "marker": "oh-my-claudecode"
197
197
  },
198
198
  {
199
199
  "name": "Long autonomous run with quality gates",
200
200
  "patterns": ["long autonomous", "ultrawork", "quality gates? (between|per) iteration"],
201
- "preferred": ["oh-my-claudecode:ultrawork", "ralph"],
201
+ "preferred": ["oh-my-claudecode:ultragoal", "ralph"],
202
202
  "fallback": "PRD-shaped autonomous loop with verify-between-iterations.",
203
203
  "marker": "oh-my-claudecode"
204
204
  },
@@ -235,8 +235,8 @@ Rows whose **Preferred skill** is not bundled with the `continuous-improvement`
235
235
  | Fan out N agents on isolated worktrees with shared contract | `superpowers:dispatching-parallel-agents` → `ruflo-swarm:swarm-init` | Use the swarm contract: fixed roles + base ref + shared contract test; reconcile results after. (Reference behavior — does not require `ruflo-swarm`.) |
236
236
  | Stream live observation of long agent runs | `ruflo-swarm:monitor-stream` | Push-based event log; poll fallback if the MCP server is offline. (Reference behavior — does not require `ruflo-swarm`.) |
237
237
  | Visual regression / browser-level diff | `oh-my-claudecode:visual-verdict` | Playwright screenshot diff against staging baseline. (Reference behavior — does not require `oh-my-claudecode`.) |
238
- | Multi-session retrospective across a sprint | `oh-my-claudecode:retrospective` → `learn-eval` | What worked / what failed / what to do differently / 3 ranked next moves. (Reference behavior — does not require `oh-my-claudecode`.) |
239
- | Long autonomous run with quality gates | `oh-my-claudecode:ultrawork` → `ralph` | PRD-shaped autonomous loop with verify-between-iterations. (Reference behavior — does not require `oh-my-claudecode`.) |
238
+ | Multi-session retrospective across a sprint | `learn-eval` | What worked / what failed / what to do differently / 3 ranked next moves. (Reference behavior — does not require `oh-my-claudecode`.) |
239
+ | Long autonomous run with quality gates | `oh-my-claudecode:ultragoal` → `ralph` | PRD-shaped autonomous loop with verify-between-iterations. (Reference behavior — does not require `oh-my-claudecode`.) |
240
240
  | Product-management work (PRD, user stories, acceptance criteria, OKRs, experiments, personas, JTBD, lean canvas, market sizing, competitive analysis, meetings family, launch checklist) | Install phuryn/pm-skills via Claude Code marketplace — see docs/THIRD_PARTY.md | Out-of-band install (`claude plugin marketplace add phuryn/pm-skills` + the eight `pm-*@pm-skills` plugins). Inline fallback: keep the work shape (problem → user → goal → metric → scope; Given/When/Then per story; objective + 3-5 measurable KRs; we-believe / we'll-know hypothesis; TAM/SAM/SOM bottom-up; cross-cutting meetings agenda/brief/recap/synthesize) without depending on a specific routing target. |
241
241
 
242
242
  ## Phase 4: Verify (Law 4 — Verify Before Reporting)
@@ -46,7 +46,7 @@ See `docs/THIRD_PARTY.md` for plugin-by-plugin scope.
46
46
 
47
47
  | Order | Skill | When It Activates |
48
48
  |-------|-------|-------------------|
49
- | 1 | **brainstorming** | Before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. |
49
+ | 1 | **brainstorming** | Before writing code. Classifies the request as spike, bounded or architectural, then scales the ceremony to it: a 2-3 sentence probe, a short design in chat, or questions plus alternatives plus a sectioned design and a written spec. Approval is required before implementation on all three paths. |
50
50
  | 2 | **using-git-worktrees** | After design approval. Creates isolated workspace on new branch, runs project setup, verifies clean test baseline. |
51
51
  | 3 | **writing-plans** | With approved design. Breaks work into bite-sized tasks (2-5 minutes each). Every task has exact file paths, complete code, verification steps. |
52
52
  | 4 | **subagent-driven-development** or **executing-plans** | With plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality), or executes in batches with human checkpoints. |
@@ -87,8 +87,8 @@ When a task trigger fires, the dispatcher resolves to the first available skill
87
87
  | Simplify code, remove duplication | 6 | `agent-skills:code-simplification` → `simplify` |
88
88
  | Security review for auth/input/secrets | 4 | `agent-skills:security-and-hardening` → `security-review` |
89
89
  | Browser-level visual regression | 4 | `oh-my-claudecode:visual-verdict` (only source) |
90
- | Reflect after session, extract patterns | 5+7 | `ci:learn-eval` → `oh-my-claudecode:retrospective` |
91
- | Long autonomous run with quality gates | 6 | `oh-my-claudecode:ultrawork` → `ci:ralph` |
90
+ | Reflect after session, extract patterns | 5+7 | `ci:learn-eval` |
91
+ | Long autonomous run with quality gates | 6 | `oh-my-claudecode:ultragoal` → `ci:ralph` |
92
92
  | Coordinator role for staged hand-off | 3 | `ruflo-swarm:agents/coordinator` (when ruflo installed) |
93
93
  | Product-management work (PRD, OKRs, personas, GTM, growth, market research, analytics) | 1+2+5 | Install `phuryn/pm-skills` via Claude Code marketplace — see docs/THIRD_PARTY.md. Eight installable plugins (`pm-toolkit`, `pm-product-strategy`, `pm-product-discovery`, `pm-market-research`, `pm-data-analytics`, `pm-marketing-growth`, `pm-go-to-market`, `pm-execution`) cover the full lifecycle. Out of band — not a `/plugin install <name>@continuous-improvement` target. |
94
94
 
@@ -122,15 +122,14 @@ Valid values:
122
122
 
123
123
  ### Which rows the override affects
124
124
 
125
- These are the routing rows where the override changes the resolved target. Rows not listed here are CI-only or companion-only and route the same under any setting.
125
+ These are the routing rows where the override changes the resolved target. Rows not listed here are CI-only or companion-only and route the same under any setting. `ci:learn-eval` was listed here until OMC 5.x: its companion `oh-my-claudecode:retrospective` never existed upstream, so the row is CI-only and the override cannot change it.
126
126
 
127
127
  | Trigger | `ci-first` (default) | `companions-first` |
128
128
  |---|---|---|
129
129
  | Write a failing test before code | `ci:tdd-workflow` | `superpowers:test-driven-development`, then `agent-skills:test-driven-development` |
130
130
  | Verify before declaring done | `ci:verification-loop` | `superpowers:verification-before-completion` |
131
131
  | Curate the right context window | `ci:context-budget` | `agent-skills:context-engineering` |
132
- | Long autonomous run with quality gates | `ci:ralph` | `oh-my-claudecode:ultrawork`, then `ci:ralph` |
133
- | Reflect after session, extract patterns | `ci:learn-eval` | `oh-my-claudecode:retrospective` |
132
+ | Long autonomous run with quality gates | `ci:ralph` | `oh-my-claudecode:ultragoal`, then `ci:ralph` |
134
133
 
135
134
  `superpowers:writing-plans` already wins the planning chain under both settings — it is the first entry, with `ci:planning-with-files` as the third fallback — so that row is unchanged.
136
135
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continuous-improvement",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "mode": "expert",
5
5
  "description": "Expert mode: tune confidence, manage instincts, and persist plans on disk. Adds safety, token-budget, and strategic-compact skills plus the /learn-eval command so long sessions stay sharp and learnings survive context resets.",
6
6
  "tools": [
@@ -191,14 +191,14 @@
191
191
  {
192
192
  "name": "Multi-session retrospective across a sprint",
193
193
  "patterns": ["retrospective", "sprint review", "multi.session review", "what worked.*what failed"],
194
- "preferred": ["oh-my-claudecode:retrospective", "learn-eval"],
194
+ "preferred": ["learn-eval"],
195
195
  "fallback": "What worked / what failed / what to do differently / 3 ranked next moves.",
196
196
  "marker": "oh-my-claudecode"
197
197
  },
198
198
  {
199
199
  "name": "Long autonomous run with quality gates",
200
200
  "patterns": ["long autonomous", "ultrawork", "quality gates? (between|per) iteration"],
201
- "preferred": ["oh-my-claudecode:ultrawork", "ralph"],
201
+ "preferred": ["oh-my-claudecode:ultragoal", "ralph"],
202
202
  "fallback": "PRD-shaped autonomous loop with verify-between-iterations.",
203
203
  "marker": "oh-my-claudecode"
204
204
  },
@@ -235,8 +235,8 @@ Rows whose **Preferred skill** is not bundled with the `continuous-improvement`
235
235
  | Fan out N agents on isolated worktrees with shared contract | `superpowers:dispatching-parallel-agents` → `ruflo-swarm:swarm-init` | Use the swarm contract: fixed roles + base ref + shared contract test; reconcile results after. (Reference behavior — does not require `ruflo-swarm`.) |
236
236
  | Stream live observation of long agent runs | `ruflo-swarm:monitor-stream` | Push-based event log; poll fallback if the MCP server is offline. (Reference behavior — does not require `ruflo-swarm`.) |
237
237
  | Visual regression / browser-level diff | `oh-my-claudecode:visual-verdict` | Playwright screenshot diff against staging baseline. (Reference behavior — does not require `oh-my-claudecode`.) |
238
- | Multi-session retrospective across a sprint | `oh-my-claudecode:retrospective` → `learn-eval` | What worked / what failed / what to do differently / 3 ranked next moves. (Reference behavior — does not require `oh-my-claudecode`.) |
239
- | Long autonomous run with quality gates | `oh-my-claudecode:ultrawork` → `ralph` | PRD-shaped autonomous loop with verify-between-iterations. (Reference behavior — does not require `oh-my-claudecode`.) |
238
+ | Multi-session retrospective across a sprint | `learn-eval` | What worked / what failed / what to do differently / 3 ranked next moves. (Reference behavior — does not require `oh-my-claudecode`.) |
239
+ | Long autonomous run with quality gates | `oh-my-claudecode:ultragoal` → `ralph` | PRD-shaped autonomous loop with verify-between-iterations. (Reference behavior — does not require `oh-my-claudecode`.) |
240
240
  | Product-management work (PRD, user stories, acceptance criteria, OKRs, experiments, personas, JTBD, lean canvas, market sizing, competitive analysis, meetings family, launch checklist) | Install phuryn/pm-skills via Claude Code marketplace — see docs/THIRD_PARTY.md | Out-of-band install (`claude plugin marketplace add phuryn/pm-skills` + the eight `pm-*@pm-skills` plugins). Inline fallback: keep the work shape (problem → user → goal → metric → scope; Given/When/Then per story; objective + 3-5 measurable KRs; we-believe / we'll-know hypothesis; TAM/SAM/SOM bottom-up; cross-cutting meetings agenda/brief/recap/synthesize) without depending on a specific routing target. |
241
241
 
242
242
  ## Phase 4: Verify (Law 4 — Verify Before Reporting)
@@ -46,7 +46,7 @@ See `docs/THIRD_PARTY.md` for plugin-by-plugin scope.
46
46
 
47
47
  | Order | Skill | When It Activates |
48
48
  |-------|-------|-------------------|
49
- | 1 | **brainstorming** | Before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. |
49
+ | 1 | **brainstorming** | Before writing code. Classifies the request as spike, bounded or architectural, then scales the ceremony to it: a 2-3 sentence probe, a short design in chat, or questions plus alternatives plus a sectioned design and a written spec. Approval is required before implementation on all three paths. |
50
50
  | 2 | **using-git-worktrees** | After design approval. Creates isolated workspace on new branch, runs project setup, verifies clean test baseline. |
51
51
  | 3 | **writing-plans** | With approved design. Breaks work into bite-sized tasks (2-5 minutes each). Every task has exact file paths, complete code, verification steps. |
52
52
  | 4 | **subagent-driven-development** or **executing-plans** | With plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality), or executes in batches with human checkpoints. |
@@ -87,8 +87,8 @@ When a task trigger fires, the dispatcher resolves to the first available skill
87
87
  | Simplify code, remove duplication | 6 | `agent-skills:code-simplification` → `simplify` |
88
88
  | Security review for auth/input/secrets | 4 | `agent-skills:security-and-hardening` → `security-review` |
89
89
  | Browser-level visual regression | 4 | `oh-my-claudecode:visual-verdict` (only source) |
90
- | Reflect after session, extract patterns | 5+7 | `ci:learn-eval` → `oh-my-claudecode:retrospective` |
91
- | Long autonomous run with quality gates | 6 | `oh-my-claudecode:ultrawork` → `ci:ralph` |
90
+ | Reflect after session, extract patterns | 5+7 | `ci:learn-eval` |
91
+ | Long autonomous run with quality gates | 6 | `oh-my-claudecode:ultragoal` → `ci:ralph` |
92
92
  | Coordinator role for staged hand-off | 3 | `ruflo-swarm:agents/coordinator` (when ruflo installed) |
93
93
  | Product-management work (PRD, OKRs, personas, GTM, growth, market research, analytics) | 1+2+5 | Install `phuryn/pm-skills` via Claude Code marketplace — see docs/THIRD_PARTY.md. Eight installable plugins (`pm-toolkit`, `pm-product-strategy`, `pm-product-discovery`, `pm-market-research`, `pm-data-analytics`, `pm-marketing-growth`, `pm-go-to-market`, `pm-execution`) cover the full lifecycle. Out of band — not a `/plugin install <name>@continuous-improvement` target. |
94
94
 
@@ -122,15 +122,14 @@ Valid values:
122
122
 
123
123
  ### Which rows the override affects
124
124
 
125
- These are the routing rows where the override changes the resolved target. Rows not listed here are CI-only or companion-only and route the same under any setting.
125
+ These are the routing rows where the override changes the resolved target. Rows not listed here are CI-only or companion-only and route the same under any setting. `ci:learn-eval` was listed here until OMC 5.x: its companion `oh-my-claudecode:retrospective` never existed upstream, so the row is CI-only and the override cannot change it.
126
126
 
127
127
  | Trigger | `ci-first` (default) | `companions-first` |
128
128
  |---|---|---|
129
129
  | Write a failing test before code | `ci:tdd-workflow` | `superpowers:test-driven-development`, then `agent-skills:test-driven-development` |
130
130
  | Verify before declaring done | `ci:verification-loop` | `superpowers:verification-before-completion` |
131
131
  | Curate the right context window | `ci:context-budget` | `agent-skills:context-engineering` |
132
- | Long autonomous run with quality gates | `ci:ralph` | `oh-my-claudecode:ultrawork`, then `ci:ralph` |
133
- | Reflect after session, extract patterns | `ci:learn-eval` | `oh-my-claudecode:retrospective` |
132
+ | Long autonomous run with quality gates | `ci:ralph` | `oh-my-claudecode:ultragoal`, then `ci:ralph` |
134
133
 
135
134
  `superpowers:writing-plans` already wins the planning chain under both settings — it is the first entry, with `ci:planning-with-files` as the third fallback — so that row is unchanged.
136
135