continuous-improvement 3.0.0 → 3.8.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 +78 -0
- package/CHANGELOG.md +210 -0
- package/LICENSE +21 -21
- package/QUICKSTART.md +101 -81
- package/README.md +224 -198
- package/SKILL.md +87 -9
- package/action.yml +33 -0
- package/bin/analyze.sh +161 -153
- package/bin/backfill.mjs +172 -0
- package/bin/check-docs-substrings.mjs +333 -0
- package/bin/check-everything-mirror.mjs +145 -0
- package/bin/check-routing-targets.mjs +151 -0
- package/bin/check-skill-law-tag.mjs +128 -0
- package/bin/check-skill-mirror.mjs +119 -0
- package/bin/check-skill-tiers.mjs +116 -0
- package/bin/check-third-party-shape.mjs +202 -0
- package/bin/generate-plugin-manifests.mjs +169 -0
- package/bin/harvest-friction.mjs +279 -0
- package/bin/hook-stats.mjs +258 -0
- package/bin/install.mjs +418 -456
- package/bin/lint-transcript.mjs +239 -0
- package/bin/mcp-server.mjs +842 -499
- package/bin/observe.mjs +148 -0
- package/bin/pre-commit-block-strays.sh +49 -0
- package/bin/refresh-third-party.mjs +416 -0
- package/bin/unified-cli.mjs +533 -0
- package/commands/continuous-improvement.md +115 -74
- package/commands/dashboard.md +56 -0
- package/commands/discipline.md +51 -0
- package/commands/harvest.md +76 -0
- package/commands/learn-eval.md +117 -0
- package/commands/planning-with-files.md +66 -0
- package/commands/proceed-with-the-recommendation.md +62 -0
- package/commands/ralph.md +103 -0
- package/commands/release-train.md +81 -0
- package/commands/seven-laws.md +16 -0
- package/commands/superpowers.md +153 -0
- package/commands/swarm.md +101 -0
- package/commands/workspace-surface-audit.md +77 -0
- package/hooks/observe.sh +172 -134
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +181 -0
- package/instinct-packs/go.json +58 -0
- package/instinct-packs/meta.json +16 -0
- package/instinct-packs/python.json +58 -0
- package/instinct-packs/react.json +58 -0
- package/lib/cli-anything.mjs +401 -0
- package/lib/compound-engineering.mjs +831 -0
- package/lib/observe-event.mjs +128 -0
- package/lib/plugin-metadata.mjs +432 -0
- package/lib/pm-marketplace.mjs +61 -0
- package/lib/pm-skills.mjs +1274 -0
- package/lib/resolve-home-dir.mjs +43 -0
- package/lib/skill-tiers.mjs +137 -0
- package/lib/unified-plugin.mjs +924 -0
- package/llms.txt +68 -0
- package/package.json +38 -15
- package/plugins/beginner.json +17 -6
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
- package/plugins/continuous-improvement/LICENSE +21 -0
- package/plugins/continuous-improvement/README.md +56 -0
- package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
- package/plugins/continuous-improvement/bin/mcp-server.mjs +886 -0
- package/plugins/continuous-improvement/bin/observe.mjs +148 -0
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
- package/plugins/continuous-improvement/commands/dashboard.md +56 -0
- package/plugins/continuous-improvement/commands/discipline.md +51 -0
- package/plugins/continuous-improvement/commands/harvest.md +76 -0
- package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
- package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
- package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
- package/plugins/continuous-improvement/commands/ralph.md +103 -0
- package/plugins/continuous-improvement/commands/release-train.md +81 -0
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
- package/plugins/continuous-improvement/commands/superpowers.md +153 -0
- package/plugins/continuous-improvement/commands/swarm.md +101 -0
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
- package/plugins/continuous-improvement/hooks/hooks.json +60 -0
- package/plugins/continuous-improvement/hooks/observe.sh +172 -0
- package/plugins/continuous-improvement/hooks/session.sh +106 -0
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
- package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +432 -0
- package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
- package/plugins/continuous-improvement/skills/README.md +34 -0
- package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +131 -0
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +155 -0
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +454 -0
- package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
- package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
- package/plugins/continuous-improvement/skills/superpowers/SKILL.md +212 -0
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
- package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +192 -0
- package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
- package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
- package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
- package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
- package/plugins/expert.json +34 -5
- package/skills/README.md +79 -0
- package/skills/deploy-receipt.md +131 -0
- package/skills/gateguard.md +155 -0
- package/skills/para-memory-files.md +108 -0
- package/skills/proceed-with-the-recommendation.md +454 -0
- package/skills/ralph.md +221 -0
- package/skills/safety-guard.md +76 -0
- package/skills/strategic-compact.md +104 -0
- package/skills/superpowers.md +212 -0
- package/skills/tdd-workflow.md +411 -0
- package/skills/token-budget-advisor.md +136 -0
- package/skills/verification-loop.md +192 -0
- package/skills/wild-risa-balance.md +191 -0
- package/skills/workspace-surface-audit.md +147 -0
- package/templates/planning-with-files/findings.md +8 -0
- package/templates/planning-with-files/progress.md +7 -0
- package/templates/planning-with-files/task_plan.md +23 -0
- package/templates/verify-ladder.example.json +47 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Hook Stats Reader
|
|
4
|
+
*
|
|
5
|
+
* Reads JSONL telemetry written by Claude Code Stop hooks (e.g. the
|
|
6
|
+
* three-section-close hook) from `<HOME>/.claude/hook-telemetry/*.jsonl`
|
|
7
|
+
* and prints per-hook aggregate counts over a recent time window.
|
|
8
|
+
*
|
|
9
|
+
* Each input line is JSON of the shape:
|
|
10
|
+
* {
|
|
11
|
+
* "ts": "<ISO timestamp>",
|
|
12
|
+
* "hook": "<hook-name>",
|
|
13
|
+
* "action": "pass" | "block" | "skip-short",
|
|
14
|
+
* "textLength": <number>,
|
|
15
|
+
* "missing": <string[]>,
|
|
16
|
+
* "durationMs": <number>
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* The reader is parse-tolerant: malformed lines are skipped silently so
|
|
20
|
+
* the CLI keeps working even when the hook has been crash-restarted mid
|
|
21
|
+
* append. The hook itself is fail-open; the reader matches that posture.
|
|
22
|
+
*
|
|
23
|
+
* Project hash:
|
|
24
|
+
* The hook keys telemetry files by a 12-char sha256 prefix of the
|
|
25
|
+
* *project* directory (the dir containing the transcript). For the CLI,
|
|
26
|
+
* the "project" is the current working directory: we normalize path
|
|
27
|
+
* separators to forward slash, lowercase on win32, then sha256 and
|
|
28
|
+
* slice the first 12 hex chars. Pass `--hash=<value>` to override.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* node bin/hook-stats.mjs [--hours=<n>] [--hash=<hash>] [--telemetry-dir=<path>]
|
|
32
|
+
* node bin/hook-stats.mjs --help
|
|
33
|
+
*
|
|
34
|
+
* Defaults:
|
|
35
|
+
* --hours 24
|
|
36
|
+
* --hash sha256(normalize(process.cwd())).slice(0, 12)
|
|
37
|
+
* --telemetry-dir <HOME>/.claude/hook-telemetry
|
|
38
|
+
*
|
|
39
|
+
* Exit codes:
|
|
40
|
+
* 0 — printed aggregate (or "no records" message)
|
|
41
|
+
* 1 — unexpected error
|
|
42
|
+
*/
|
|
43
|
+
import { createHash } from "node:crypto";
|
|
44
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
45
|
+
import { platform } from "node:os";
|
|
46
|
+
import { join } from "node:path";
|
|
47
|
+
import { argv, cwd, exit, stderr, stdout } from "node:process";
|
|
48
|
+
import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
|
|
49
|
+
const USAGE = `Usage: node bin/hook-stats.mjs [options]
|
|
50
|
+
|
|
51
|
+
Reads JSONL telemetry from <HOME>/.claude/hook-telemetry/*.jsonl and
|
|
52
|
+
prints per-hook aggregate counts over a recent time window.
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--hours=<n> Time window in hours (default: 24)
|
|
56
|
+
--hash=<hash> Only read this single <hash>.jsonl file
|
|
57
|
+
(default: sha256 of the current working directory,
|
|
58
|
+
normalized to forward slashes and lowercased on
|
|
59
|
+
Windows, then first 12 hex chars)
|
|
60
|
+
--telemetry-dir=<path> Override telemetry dir
|
|
61
|
+
(default: <HOME>/.claude/hook-telemetry)
|
|
62
|
+
--help Show this help and exit 0
|
|
63
|
+
|
|
64
|
+
Output:
|
|
65
|
+
One line per hook: "<hook> last <hours>h: pass=<n> block=<n>
|
|
66
|
+
skip-short=<n> total=<n> avg-duration=<X>ms".
|
|
67
|
+
If there are no records in the window: "no records in last <hours>h".
|
|
68
|
+
|
|
69
|
+
The reader is parse-tolerant: malformed JSONL lines are skipped silently.
|
|
70
|
+
`;
|
|
71
|
+
function parseArgs(rawArgs) {
|
|
72
|
+
const opts = {
|
|
73
|
+
hours: 24,
|
|
74
|
+
hash: null,
|
|
75
|
+
telemetryDir: null,
|
|
76
|
+
help: false,
|
|
77
|
+
};
|
|
78
|
+
for (const arg of rawArgs) {
|
|
79
|
+
if (arg === "--help" || arg === "-h") {
|
|
80
|
+
opts.help = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg.startsWith("--hours=")) {
|
|
84
|
+
const raw = arg.slice("--hours=".length);
|
|
85
|
+
const n = Number(raw);
|
|
86
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
87
|
+
throw new Error(`invalid --hours value: ${raw}`);
|
|
88
|
+
}
|
|
89
|
+
opts.hours = n;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (arg.startsWith("--hash=")) {
|
|
93
|
+
opts.hash = arg.slice("--hash=".length);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (arg.startsWith("--telemetry-dir=")) {
|
|
97
|
+
opts.telemetryDir = arg.slice("--telemetry-dir=".length);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
101
|
+
}
|
|
102
|
+
return opts;
|
|
103
|
+
}
|
|
104
|
+
export function projectHashForCwd(workingDir) {
|
|
105
|
+
let normalized = workingDir.split("\\").join("/");
|
|
106
|
+
if (platform() === "win32")
|
|
107
|
+
normalized = normalized.toLowerCase();
|
|
108
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
109
|
+
}
|
|
110
|
+
function listJsonlFiles(dir, hashFilter) {
|
|
111
|
+
let entries;
|
|
112
|
+
try {
|
|
113
|
+
entries = readdirSync(dir);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const out = [];
|
|
119
|
+
for (const name of entries) {
|
|
120
|
+
if (!name.endsWith(".jsonl"))
|
|
121
|
+
continue;
|
|
122
|
+
if (hashFilter !== null) {
|
|
123
|
+
if (name !== `${hashFilter}.jsonl`)
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const full = join(dir, name);
|
|
127
|
+
try {
|
|
128
|
+
const stat = statSync(full);
|
|
129
|
+
if (!stat.isFile())
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
out.push(full);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function safeJsonParse(line) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(line);
|
|
142
|
+
if (parsed &&
|
|
143
|
+
typeof parsed === "object" &&
|
|
144
|
+
typeof parsed.hook === "string" &&
|
|
145
|
+
typeof parsed.ts === "string" &&
|
|
146
|
+
typeof parsed.action === "string") {
|
|
147
|
+
return parsed;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export function aggregate(records, cutoffMs) {
|
|
156
|
+
const byHook = new Map();
|
|
157
|
+
for (const rec of records) {
|
|
158
|
+
const tsMs = Date.parse(rec.ts);
|
|
159
|
+
if (!Number.isFinite(tsMs))
|
|
160
|
+
continue;
|
|
161
|
+
if (tsMs < cutoffMs)
|
|
162
|
+
continue;
|
|
163
|
+
let agg = byHook.get(rec.hook);
|
|
164
|
+
if (!agg) {
|
|
165
|
+
agg = {
|
|
166
|
+
hook: rec.hook,
|
|
167
|
+
pass: 0,
|
|
168
|
+
block: 0,
|
|
169
|
+
skipShort: 0,
|
|
170
|
+
total: 0,
|
|
171
|
+
durationSum: 0,
|
|
172
|
+
durationCount: 0,
|
|
173
|
+
};
|
|
174
|
+
byHook.set(rec.hook, agg);
|
|
175
|
+
}
|
|
176
|
+
agg.total += 1;
|
|
177
|
+
if (rec.action === "pass")
|
|
178
|
+
agg.pass += 1;
|
|
179
|
+
else if (rec.action === "block")
|
|
180
|
+
agg.block += 1;
|
|
181
|
+
else if (rec.action === "skip-short")
|
|
182
|
+
agg.skipShort += 1;
|
|
183
|
+
if (typeof rec.durationMs === "number" && Number.isFinite(rec.durationMs)) {
|
|
184
|
+
agg.durationSum += rec.durationMs;
|
|
185
|
+
agg.durationCount += 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return Array.from(byHook.values()).sort((a, b) => a.hook.localeCompare(b.hook));
|
|
189
|
+
}
|
|
190
|
+
function formatAggregate(agg, hours) {
|
|
191
|
+
const avg = agg.durationCount > 0
|
|
192
|
+
? Math.round(agg.durationSum / agg.durationCount)
|
|
193
|
+
: 0;
|
|
194
|
+
return `${agg.hook} last ${hours}h: pass=${agg.pass} block=${agg.block} skip-short=${agg.skipShort} total=${agg.total} avg-duration=${avg}ms`;
|
|
195
|
+
}
|
|
196
|
+
function main() {
|
|
197
|
+
const opts = parseArgs(argv.slice(2));
|
|
198
|
+
if (opts.help) {
|
|
199
|
+
stdout.write(USAGE);
|
|
200
|
+
exit(0);
|
|
201
|
+
}
|
|
202
|
+
let telemetryDir;
|
|
203
|
+
if (opts.telemetryDir !== null) {
|
|
204
|
+
telemetryDir = opts.telemetryDir;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const home = resolveHomeDir();
|
|
208
|
+
if (!home) {
|
|
209
|
+
stderr.write("no telemetry dir resolved\n");
|
|
210
|
+
exit(0);
|
|
211
|
+
}
|
|
212
|
+
telemetryDir = join(home, ".claude", "hook-telemetry");
|
|
213
|
+
}
|
|
214
|
+
const hashFilter = opts.hash ?? projectHashForCwd(cwd());
|
|
215
|
+
// When the user did not pass --hash, we still read all files (per spec).
|
|
216
|
+
// Only restrict to a single file when --hash is explicitly set.
|
|
217
|
+
const useHashFilter = opts.hash !== null;
|
|
218
|
+
const files = listJsonlFiles(telemetryDir, useHashFilter ? hashFilter : null);
|
|
219
|
+
const records = [];
|
|
220
|
+
for (const file of files) {
|
|
221
|
+
let content;
|
|
222
|
+
try {
|
|
223
|
+
content = readFileSync(file, "utf8");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
229
|
+
const line = rawLine.trim();
|
|
230
|
+
if (!line)
|
|
231
|
+
continue;
|
|
232
|
+
const rec = safeJsonParse(line);
|
|
233
|
+
if (rec)
|
|
234
|
+
records.push(rec);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const cutoffMs = Date.now() - opts.hours * 3600 * 1000;
|
|
238
|
+
const aggregates = aggregate(records, cutoffMs);
|
|
239
|
+
if (aggregates.length === 0) {
|
|
240
|
+
stdout.write(`no records in last ${opts.hours}h\n`);
|
|
241
|
+
exit(0);
|
|
242
|
+
}
|
|
243
|
+
for (const agg of aggregates) {
|
|
244
|
+
stdout.write(formatAggregate(agg, opts.hours) + "\n");
|
|
245
|
+
}
|
|
246
|
+
exit(0);
|
|
247
|
+
}
|
|
248
|
+
const invokedDirectly = argv[1] !== undefined && import.meta.url.endsWith(argv[1].split("\\").join("/"));
|
|
249
|
+
if (invokedDirectly || argv[1]?.endsWith("hook-stats.mjs")) {
|
|
250
|
+
try {
|
|
251
|
+
main();
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
255
|
+
stderr.write(`${message}\n`);
|
|
256
|
+
exit(1);
|
|
257
|
+
}
|
|
258
|
+
}
|