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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +20 -0
- package/bin/check-invariant-count.mjs +144 -0
- package/bin/check-routing-targets.mjs +74 -4
- package/bin/check-test-count.mjs +135 -0
- package/bin/refresh-third-party.mjs +180 -10
- package/commands/superpowers.md +1 -1
- package/hooks/companion-preference.mjs +1 -5
- package/package.json +4 -2
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +1 -1
- package/plugins/continuous-improvement/commands/superpowers.md +1 -1
- package/plugins/continuous-improvement/hooks/companion-preference.mjs +1 -5
- package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +2 -2
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/superpowers/SKILL.md +5 -6
- package/plugins/expert.json +1 -1
- package/scripts/route-recommendation.routes.json +2 -2
- package/skills/proceed-with-the-recommendation.md +2 -2
- package/skills/superpowers.md +5 -6
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
+
}
|
package/commands/superpowers.md
CHANGED
|
@@ -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 `
|
|
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:
|
|
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.
|
|
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/",
|
package/plugins/beginner.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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.
|
|
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 `
|
|
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:
|
|
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": ["
|
|
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:
|
|
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 | `
|
|
239
|
-
| Long autonomous run with quality gates | `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.
|
|
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`
|
|
91
|
-
| Long autonomous run with quality gates | 6 | `oh-my-claudecode:
|
|
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:
|
|
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
|
|
package/plugins/expert.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
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": ["
|
|
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:
|
|
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 | `
|
|
239
|
-
| Long autonomous run with quality gates | `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)
|
package/skills/superpowers.md
CHANGED
|
@@ -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.
|
|
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`
|
|
91
|
-
| Long autonomous run with quality gates | 6 | `oh-my-claudecode:
|
|
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:
|
|
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
|
|