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.
Files changed (198) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +16 -11
  3. package/bin/install.js +127 -11
  4. package/manifest.json +20 -1
  5. package/package.json +4 -3
  6. package/src/agents/a11y-architect.md +141 -0
  7. package/src/agents/code-architect.md +71 -0
  8. package/src/agents/code-explorer.md +69 -0
  9. package/src/agents/code-simplifier.md +47 -0
  10. package/src/agents/comment-analyzer.md +45 -0
  11. package/src/agents/csharp-reviewer.md +101 -0
  12. package/src/agents/dart-build-resolver.md +201 -0
  13. package/src/agents/django-build-resolver.md +252 -0
  14. package/src/agents/django-reviewer.md +169 -0
  15. package/src/agents/fastapi-reviewer.md +79 -0
  16. package/src/agents/fsharp-reviewer.md +109 -0
  17. package/src/agents/pr-test-analyzer.md +45 -0
  18. package/src/agents/silent-failure-hunter.md +50 -0
  19. package/src/agents/swift-build-resolver.md +170 -0
  20. package/src/agents/swift-reviewer.md +116 -0
  21. package/src/agents/type-design-analyzer.md +41 -0
  22. package/src/available-rules/README.md +3 -1
  23. package/src/available-rules/dart/coding-style.md +159 -0
  24. package/src/available-rules/dart/hooks.md +66 -0
  25. package/src/available-rules/dart/patterns.md +261 -0
  26. package/src/available-rules/dart/security.md +135 -0
  27. package/src/available-rules/dart/testing.md +215 -0
  28. package/src/available-rules/web/coding-style.md +105 -0
  29. package/src/available-rules/web/design-quality.md +72 -0
  30. package/src/available-rules/web/hooks.md +129 -0
  31. package/src/available-rules/web/patterns.md +88 -0
  32. package/src/available-rules/web/performance.md +73 -0
  33. package/src/available-rules/web/security.md +66 -0
  34. package/src/available-rules/web/testing.md +64 -0
  35. package/src/commands/ccp/ai-integration-phase.md +36 -0
  36. package/src/commands/ccp/audit-fix.md +33 -0
  37. package/src/commands/ccp/code-review-fix.md +52 -0
  38. package/src/commands/ccp/cost-report.md +107 -0
  39. package/src/commands/ccp/eval-review.md +32 -0
  40. package/src/commands/ccp/extract_learnings.md +22 -0
  41. package/src/commands/ccp/import.md +37 -0
  42. package/src/commands/ccp/ingest-docs.md +42 -0
  43. package/src/commands/ccp/intel.md +179 -0
  44. package/src/commands/ccp/mvp-phase.md +45 -0
  45. package/src/commands/ccp/plan-prd.md +160 -0
  46. package/src/commands/ccp/plan-review-convergence.md +58 -0
  47. package/src/commands/ccp/pr-ecc.md +184 -0
  48. package/src/commands/ccp/scan.md +26 -0
  49. package/src/commands/ccp/security-scan.md +74 -0
  50. package/src/commands/ccp/sketch-wrap-up.md +31 -0
  51. package/src/commands/ccp/sketch.md +54 -0
  52. package/src/commands/ccp/spec-phase.md +62 -0
  53. package/src/commands/ccp/spike-wrap-up.md +31 -0
  54. package/src/commands/ccp/spike.md +51 -0
  55. package/src/commands/ccp/ultraplan-phase.md +33 -0
  56. package/src/hooks/ccp-bash-hook-dispatcher.js +96 -0
  57. package/src/hooks/ccp-context-monitor.js +23 -0
  58. package/src/hooks/ccp-doc-file-warning.js +93 -0
  59. package/src/hooks/ccp-pre-bash-dispatcher.js +24 -0
  60. package/src/hooks/ccp-read-injection-scanner.js +152 -0
  61. package/src/hooks/ccp-write-gateguard.js +868 -0
  62. package/src/hooks/kit-check-update.js +59 -7
  63. package/src/hooks/run-with-flags-shell.sh +1 -0
  64. package/src/hooks/run-with-flags.js +48 -1
  65. package/src/hooks/session-end.js +88 -1
  66. package/src/lib/hook-flags.js +14 -0
  67. package/src/lib/project-detect.js +0 -2
  68. package/src/lib/shell-substitution.js +499 -0
  69. package/src/pilot/references/agent-contracts.md +79 -0
  70. package/src/pilot/references/ai-evals.md +156 -0
  71. package/src/pilot/references/ai-frameworks.md +186 -0
  72. package/src/pilot/references/doc-conflict-engine.md +91 -0
  73. package/src/pilot/references/execute-mvp-tdd.md +81 -0
  74. package/src/pilot/references/gate-prompts.md +100 -0
  75. package/src/pilot/references/gates.md +70 -0
  76. package/src/pilot/references/mandatory-initial-read.md +2 -0
  77. package/src/pilot/references/mvp-concepts.md +49 -0
  78. package/src/pilot/references/planner-graphify-auto-update.md +67 -0
  79. package/src/pilot/references/planner-human-verify-mode.md +57 -0
  80. package/src/pilot/references/planner-mvp-mode.md +53 -0
  81. package/src/pilot/references/project-skills-discovery.md +19 -0
  82. package/src/pilot/references/revision-loop.md +97 -0
  83. package/src/pilot/references/skeleton-template.md +48 -0
  84. package/src/pilot/references/sketch-interactivity.md +41 -0
  85. package/src/pilot/references/sketch-theme-system.md +94 -0
  86. package/src/pilot/references/sketch-tooling.md +45 -0
  87. package/src/pilot/references/sketch-variant-patterns.md +81 -0
  88. package/src/pilot/references/spidr-splitting.md +69 -0
  89. package/src/pilot/references/thinking-models-debug.md +44 -0
  90. package/src/pilot/references/thinking-models-execution.md +50 -0
  91. package/src/pilot/references/thinking-models-planning.md +62 -0
  92. package/src/pilot/references/thinking-models-research.md +50 -0
  93. package/src/pilot/references/thinking-models-verification.md +55 -0
  94. package/src/pilot/references/user-story-template.md +58 -0
  95. package/src/pilot/references/verify-mvp-mode.md +85 -0
  96. package/src/pilot/references/worktree-path-safety.md +89 -0
  97. package/src/pilot/templates/AI-SPEC.md +246 -0
  98. package/src/pilot/templates/spec.md +307 -0
  99. package/src/pilot/workflows/ai-integration-phase.md +284 -0
  100. package/src/pilot/workflows/audit-fix.md +175 -0
  101. package/src/pilot/workflows/code-review-fix.md +497 -0
  102. package/src/pilot/workflows/eval-review.md +155 -0
  103. package/src/pilot/workflows/extract_learnings.md +242 -0
  104. package/src/pilot/workflows/help.md +5 -0
  105. package/src/pilot/workflows/import.md +246 -0
  106. package/src/pilot/workflows/ingest-docs.md +328 -0
  107. package/src/pilot/workflows/mvp-phase.md +199 -0
  108. package/src/pilot/workflows/plan-review-convergence.md +329 -0
  109. package/src/pilot/workflows/scan.md +102 -0
  110. package/src/pilot/workflows/sketch-wrap-up.md +285 -0
  111. package/src/pilot/workflows/sketch.md +360 -0
  112. package/src/pilot/workflows/spec-phase.md +262 -0
  113. package/src/pilot/workflows/spike-wrap-up.md +306 -0
  114. package/src/pilot/workflows/spike.md +452 -0
  115. package/src/pilot/workflows/ultraplan-phase.md +189 -0
  116. package/src/skills/accessibility/SKILL.md +146 -0
  117. package/src/skills/agent-architecture-audit/SKILL.md +256 -0
  118. package/src/skills/agent-eval/SKILL.md +145 -0
  119. package/src/skills/agent-harness-design/SKILL.md +73 -0
  120. package/src/skills/agent-introspection-debugging/SKILL.md +153 -0
  121. package/src/skills/android-clean-architecture/SKILL.md +339 -0
  122. package/src/skills/angular-developer/SKILL.md +154 -0
  123. package/src/skills/angular-developer/references/angular-animations.md +160 -0
  124. package/src/skills/angular-developer/references/angular-aria.md +410 -0
  125. package/src/skills/angular-developer/references/cli.md +86 -0
  126. package/src/skills/angular-developer/references/component-harnesses.md +59 -0
  127. package/src/skills/angular-developer/references/component-styling.md +91 -0
  128. package/src/skills/angular-developer/references/components.md +117 -0
  129. package/src/skills/angular-developer/references/creating-services.md +97 -0
  130. package/src/skills/angular-developer/references/data-resolvers.md +69 -0
  131. package/src/skills/angular-developer/references/define-routes.md +67 -0
  132. package/src/skills/angular-developer/references/defining-providers.md +72 -0
  133. package/src/skills/angular-developer/references/di-fundamentals.md +120 -0
  134. package/src/skills/angular-developer/references/e2e-testing.md +56 -0
  135. package/src/skills/angular-developer/references/effects.md +83 -0
  136. package/src/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  137. package/src/skills/angular-developer/references/host-elements.md +80 -0
  138. package/src/skills/angular-developer/references/injection-context.md +63 -0
  139. package/src/skills/angular-developer/references/inputs.md +101 -0
  140. package/src/skills/angular-developer/references/linked-signal.md +59 -0
  141. package/src/skills/angular-developer/references/loading-strategies.md +61 -0
  142. package/src/skills/angular-developer/references/mcp.md +108 -0
  143. package/src/skills/angular-developer/references/navigate-to-routes.md +69 -0
  144. package/src/skills/angular-developer/references/outputs.md +86 -0
  145. package/src/skills/angular-developer/references/reactive-forms.md +122 -0
  146. package/src/skills/angular-developer/references/rendering-strategies.md +44 -0
  147. package/src/skills/angular-developer/references/resource.md +77 -0
  148. package/src/skills/angular-developer/references/route-animations.md +56 -0
  149. package/src/skills/angular-developer/references/route-guards.md +52 -0
  150. package/src/skills/angular-developer/references/router-lifecycle.md +45 -0
  151. package/src/skills/angular-developer/references/router-testing.md +87 -0
  152. package/src/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  153. package/src/skills/angular-developer/references/signal-forms.md +795 -0
  154. package/src/skills/angular-developer/references/signals-overview.md +94 -0
  155. package/src/skills/angular-developer/references/tailwind-css.md +69 -0
  156. package/src/skills/angular-developer/references/template-driven-forms.md +114 -0
  157. package/src/skills/angular-developer/references/testing-fundamentals.md +65 -0
  158. package/src/skills/api-connector-builder/SKILL.md +120 -0
  159. package/src/skills/code-tour/SKILL.md +236 -0
  160. package/src/skills/compose-multiplatform-patterns/SKILL.md +299 -0
  161. package/src/skills/csharp-testing/SKILL.md +321 -0
  162. package/src/skills/dart-flutter-patterns/SKILL.md +563 -0
  163. package/src/skills/dashboard-builder/SKILL.md +108 -0
  164. package/src/skills/dotnet-patterns/SKILL.md +321 -0
  165. package/src/skills/error-handling/SKILL.md +376 -0
  166. package/src/skills/fastapi-patterns/SKILL.md +327 -0
  167. package/src/skills/flox-environments/SKILL.md +496 -0
  168. package/src/skills/frontend-design/SKILL.md +145 -0
  169. package/src/skills/frontend-slides/SKILL.md +184 -0
  170. package/src/skills/frontend-slides/STYLE_PRESETS.md +330 -0
  171. package/src/skills/fsharp-testing/SKILL.md +280 -0
  172. package/src/skills/gateguard/SKILL.md +121 -0
  173. package/src/skills/github-ops/SKILL.md +144 -0
  174. package/src/skills/hookify-rules/SKILL.md +128 -0
  175. package/src/skills/ios-icon-gen/SKILL.md +157 -0
  176. package/src/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
  177. package/src/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
  178. package/src/skills/knowledge-ops/SKILL.md +154 -0
  179. package/src/skills/liquid-glass-design/SKILL.md +279 -0
  180. package/src/skills/make-interfaces-feel-better/SKILL.md +151 -0
  181. package/src/skills/mysql-patterns/SKILL.md +412 -0
  182. package/src/skills/nestjs-patterns/SKILL.md +230 -0
  183. package/src/skills/plan-orchestrate/SKILL.md +220 -0
  184. package/src/skills/prisma-patterns/SKILL.md +371 -0
  185. package/src/skills/production-audit/SKILL.md +206 -0
  186. package/src/skills/security-bounty-hunter/SKILL.md +99 -0
  187. package/src/skills/security-scan/references/agentshield-policy-exception/candidate-playbook.md +49 -0
  188. package/src/skills/security-scan/references/agentshield-policy-exception/report.json +35 -0
  189. package/src/skills/security-scan/references/agentshield-policy-exception/scenario.json +62 -0
  190. package/src/skills/security-scan/references/agentshield-policy-exception/trace.json +45 -0
  191. package/src/skills/security-scan/references/agentshield-policy-exception/verifier-result.json +35 -0
  192. package/src/skills/swift-actor-persistence/SKILL.md +143 -0
  193. package/src/skills/swift-protocol-di-testing/SKILL.md +190 -0
  194. package/src/skills/swiftui-patterns/SKILL.md +259 -0
  195. package/src/skills/terminal-ops/SKILL.md +109 -0
  196. package/src/skills/ui-demo/SKILL.md +465 -0
  197. package/src/skills/vite-patterns/SKILL.md +449 -0
  198. 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
- // Show cached result if fresh
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
- process.stdout.write(JSON.stringify({
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 };
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # ccp-hook-version: {{CCP_VERSION}}
2
3
  set -euo pipefail
3
4
 
4
5
  HOOK_ID="${1:-}"
@@ -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);
@@ -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
 
@@ -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,
@@ -3,8 +3,6 @@
3
3
  *
4
4
  * Cross-platform (Windows, macOS, Linux) project type detection
5
5
  * by inspecting files in the working directory.
6
- *
7
- * Resolves: https://github.com/affaan-m/everything-claude-code/issues/293
8
6
  */
9
7
 
10
8
  const fs = require('fs');