claude-code-pilot 3.1.1 → 3.3.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/CHANGELOG.md +57 -0
- package/README.md +16 -11
- package/bin/install.js +127 -11
- package/manifest.json +20 -1
- package/package.json +4 -3
- package/src/agents/a11y-architect.md +141 -0
- package/src/agents/code-architect.md +71 -0
- package/src/agents/code-explorer.md +69 -0
- package/src/agents/code-simplifier.md +47 -0
- package/src/agents/comment-analyzer.md +45 -0
- package/src/agents/csharp-reviewer.md +101 -0
- package/src/agents/dart-build-resolver.md +201 -0
- package/src/agents/django-build-resolver.md +252 -0
- package/src/agents/django-reviewer.md +169 -0
- package/src/agents/fastapi-reviewer.md +79 -0
- package/src/agents/fsharp-reviewer.md +109 -0
- package/src/agents/pr-test-analyzer.md +45 -0
- package/src/agents/silent-failure-hunter.md +50 -0
- package/src/agents/swift-build-resolver.md +170 -0
- package/src/agents/swift-reviewer.md +116 -0
- package/src/agents/type-design-analyzer.md +41 -0
- package/src/available-rules/README.md +3 -1
- package/src/available-rules/dart/coding-style.md +159 -0
- package/src/available-rules/dart/hooks.md +66 -0
- package/src/available-rules/dart/patterns.md +261 -0
- package/src/available-rules/dart/security.md +135 -0
- package/src/available-rules/dart/testing.md +215 -0
- package/src/available-rules/web/coding-style.md +105 -0
- package/src/available-rules/web/design-quality.md +72 -0
- package/src/available-rules/web/hooks.md +129 -0
- package/src/available-rules/web/patterns.md +88 -0
- package/src/available-rules/web/performance.md +73 -0
- package/src/available-rules/web/security.md +66 -0
- package/src/available-rules/web/testing.md +64 -0
- package/src/commands/ccp/ai-integration-phase.md +36 -0
- package/src/commands/ccp/audit-fix.md +33 -0
- package/src/commands/ccp/code-review-fix.md +52 -0
- package/src/commands/ccp/cost-report.md +107 -0
- package/src/commands/ccp/eval-review.md +32 -0
- package/src/commands/ccp/extract_learnings.md +22 -0
- package/src/commands/ccp/import.md +37 -0
- package/src/commands/ccp/ingest-docs.md +42 -0
- package/src/commands/ccp/intel.md +179 -0
- package/src/commands/ccp/mvp-phase.md +45 -0
- package/src/commands/ccp/plan-prd.md +160 -0
- package/src/commands/ccp/plan-review-convergence.md +58 -0
- package/src/commands/ccp/pr-ecc.md +184 -0
- package/src/commands/ccp/scan.md +26 -0
- package/src/commands/ccp/security-scan.md +74 -0
- package/src/commands/ccp/sketch-wrap-up.md +31 -0
- package/src/commands/ccp/sketch.md +54 -0
- package/src/commands/ccp/spec-phase.md +62 -0
- package/src/commands/ccp/spike-wrap-up.md +31 -0
- package/src/commands/ccp/spike.md +51 -0
- package/src/commands/ccp/ultraplan-phase.md +33 -0
- package/src/hooks/ccp-bash-hook-dispatcher.js +96 -0
- package/src/hooks/ccp-context-monitor.js +23 -0
- package/src/hooks/ccp-doc-file-warning.js +93 -0
- package/src/hooks/ccp-pre-bash-dispatcher.js +24 -0
- package/src/hooks/ccp-read-injection-scanner.js +152 -0
- package/src/hooks/ccp-write-gateguard.js +868 -0
- package/src/hooks/kit-check-update.js +59 -7
- package/src/hooks/run-with-flags-shell.sh +1 -0
- package/src/hooks/run-with-flags.js +48 -1
- package/src/hooks/session-end.js +88 -1
- package/src/lib/hook-flags.js +14 -0
- package/src/lib/project-detect.js +0 -2
- package/src/lib/shell-substitution.js +499 -0
- package/src/pilot/references/agent-contracts.md +79 -0
- package/src/pilot/references/ai-evals.md +156 -0
- package/src/pilot/references/ai-frameworks.md +186 -0
- package/src/pilot/references/doc-conflict-engine.md +91 -0
- package/src/pilot/references/execute-mvp-tdd.md +81 -0
- package/src/pilot/references/gate-prompts.md +100 -0
- package/src/pilot/references/gates.md +70 -0
- package/src/pilot/references/mandatory-initial-read.md +2 -0
- package/src/pilot/references/mvp-concepts.md +49 -0
- package/src/pilot/references/planner-graphify-auto-update.md +67 -0
- package/src/pilot/references/planner-human-verify-mode.md +57 -0
- package/src/pilot/references/planner-mvp-mode.md +53 -0
- package/src/pilot/references/project-skills-discovery.md +19 -0
- package/src/pilot/references/revision-loop.md +97 -0
- package/src/pilot/references/skeleton-template.md +48 -0
- package/src/pilot/references/sketch-interactivity.md +41 -0
- package/src/pilot/references/sketch-theme-system.md +94 -0
- package/src/pilot/references/sketch-tooling.md +45 -0
- package/src/pilot/references/sketch-variant-patterns.md +81 -0
- package/src/pilot/references/spidr-splitting.md +69 -0
- package/src/pilot/references/thinking-models-debug.md +44 -0
- package/src/pilot/references/thinking-models-execution.md +50 -0
- package/src/pilot/references/thinking-models-planning.md +62 -0
- package/src/pilot/references/thinking-models-research.md +50 -0
- package/src/pilot/references/thinking-models-verification.md +55 -0
- package/src/pilot/references/user-story-template.md +58 -0
- package/src/pilot/references/verify-mvp-mode.md +85 -0
- package/src/pilot/references/worktree-path-safety.md +89 -0
- package/src/pilot/templates/AI-SPEC.md +246 -0
- package/src/pilot/templates/spec.md +307 -0
- package/src/pilot/workflows/ai-integration-phase.md +284 -0
- package/src/pilot/workflows/audit-fix.md +175 -0
- package/src/pilot/workflows/code-review-fix.md +497 -0
- package/src/pilot/workflows/eval-review.md +155 -0
- package/src/pilot/workflows/extract_learnings.md +242 -0
- package/src/pilot/workflows/help.md +5 -0
- package/src/pilot/workflows/import.md +246 -0
- package/src/pilot/workflows/ingest-docs.md +328 -0
- package/src/pilot/workflows/mvp-phase.md +199 -0
- package/src/pilot/workflows/plan-review-convergence.md +329 -0
- package/src/pilot/workflows/scan.md +102 -0
- package/src/pilot/workflows/sketch-wrap-up.md +285 -0
- package/src/pilot/workflows/sketch.md +360 -0
- package/src/pilot/workflows/spec-phase.md +262 -0
- package/src/pilot/workflows/spike-wrap-up.md +306 -0
- package/src/pilot/workflows/spike.md +452 -0
- package/src/pilot/workflows/ultraplan-phase.md +189 -0
- package/src/skills/accessibility/SKILL.md +146 -0
- package/src/skills/agent-architecture-audit/SKILL.md +256 -0
- package/src/skills/agent-eval/SKILL.md +145 -0
- package/src/skills/agent-harness-design/SKILL.md +73 -0
- package/src/skills/agent-introspection-debugging/SKILL.md +153 -0
- package/src/skills/android-clean-architecture/SKILL.md +339 -0
- package/src/skills/angular-developer/SKILL.md +154 -0
- package/src/skills/angular-developer/references/angular-animations.md +160 -0
- package/src/skills/angular-developer/references/angular-aria.md +410 -0
- package/src/skills/angular-developer/references/cli.md +86 -0
- package/src/skills/angular-developer/references/component-harnesses.md +59 -0
- package/src/skills/angular-developer/references/component-styling.md +91 -0
- package/src/skills/angular-developer/references/components.md +117 -0
- package/src/skills/angular-developer/references/creating-services.md +97 -0
- package/src/skills/angular-developer/references/data-resolvers.md +69 -0
- package/src/skills/angular-developer/references/define-routes.md +67 -0
- package/src/skills/angular-developer/references/defining-providers.md +72 -0
- package/src/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/src/skills/angular-developer/references/e2e-testing.md +56 -0
- package/src/skills/angular-developer/references/effects.md +83 -0
- package/src/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/src/skills/angular-developer/references/host-elements.md +80 -0
- package/src/skills/angular-developer/references/injection-context.md +63 -0
- package/src/skills/angular-developer/references/inputs.md +101 -0
- package/src/skills/angular-developer/references/linked-signal.md +59 -0
- package/src/skills/angular-developer/references/loading-strategies.md +61 -0
- package/src/skills/angular-developer/references/mcp.md +108 -0
- package/src/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/src/skills/angular-developer/references/outputs.md +86 -0
- package/src/skills/angular-developer/references/reactive-forms.md +122 -0
- package/src/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/src/skills/angular-developer/references/resource.md +77 -0
- package/src/skills/angular-developer/references/route-animations.md +56 -0
- package/src/skills/angular-developer/references/route-guards.md +52 -0
- package/src/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/src/skills/angular-developer/references/router-testing.md +87 -0
- package/src/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/src/skills/angular-developer/references/signal-forms.md +795 -0
- package/src/skills/angular-developer/references/signals-overview.md +94 -0
- package/src/skills/angular-developer/references/tailwind-css.md +69 -0
- package/src/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/src/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/src/skills/api-connector-builder/SKILL.md +120 -0
- package/src/skills/code-tour/SKILL.md +236 -0
- package/src/skills/compose-multiplatform-patterns/SKILL.md +299 -0
- package/src/skills/csharp-testing/SKILL.md +321 -0
- package/src/skills/dart-flutter-patterns/SKILL.md +563 -0
- package/src/skills/dashboard-builder/SKILL.md +108 -0
- package/src/skills/dotnet-patterns/SKILL.md +321 -0
- package/src/skills/error-handling/SKILL.md +376 -0
- package/src/skills/fastapi-patterns/SKILL.md +327 -0
- package/src/skills/flox-environments/SKILL.md +496 -0
- package/src/skills/frontend-design/SKILL.md +145 -0
- package/src/skills/frontend-slides/SKILL.md +184 -0
- package/src/skills/frontend-slides/STYLE_PRESETS.md +330 -0
- package/src/skills/fsharp-testing/SKILL.md +280 -0
- package/src/skills/gateguard/SKILL.md +121 -0
- package/src/skills/github-ops/SKILL.md +144 -0
- package/src/skills/hookify-rules/SKILL.md +128 -0
- package/src/skills/ios-icon-gen/SKILL.md +157 -0
- package/src/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
- package/src/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
- package/src/skills/knowledge-ops/SKILL.md +154 -0
- package/src/skills/liquid-glass-design/SKILL.md +279 -0
- package/src/skills/make-interfaces-feel-better/SKILL.md +151 -0
- package/src/skills/mysql-patterns/SKILL.md +412 -0
- package/src/skills/nestjs-patterns/SKILL.md +230 -0
- package/src/skills/plan-orchestrate/SKILL.md +220 -0
- package/src/skills/prisma-patterns/SKILL.md +371 -0
- package/src/skills/production-audit/SKILL.md +206 -0
- package/src/skills/security-bounty-hunter/SKILL.md +99 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/candidate-playbook.md +49 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/report.json +35 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/scenario.json +62 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/trace.json +45 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/verifier-result.json +35 -0
- package/src/skills/swift-actor-persistence/SKILL.md +143 -0
- package/src/skills/swift-protocol-di-testing/SKILL.md +190 -0
- package/src/skills/swiftui-patterns/SKILL.md +259 -0
- package/src/skills/terminal-ops/SKILL.md +109 -0
- package/src/skills/ui-demo/SKILL.md +465 -0
- package/src/skills/vite-patterns/SKILL.md +449 -0
- package/src/skills/windows-desktop-e2e/SKILL.md +887 -0
|
@@ -41,17 +41,69 @@ const child = spawn(process.execPath, ['-e', `
|
|
|
41
41
|
`], { stdio: 'ignore', windowsHide: true, detached: true });
|
|
42
42
|
child.unref();
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// Stale-hook detector: scan installed .claude/hooks/*.{js,sh} for ccp-hook-version
|
|
45
|
+
// headers, compare against the installed pilot VERSION. Advisory only; never blocks.
|
|
46
|
+
//
|
|
47
|
+
// Regex matches both bash ('# ') and JS ('// ') comment styles to avoid the
|
|
48
|
+
// upstream bug where bash hooks landed in the "unknown" branch on every session.
|
|
49
|
+
// The negative case (no comment prefix) is intentional to prevent false positives
|
|
50
|
+
// on bare version strings inside file content.
|
|
51
|
+
function findStaleHooks(hooksDir, installedVersion) {
|
|
52
|
+
if (!hooksDir || !fs.existsSync(hooksDir)) return [];
|
|
53
|
+
const VERSION_RE = /(?:\/\/|#) ccp-hook-version:\s*(.+)/;
|
|
54
|
+
const stale = [];
|
|
55
|
+
try {
|
|
56
|
+
for (const file of fs.readdirSync(hooksDir)) {
|
|
57
|
+
if (!file.endsWith('.js') && !file.endsWith('.sh')) continue;
|
|
58
|
+
const p = path.join(hooksDir, file);
|
|
59
|
+
let content;
|
|
60
|
+
try { content = fs.readFileSync(p, 'utf8'); } catch { continue; }
|
|
61
|
+
const m = content.match(VERSION_RE);
|
|
62
|
+
if (!m) continue; // unversioned hooks are not flagged
|
|
63
|
+
const hookVersion = m[1].trim();
|
|
64
|
+
if (hookVersion.includes('{{')) continue; // unsubstituted placeholder -- skip
|
|
65
|
+
if (hookVersion !== installedVersion) {
|
|
66
|
+
stale.push({ file, hookVersion, installedVersion });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// detector failure is silent; never break SessionStart
|
|
71
|
+
}
|
|
72
|
+
return stale;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Show cached result if fresh + stale-hook advisory (combined into one notice)
|
|
45
76
|
try {
|
|
77
|
+
let installedVersion = '0.0.0';
|
|
78
|
+
try { installedVersion = fs.readFileSync(versionFile, 'utf8').trim(); } catch {}
|
|
79
|
+
const hooksDir = path.join(path.dirname(path.dirname(versionFile)), 'hooks');
|
|
80
|
+
let stale = [];
|
|
81
|
+
try { stale = findStaleHooks(hooksDir, installedVersion); } catch {}
|
|
82
|
+
|
|
83
|
+
let updateMsg = null;
|
|
46
84
|
if (fs.existsSync(cacheFile)) {
|
|
47
85
|
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
48
86
|
if ((Math.floor(Date.now() / 1000) - (cached.checked || 0)) < 86400 && cached.update_available) {
|
|
49
|
-
|
|
50
|
-
hookSpecificOutput: {
|
|
51
|
-
hookEventName: 'SessionStart',
|
|
52
|
-
additionalContext: `Pilot update available: ${cached.installed} -> ${cached.latest}. Run /ccp:update to upgrade.`
|
|
53
|
-
}
|
|
54
|
-
}));
|
|
87
|
+
updateMsg = `Pilot update available: ${cached.installed} -> ${cached.latest}. Run /ccp:update to upgrade.`;
|
|
55
88
|
}
|
|
56
89
|
}
|
|
90
|
+
|
|
91
|
+
let staleMsg = null;
|
|
92
|
+
if (stale.length > 0) {
|
|
93
|
+
staleMsg = `Stale hooks detected (run /ccp:update): ${stale.map(s => s.file).join(', ')}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (updateMsg || staleMsg) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
if (staleMsg) lines.push(staleMsg);
|
|
99
|
+
if (updateMsg) lines.push(updateMsg);
|
|
100
|
+
process.stdout.write(JSON.stringify({
|
|
101
|
+
hookSpecificOutput: {
|
|
102
|
+
hookEventName: 'SessionStart',
|
|
103
|
+
additionalContext: lines.join('\n')
|
|
104
|
+
}
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
57
107
|
} catch {}
|
|
108
|
+
|
|
109
|
+
module.exports = { findStaleHooks };
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
12
13
|
const path = require('path');
|
|
13
14
|
const { spawnSync } = require('child_process');
|
|
14
|
-
const { isHookEnabled } = require('../lib/hook-flags');
|
|
15
|
+
const { isHookEnabled, isProfilingEnabled } = require('../lib/hook-flags');
|
|
15
16
|
|
|
16
17
|
const MAX_STDIN = 1024 * 1024;
|
|
17
18
|
|
|
@@ -85,6 +86,45 @@ function getPluginRoot() {
|
|
|
85
86
|
return path.resolve(__dirname, '..', '..');
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Append a single JSONL row to the timings sidecar.
|
|
91
|
+
*
|
|
92
|
+
* Wrapped in try/catch so timing failures NEVER block the hook itself.
|
|
93
|
+
* File path: {CCP_HOOK_TIMINGS_DIR || os.tmpdir()}/ccp-hook-timings-{sessionId}.json
|
|
94
|
+
*/
|
|
95
|
+
function recordTiming(hookId, durationMs) {
|
|
96
|
+
try {
|
|
97
|
+
const timingsDir = (process.env.CCP_HOOK_TIMINGS_DIR && process.env.CCP_HOOK_TIMINGS_DIR.trim())
|
|
98
|
+
? process.env.CCP_HOOK_TIMINGS_DIR
|
|
99
|
+
: os.tmpdir();
|
|
100
|
+
const sessionId = (process.env.CLAUDE_SESSION_ID && process.env.CLAUDE_SESSION_ID.trim())
|
|
101
|
+
? process.env.CLAUDE_SESSION_ID
|
|
102
|
+
: 'unknown';
|
|
103
|
+
const sidecarPath = path.join(timingsDir, `ccp-hook-timings-${sessionId}.json`);
|
|
104
|
+
const row = JSON.stringify({
|
|
105
|
+
hookId: String(hookId || ''),
|
|
106
|
+
durationMs: Number(durationMs) || 0,
|
|
107
|
+
ts: Date.now()
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
fs.appendFileSync(sidecarPath, row + '\n', { flag: 'a' });
|
|
111
|
+
} catch {
|
|
112
|
+
// sidecar write failed -- swallow silently; never block the hook
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// outer guard: even path resolution must not break the dispatcher
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Measure wall-clock duration in milliseconds using the monotonic hrtime
|
|
121
|
+
* clock (resists wall-clock skew/NTP adjustments).
|
|
122
|
+
*/
|
|
123
|
+
function elapsedMs(startNs) {
|
|
124
|
+
const deltaNs = process.hrtime.bigint() - startNs;
|
|
125
|
+
return Number(deltaNs) / 1e6;
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
async function main() {
|
|
89
129
|
const [, , hookId, relScriptPath, profilesCsv] = process.argv;
|
|
90
130
|
const { raw, truncated } = await readStdinRaw();
|
|
@@ -135,11 +175,16 @@ async function main() {
|
|
|
135
175
|
}
|
|
136
176
|
}
|
|
137
177
|
|
|
178
|
+
const profileOn = isProfilingEnabled();
|
|
179
|
+
|
|
138
180
|
if (hookModule && typeof hookModule.run === 'function') {
|
|
181
|
+
const startNs = profileOn ? process.hrtime.bigint() : null;
|
|
139
182
|
try {
|
|
140
183
|
const output = hookModule.run(raw, { truncated, maxStdin: MAX_STDIN });
|
|
184
|
+
if (profileOn) recordTiming(hookId, elapsedMs(startNs));
|
|
141
185
|
process.exit(emitHookResult(raw, output));
|
|
142
186
|
} catch (runErr) {
|
|
187
|
+
if (profileOn) recordTiming(hookId, elapsedMs(startNs));
|
|
143
188
|
process.stderr.write(`[Hook] run() error for ${hookId}: ${runErr.message}\n`);
|
|
144
189
|
process.stdout.write(raw);
|
|
145
190
|
}
|
|
@@ -147,6 +192,7 @@ async function main() {
|
|
|
147
192
|
}
|
|
148
193
|
|
|
149
194
|
// Legacy path: spawn a child Node process for hooks without run() export
|
|
195
|
+
const legacyStartNs = profileOn ? process.hrtime.bigint() : null;
|
|
150
196
|
const result = spawnSync(process.execPath, [scriptPath], {
|
|
151
197
|
input: raw,
|
|
152
198
|
encoding: 'utf8',
|
|
@@ -158,6 +204,7 @@ async function main() {
|
|
|
158
204
|
cwd: process.cwd(),
|
|
159
205
|
timeout: 30000
|
|
160
206
|
});
|
|
207
|
+
if (profileOn) recordTiming(hookId, elapsedMs(legacyStartNs));
|
|
161
208
|
|
|
162
209
|
writeLegacySpawnOutput(raw, result);
|
|
163
210
|
if (result.stderr) process.stderr.write(result.stderr);
|
package/src/hooks/session-end.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
14
15
|
const {
|
|
15
16
|
getSessionsDir,
|
|
16
17
|
getDateString,
|
|
@@ -25,6 +26,52 @@ const {
|
|
|
25
26
|
log
|
|
26
27
|
} = require('../lib/utils');
|
|
27
28
|
|
|
29
|
+
// Cap sidecar reads at 1 MB to prevent memory blow-up if the file grew
|
|
30
|
+
// unexpectedly large. Mitigation for T-21-02 in the threat model.
|
|
31
|
+
const MAX_TIMINGS_SIDECAR_BYTES = 1024 * 1024;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build a "## Hook Timings" markdown section by parsing a JSONL sidecar.
|
|
35
|
+
* Returns null if the sidecar is missing/empty/unreadable. Wrapped in
|
|
36
|
+
* try/catch by callers; this helper is best-effort and never throws.
|
|
37
|
+
*/
|
|
38
|
+
function buildHookTimingsSection(sidecarPath) {
|
|
39
|
+
try {
|
|
40
|
+
if (!sidecarPath || !fs.existsSync(sidecarPath)) return null;
|
|
41
|
+
const stat = fs.statSync(sidecarPath);
|
|
42
|
+
if (stat.size === 0 || stat.size > MAX_TIMINGS_SIDECAR_BYTES) return null;
|
|
43
|
+
const raw = fs.readFileSync(sidecarPath, 'utf8');
|
|
44
|
+
const grouped = new Map();
|
|
45
|
+
for (const line of raw.split('\n')) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (!trimmed) continue;
|
|
48
|
+
let row;
|
|
49
|
+
try { row = JSON.parse(trimmed); } catch { continue; }
|
|
50
|
+
if (!row || typeof row.hookId !== 'string') continue;
|
|
51
|
+
const id = row.hookId;
|
|
52
|
+
const dur = Number(row.durationMs) || 0;
|
|
53
|
+
const prev = grouped.get(id) || { calls: 0, totalMs: 0 };
|
|
54
|
+
prev.calls += 1;
|
|
55
|
+
prev.totalMs += dur;
|
|
56
|
+
grouped.set(id, prev);
|
|
57
|
+
}
|
|
58
|
+
if (grouped.size === 0) return null;
|
|
59
|
+
const rows = Array.from(grouped.entries())
|
|
60
|
+
.sort((a, b) => b[1].totalMs - a[1].totalMs)
|
|
61
|
+
.map(([id, s]) => `| ${id} | ${s.calls} | ${s.totalMs.toFixed(2)} | ${(s.totalMs / s.calls).toFixed(2)} |`);
|
|
62
|
+
return [
|
|
63
|
+
'## Hook Timings',
|
|
64
|
+
'',
|
|
65
|
+
'| Hook | Calls | Total (ms) | Avg (ms) |',
|
|
66
|
+
'|------|-------|------------|----------|',
|
|
67
|
+
...rows,
|
|
68
|
+
''
|
|
69
|
+
].join('\n');
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
28
75
|
const SUMMARY_START_MARKER = '<!-- ECC:SUMMARY:START -->';
|
|
29
76
|
const SUMMARY_END_MARKER = '<!-- ECC:SUMMARY:END -->';
|
|
30
77
|
const SESSION_SEPARATOR = '\n---\n';
|
|
@@ -178,11 +225,13 @@ function mergeSessionHeader(content, today, currentTime, metadata) {
|
|
|
178
225
|
}
|
|
179
226
|
|
|
180
227
|
async function main() {
|
|
181
|
-
// Parse stdin JSON to get transcript_path
|
|
228
|
+
// Parse stdin JSON to get transcript_path + session_id
|
|
182
229
|
let transcriptPath = null;
|
|
230
|
+
let sessionIdFromStdin = null;
|
|
183
231
|
try {
|
|
184
232
|
const input = JSON.parse(stdinData);
|
|
185
233
|
transcriptPath = input.transcript_path;
|
|
234
|
+
sessionIdFromStdin = input.session_id;
|
|
186
235
|
} catch {
|
|
187
236
|
// Fallback: try env var for backwards compatibility
|
|
188
237
|
transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
@@ -260,6 +309,44 @@ async function main() {
|
|
|
260
309
|
log(`[SessionEnd] Created session file: ${sessionFile}`);
|
|
261
310
|
}
|
|
262
311
|
|
|
312
|
+
// Hook Timings summary -- best-effort. If a sidecar exists, summarise it
|
|
313
|
+
// into a "## Hook Timings" markdown section appended to the session file,
|
|
314
|
+
// then delete the sidecar. Failures are silent; never block session-end.
|
|
315
|
+
try {
|
|
316
|
+
const sessionId = (sessionIdFromStdin && String(sessionIdFromStdin).trim())
|
|
317
|
+
? String(sessionIdFromStdin).trim()
|
|
318
|
+
: (process.env.CLAUDE_SESSION_ID && process.env.CLAUDE_SESSION_ID.trim()
|
|
319
|
+
? process.env.CLAUDE_SESSION_ID.trim()
|
|
320
|
+
: null);
|
|
321
|
+
if (sessionId) {
|
|
322
|
+
const timingsDir = (process.env.CCP_HOOK_TIMINGS_DIR && process.env.CCP_HOOK_TIMINGS_DIR.trim())
|
|
323
|
+
? process.env.CCP_HOOK_TIMINGS_DIR
|
|
324
|
+
: os.tmpdir();
|
|
325
|
+
const sidecarPath = path.join(timingsDir, `ccp-hook-timings-${sessionId}.json`);
|
|
326
|
+
const timingSection = buildHookTimingsSection(sidecarPath);
|
|
327
|
+
if (timingSection && fs.existsSync(sessionFile)) {
|
|
328
|
+
try {
|
|
329
|
+
const existing = readFile(sessionFile) || '';
|
|
330
|
+
// If a previous Hook Timings section exists, replace it; otherwise append.
|
|
331
|
+
let updated;
|
|
332
|
+
if (existing.includes('## Hook Timings')) {
|
|
333
|
+
updated = existing.replace(/## Hook Timings[\s\S]*?(?=\n## |\n# |$)/, timingSection + '\n');
|
|
334
|
+
} else {
|
|
335
|
+
updated = existing.endsWith('\n')
|
|
336
|
+
? `${existing}\n${timingSection}\n`
|
|
337
|
+
: `${existing}\n\n${timingSection}\n`;
|
|
338
|
+
}
|
|
339
|
+
writeFile(sessionFile, updated);
|
|
340
|
+
} catch {
|
|
341
|
+
// ignore: never block on timing summary write failure
|
|
342
|
+
}
|
|
343
|
+
try { fs.unlinkSync(sidecarPath); } catch { /* ignore */ }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
// outer guard: timing summary must not affect session-end success
|
|
348
|
+
}
|
|
349
|
+
|
|
263
350
|
process.exit(0);
|
|
264
351
|
}
|
|
265
352
|
|
package/src/lib/hook-flags.js
CHANGED
|
@@ -24,6 +24,19 @@ function getHookProfile() {
|
|
|
24
24
|
return VALID_PROFILES.has(raw) ? raw : 'standard';
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Boolean toggle for per-hook timing instrumentation.
|
|
29
|
+
*
|
|
30
|
+
* Distinct from getHookProfile() (string profile selector). The timing toggle
|
|
31
|
+
* is read from CCP_HOOK_PROFILE === '1' (or ECC_HOOK_PROFILE === '1' for
|
|
32
|
+
* back-compat). Other values like 'standard' / 'minimal' / 'strict' do NOT
|
|
33
|
+
* enable timing — those are profile names handled by getHookProfile().
|
|
34
|
+
*/
|
|
35
|
+
function isProfilingEnabled() {
|
|
36
|
+
const raw = String(process.env.CCP_HOOK_PROFILE || process.env.ECC_HOOK_PROFILE || '').trim();
|
|
37
|
+
return raw === '1';
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
function getDisabledHookIds() {
|
|
28
41
|
const raw = String(process.env.CCP_DISABLED_HOOKS || process.env.ECC_DISABLED_HOOKS || '');
|
|
29
42
|
if (!raw.trim()) return new Set();
|
|
@@ -72,6 +85,7 @@ module.exports = {
|
|
|
72
85
|
VALID_PROFILES,
|
|
73
86
|
normalizeId,
|
|
74
87
|
getHookProfile,
|
|
88
|
+
isProfilingEnabled,
|
|
75
89
|
getDisabledHookIds,
|
|
76
90
|
parseProfiles,
|
|
77
91
|
isHookEnabled,
|