continuous-improvement 3.24.0 → 3.26.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 +31 -0
- package/README.md +2 -1
- 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/ship.md +2 -0
- package/commands/superpowers.md +1 -1
- package/hooks/companion-preference.mjs +1 -5
- package/hooks/route-prompt.mjs +12 -0
- package/lib/gateguard-state.mjs +12 -1
- 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/ship.md +2 -0
- package/plugins/continuous-improvement/commands/superpowers.md +1 -1
- package/plugins/continuous-improvement/hooks/companion-preference.mjs +1 -5
- package/plugins/continuous-improvement/hooks/route-prompt.mjs +12 -0
- package/plugins/continuous-improvement/lib/gateguard-state.mjs +12 -1
- package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +2 -2
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +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/gateguard.md +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.26.0",
|
|
12
12
|
"source": "./plugins/continuous-improvement",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "naimkatiman"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to this skill are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.26.0] — 2026-11-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`CI_ROUTE_PROMPT_DISABLE=1` turns the prompt router off** — `hooks/route-prompt.mjs` had no off switch, so a project with its own plan, test or review skills got a competing `planning-with-files` / `tdd-workflow` hint on every prompt containing those words. Set the variable in that project's `.claude/settings.json` `env` block and the hook emits nothing and writes no telemetry. Default unchanged. (#321)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`/ship` no longer falls through to a project's own `ship` skill** — the command forwarded to "the native `ship` skill", and in a repo that defines `.claude/skills/ship` a bare `ship` resolves to that project workflow, which can merge and deploy. The command now names where the native skill lives and stops to ask when the project defines its own. (#321)
|
|
16
|
+
- **gateguard's cleared-files cap no longer stops real multi-slice work** — the 50-file cap halted a session touching source, tests and docs across a few PRs. The default is now 1000, which still bounds a runaway loop, and a positive-integer `GATEGUARD_MAX_CLEARED_FILES` overrides it. (#320)
|
|
17
|
+
|
|
18
|
+
## [3.25.0] — 2026-09-07
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **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)
|
|
23
|
+
- **`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)
|
|
24
|
+
- **`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)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **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)
|
|
29
|
+
- **obra/superpowers snapshot 5.1.0 → 6.3.0**. (#304)
|
|
30
|
+
- **`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)
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`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)
|
|
35
|
+
- **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)
|
|
36
|
+
- **Brainstorming dispatcher wording** now describes the three-path router, not just the architectural path. (#305)
|
|
37
|
+
|
|
7
38
|
## [3.24.0] — 2026-09-06
|
|
8
39
|
|
|
9
40
|
### Added
|
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ You should see Claude **blocked** by the bundled `gateguard` PreToolUse hook (`h
|
|
|
171
171
|
|
|
172
172
|
The 7 Laws are enforced at **two layers**:
|
|
173
173
|
|
|
174
|
-
- **Runtime layer (hooks).** `gateguard` ships as a PreToolUse hook (`hooks/gateguard.mjs`) that physically blocks Edit / Write / MultiEdit / destructive Bash on the first mutation per file until the agent presents the facts named in [skills/gateguard.md § Gate Types](skills/gateguard.md). Destructive Bash (`rm -rf`, `git push --force`, `--force-with-lease`, `DROP DATABASE`, Windows `Remove-Item -Recurse`, etc.) is gated on every call, not just first. Read-only and exploratory tools (Read, Grep, Glob, routine Bash like `git status`) bypass the gate. Per-session state at `~/.claude/instincts/<project-hash>/gateguard-session.json` caps cumulative clearances at
|
|
174
|
+
- **Runtime layer (hooks).** `gateguard` ships as a PreToolUse hook (`hooks/gateguard.mjs`) that physically blocks Edit / Write / MultiEdit / destructive Bash on the first mutation per file until the agent presents the facts named in [skills/gateguard.md § Gate Types](skills/gateguard.md). Destructive Bash (`rm -rf`, `git push --force`, `--force-with-lease`, `DROP DATABASE`, Windows `Remove-Item -Recurse`, etc.) is gated on every call, not just first. Read-only and exploratory tools (Read, Grep, Glob, routine Bash like `git status`) bypass the gate. Per-session state at `~/.claude/instincts/<project-hash>/gateguard-session.json` caps cumulative clearances at 1000 distinct files per session by default (`GATEGUARD_MAX_CLEARED_FILES` overrides it) to bound stuck-loop damage. A second runtime hook, `goal-drift-stop` (`hooks/goal-drift-stop.mjs`), fires on `Stop`: it scores each turn against the stated `## Goal` and, on a substantive wrap-up that has drifted off-goal, warns by default (or re-prompts under `CLAUDE_GOAL_DRIFT_GATE=block`) — so a drifted session can't quietly declare "done". Fail-open.
|
|
175
175
|
- **Model layer (skills).** Once the runtime gate clears for a file, the rest of the discipline (`tdd-workflow`, `verification-loop`, `proceed-with-the-recommendation`, etc.) runs model-side — the agent reads each skill and applies it. `observe.sh` / `observe.mjs` records every tool call into the Mulahazah feed for instinct extraction; that surface is observational, not enforcement.
|
|
176
176
|
|
|
177
177
|
V1 honest limitations: the runtime gate is honor-system once the agent flips `_gateguard_facts_presented: true` (the hook can't verify the investigation actually happened); the state file is deletable and parallel hook invocations can race. Documented in `src/hooks/gateguard.mts` and `src/lib/gateguard-state.mts` headers.
|
|
@@ -226,6 +226,7 @@ The framework has documented operator-level modes that change hook behavior with
|
|
|
226
226
|
| `CI_GATEGUARD_EXCLUDE` | Comma-separated path fragments that `hooks/gateguard.mjs` skips for Edit / Write / MultiEdit (case-insensitive substring match on the forward-slash path). Meant for a prose wiki or a generated scratch dir. The hook prints one stderr line naming the fragment whenever an exclusion fires, and a catch-all fragment such as `/` or `.` matches every path and is reported as the file gate being off. Destructive Bash is never excluded. Unset (default) changes nothing. | bash/zsh: `export CI_GATEGUARD_EXCLUDE=docs/wiki,scratch/`. PowerShell: `$env:CI_GATEGUARD_EXCLUDE='docs/wiki,scratch/'` (session). |
|
|
227
227
|
| `CI_GATEGUARD_TARGET_LOCK=block` | `hooks/gateguard.mjs` refuses any Edit / Write / MultiEdit whose absolute target canonicalizes outside the session project root, before the fact gate and independent of clearance, so a wrong-repo or wrong-worktree write is denied even with perfect facts. Off by default. | bash/zsh: `export CI_GATEGUARD_TARGET_LOCK=block`. PowerShell: `$env:CI_GATEGUARD_TARGET_LOCK='block'` (session). |
|
|
228
228
|
| `CI_CONFIG_GUARD` | `hooks/config-guard.mjs` (a PreToolUse hook on Bash / Edit / Write / MultiEdit / NotebookEdit) watches the files that wire the guardrails: `.claude/settings*.json`, `.mcp.json`, `hooks.json`, `.claude/hooks/`, `.claude/plugins/`, `.claude-plugin/`, and the `claude plugin\|mcp\|config` CLI forms that edit them. `warn` (default) prints one stderr line and never blocks; `block` denies the call with the reason and the bypass; `off` disables it. Reads never trigger it. One-call bypass: `CI_CONFIG_GUARD_ALLOW=true`. Fails open. | bash/zsh: `export CI_CONFIG_GUARD=block` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CI_CONFIG_GUARD='block'` (session) or `[Environment]::SetEnvironmentVariable('CI_CONFIG_GUARD','block','User')` (persistent). |
|
|
229
|
+
| `CI_ROUTE_PROMPT_DISABLE=1` | `hooks/route-prompt.mjs` (a UserPromptSubmit hook) emits no skill suggestion and writes no telemetry. Use it in a project that routes plan, test or review prompts to its own skills, so the generic hints do not compete with them. Set it per project in that project's `.claude/settings.json` `env` block. `true` also works; unset or any other value changes nothing. | Per project: `"env": { "CI_ROUTE_PROMPT_DISABLE": "1" }` in `.claude/settings.json`. bash/zsh: `export CI_ROUTE_PROMPT_DISABLE=1`. PowerShell: `$env:CI_ROUTE_PROMPT_DISABLE=1` (session). |
|
|
229
230
|
| `CLAUDE_QUERY_COST_NUDGE=on` | `hooks/query-cost-nudge.mjs` (a `Stop` hook) guards against surprise DB bills: when the working tree has changed DB/query files (`.sql`, `.prisma`, `migrations/`, `/db/`, `schema.*`, `drizzle`) at turn end, it injects a once-per-session `additionalContext` reminder to run a D1-aware cost audit — dispatch the `database-reviewer` agent or check EXPLAIN QUERY PLAN, index coverage, N+1, and D1 `rows_read` billing before finishing. `on` enables it; default (unset) is off. Opt-in amplifier, never a gate; dedupes per session and fails open. | bash/zsh: `export CLAUDE_QUERY_COST_NUDGE=on` in `~/.bashrc` / `~/.zshrc`. PowerShell: `$env:CLAUDE_QUERY_COST_NUDGE='on'` (session) or `[Environment]::SetEnvironmentVariable('CLAUDE_QUERY_COST_NUDGE','on','User')` (persistent). |
|
|
230
231
|
|
|
231
232
|
</details>
|
|
@@ -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/ship.md
CHANGED
|
@@ -10,4 +10,6 @@ Invoke the native `ship` skill with this complete defect request:
|
|
|
10
10
|
|
|
11
11
|
$ARGUMENTS
|
|
12
12
|
|
|
13
|
+
The native skill is the one continuous-improvement installs: `skills/ship.md` in the plugin, or `~/.claude/skills/ship/SKILL.md` from the npm installer. If the current project defines its own `.claude/skills/ship`, that is a different workflow with its own merge and deploy rules, and a bare `ship` resolves to it. In that case stop and ask the operator which workflow they meant before doing anything.
|
|
14
|
+
|
|
13
15
|
Follow that skill exactly. Do not reconstruct or duplicate its Git, worktree, TDD, return, cleanup, merge, or deploy rules here. If the native skill is unavailable, stop and tell the operator to update or reinstall `continuous-improvement`.
|
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/hooks/route-prompt.mjs
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* OR empty when there is no match.
|
|
14
14
|
* Exit : 0 always — routing decisions live in stdout, never in exit code.
|
|
15
15
|
*
|
|
16
|
+
* Opt-out: CI_ROUTE_PROMPT_DISABLE=1 (or `true`) emits nothing and writes no
|
|
17
|
+
* telemetry. Set it in a project's `.claude/settings.json` `env` block when
|
|
18
|
+
* that project routes plan / test / review prompts to its own skills.
|
|
19
|
+
*
|
|
16
20
|
* Fail-open: any unexpected error reading stdin / parsing the route table /
|
|
17
21
|
* compiling a pattern → emit nothing and exit 0. The hook never blocks a
|
|
18
22
|
* prompt on its own bugs.
|
|
@@ -44,6 +48,10 @@ function readStdin() {
|
|
|
44
48
|
return "";
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
function isRouterDisabled() {
|
|
52
|
+
const value = (process.env.CI_ROUTE_PROMPT_DISABLE ?? "").trim().toLowerCase();
|
|
53
|
+
return value === "1" || value === "true";
|
|
54
|
+
}
|
|
47
55
|
function resolveHome() {
|
|
48
56
|
return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
49
57
|
}
|
|
@@ -129,6 +137,10 @@ function emit(output) {
|
|
|
129
137
|
process.exit(0);
|
|
130
138
|
}
|
|
131
139
|
function main() {
|
|
140
|
+
if (isRouterDisabled()) {
|
|
141
|
+
emit(null);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
132
144
|
const raw = readStdin();
|
|
133
145
|
let payload;
|
|
134
146
|
try {
|
package/lib/gateguard-state.mjs
CHANGED
|
@@ -27,7 +27,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
27
27
|
import { execFileSync } from "node:child_process";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { join } from "node:path";
|
|
30
|
-
|
|
30
|
+
const DEFAULT_MAX_CLEARED_FILES = 1000;
|
|
31
|
+
/**
|
|
32
|
+
* The per-session cap. 50 stopped real multi-slice work mid-task (a session that
|
|
33
|
+
* touches a few dozen source, test and doc files hit it); 1000 still bounds a
|
|
34
|
+
* runaway loop. GATEGUARD_MAX_CLEARED_FILES (a positive integer) overrides it.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveMaxClearedFiles(env) {
|
|
37
|
+
const raw = env.GATEGUARD_MAX_CLEARED_FILES ?? "";
|
|
38
|
+
const n = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
|
|
39
|
+
return Number.isSafeInteger(n) && n > 0 ? n : DEFAULT_MAX_CLEARED_FILES;
|
|
40
|
+
}
|
|
41
|
+
export const MAX_CLEARED_FILES = resolveMaxClearedFiles(process.env);
|
|
31
42
|
// A clearance file whose created_at is older than this self-heals to empty on
|
|
32
43
|
// the next load, so a stale gate never needs a manual rm. Long enough not to
|
|
33
44
|
// reset an active session mid-work; the real cross-session fix is the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.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.26.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.26.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.26.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",
|
|
@@ -10,4 +10,6 @@ Invoke the native `ship` skill with this complete defect request:
|
|
|
10
10
|
|
|
11
11
|
$ARGUMENTS
|
|
12
12
|
|
|
13
|
+
The native skill is the one continuous-improvement installs: `skills/ship.md` in the plugin, or `~/.claude/skills/ship/SKILL.md` from the npm installer. If the current project defines its own `.claude/skills/ship`, that is a different workflow with its own merge and deploy rules, and a bare `ship` resolves to it. In that case stop and ask the operator which workflow they meant before doing anything.
|
|
14
|
+
|
|
13
15
|
Follow that skill exactly. Do not reconstruct or duplicate its Git, worktree, TDD, return, cleanup, merge, or deploy rules here. If the native skill is unavailable, stop and tell the operator to update or reinstall `continuous-improvement`.
|
|
@@ -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
|
};
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* OR empty when there is no match.
|
|
14
14
|
* Exit : 0 always — routing decisions live in stdout, never in exit code.
|
|
15
15
|
*
|
|
16
|
+
* Opt-out: CI_ROUTE_PROMPT_DISABLE=1 (or `true`) emits nothing and writes no
|
|
17
|
+
* telemetry. Set it in a project's `.claude/settings.json` `env` block when
|
|
18
|
+
* that project routes plan / test / review prompts to its own skills.
|
|
19
|
+
*
|
|
16
20
|
* Fail-open: any unexpected error reading stdin / parsing the route table /
|
|
17
21
|
* compiling a pattern → emit nothing and exit 0. The hook never blocks a
|
|
18
22
|
* prompt on its own bugs.
|
|
@@ -44,6 +48,10 @@ function readStdin() {
|
|
|
44
48
|
return "";
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
function isRouterDisabled() {
|
|
52
|
+
const value = (process.env.CI_ROUTE_PROMPT_DISABLE ?? "").trim().toLowerCase();
|
|
53
|
+
return value === "1" || value === "true";
|
|
54
|
+
}
|
|
47
55
|
function resolveHome() {
|
|
48
56
|
return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
49
57
|
}
|
|
@@ -129,6 +137,10 @@ function emit(output) {
|
|
|
129
137
|
process.exit(0);
|
|
130
138
|
}
|
|
131
139
|
function main() {
|
|
140
|
+
if (isRouterDisabled()) {
|
|
141
|
+
emit(null);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
132
144
|
const raw = readStdin();
|
|
133
145
|
let payload;
|
|
134
146
|
try {
|
|
@@ -27,7 +27,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
27
27
|
import { execFileSync } from "node:child_process";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { join } from "node:path";
|
|
30
|
-
|
|
30
|
+
const DEFAULT_MAX_CLEARED_FILES = 1000;
|
|
31
|
+
/**
|
|
32
|
+
* The per-session cap. 50 stopped real multi-slice work mid-task (a session that
|
|
33
|
+
* touches a few dozen source, test and doc files hit it); 1000 still bounds a
|
|
34
|
+
* runaway loop. GATEGUARD_MAX_CLEARED_FILES (a positive integer) overrides it.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveMaxClearedFiles(env) {
|
|
37
|
+
const raw = env.GATEGUARD_MAX_CLEARED_FILES ?? "";
|
|
38
|
+
const n = /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : Number.NaN;
|
|
39
|
+
return Number.isSafeInteger(n) && n > 0 ? n : DEFAULT_MAX_CLEARED_FILES;
|
|
40
|
+
}
|
|
41
|
+
export const MAX_CLEARED_FILES = resolveMaxClearedFiles(process.env);
|
|
31
42
|
// A clearance file whose created_at is older than this self-heals to empty on
|
|
32
43
|
// the next load, so a stale gate never needs a manual rm. Long enough not to
|
|
33
44
|
// reset an active session mid-work; the real cross-session fix is the
|
|
@@ -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
|
},
|
|
@@ -136,7 +136,7 @@ This gate is what catches the squash-merge / ahead-of-origin trap recorded in th
|
|
|
136
136
|
|
|
137
137
|
### Today: runtime hook + skill (zero install beyond the plugin)
|
|
138
138
|
|
|
139
|
-
`hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES
|
|
139
|
+
`hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES`, 1000 distinct files per session by default (set `GATEGUARD_MAX_CLEARED_FILES` to a positive integer to change it), and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
|
|
140
140
|
|
|
141
141
|
Smoke-test the runtime gate after install: ask Claude to write a throwaway file with no research first. The hook should return a `block` decision with a fact-list reason; Claude should pause rather than write.
|
|
142
142
|
|
|
@@ -174,7 +174,7 @@ If you previously opted into `safety-guard` for autonomous or production-adjacen
|
|
|
174
174
|
|
|
175
175
|
### Limitations and guarantees
|
|
176
176
|
|
|
177
|
-
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the
|
|
177
|
+
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the per-session file cap (1000 by default) bounds damage from stuck loops or rogue agents.
|
|
178
178
|
- **State-file deletion / self-heal.** `rm`-ing the session state resets every gate. Because state is scoped per session (`sessions/<session-id>/`), the session is a real trust boundary, not one shared across concurrent runs. A stale state file also self-heals once its `created_at` ages past `STATE_TTL_MS`, so a manual `rm` is rarely needed.
|
|
179
179
|
- **Parallel-hook race.** Two simultaneous hook invocations can race the read+write of the state file. Acceptable trade-off vs Windows atomic-rename complexity.
|
|
180
180
|
|
|
@@ -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.26.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
|
},
|
package/skills/gateguard.md
CHANGED
|
@@ -136,7 +136,7 @@ This gate is what catches the squash-merge / ahead-of-origin trap recorded in th
|
|
|
136
136
|
|
|
137
137
|
### Today: runtime hook + skill (zero install beyond the plugin)
|
|
138
138
|
|
|
139
|
-
`hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES
|
|
139
|
+
`hooks/gateguard.mjs` is bundled with this plugin and wired as the first PreToolUse hook in `plugins/continuous-improvement/hooks/hooks.json`. When you install the plugin, the runtime gate is live — no extra config, no opt-in. The hook reads tool input from stdin, classifies it through a data-driven routing table (Read/Grep/Glob → allow, Write/Edit/MultiEdit → mutating-file gate, Bash → destructive-pattern check), and emits `{decision, reason?}` on stdout. Per-session state lives at `~/.claude/instincts/<project-hash>/sessions/<session-id>/gateguard-session.json` — scoped by the stdin `session_id` so the cap never bleeds across concurrent sessions (it falls back to the unscoped `<project-hash>/` dir when no session id is present, and `GATEGUARD_SESSION_DIR` overrides it for tests). The cap is `MAX_CLEARED_FILES`, 1000 distinct files per session by default (set `GATEGUARD_MAX_CLEARED_FILES` to a positive integer to change it), and the state self-heals after `STATE_TTL_MS` so a stale gate never needs a manual `rm`.
|
|
140
140
|
|
|
141
141
|
Smoke-test the runtime gate after install: ask Claude to write a throwaway file with no research first. The hook should return a `block` decision with a fact-list reason; Claude should pause rather than write.
|
|
142
142
|
|
|
@@ -174,7 +174,7 @@ If you previously opted into `safety-guard` for autonomous or production-adjacen
|
|
|
174
174
|
|
|
175
175
|
### Limitations and guarantees
|
|
176
176
|
|
|
177
|
-
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the
|
|
177
|
+
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the per-session file cap (1000 by default) bounds damage from stuck loops or rogue agents.
|
|
178
178
|
- **State-file deletion / self-heal.** `rm`-ing the session state resets every gate. Because state is scoped per session (`sessions/<session-id>/`), the session is a real trust boundary, not one shared across concurrent runs. A stale state file also self-heals once its `created_at` ages past `STATE_TTL_MS`, so a manual `rm` is rarely needed.
|
|
179
179
|
- **Parallel-hook race.** Two simultaneous hook invocations can race the read+write of the state file. Acceptable trade-off vs Windows atomic-rename complexity.
|
|
180
180
|
|
|
@@ -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
|
|