claude-code-pilot 2.0.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 (257) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/bin/install.js +431 -0
  4. package/docs/agent-guides/architecture.md +107 -0
  5. package/ecc/agents/architect.md +211 -0
  6. package/ecc/agents/code-reviewer.md +237 -0
  7. package/ecc/agents/doc-updater.md +107 -0
  8. package/ecc/agents/e2e-runner.md +107 -0
  9. package/ecc/agents/security-reviewer.md +108 -0
  10. package/ecc/agents/tdd-guide.md +91 -0
  11. package/ecc/commands/checkpoint.md +74 -0
  12. package/ecc/commands/evolve.md +178 -0
  13. package/ecc/commands/learn.md +70 -0
  14. package/ecc/commands/model-route.md +26 -0
  15. package/ecc/commands/quality-gate.md +29 -0
  16. package/ecc/commands/resume-session.md +155 -0
  17. package/ecc/commands/save-session.md +275 -0
  18. package/ecc/commands/sessions.md +305 -0
  19. package/ecc/commands/verify.md +59 -0
  20. package/ecc/contexts/dev.md +20 -0
  21. package/ecc/contexts/research.md +26 -0
  22. package/ecc/contexts/review.md +22 -0
  23. package/ecc/examples/CLAUDE.md +100 -0
  24. package/ecc/examples/django-api-CLAUDE.md +308 -0
  25. package/ecc/examples/go-microservice-CLAUDE.md +267 -0
  26. package/ecc/examples/rust-api-CLAUDE.md +285 -0
  27. package/ecc/examples/saas-nextjs-CLAUDE.md +166 -0
  28. package/ecc/examples/user-CLAUDE.md +109 -0
  29. package/ecc/rules/common/agents.md +49 -0
  30. package/ecc/rules/common/coding-style.md +48 -0
  31. package/ecc/rules/common/development-workflow.md +37 -0
  32. package/ecc/rules/common/git-workflow.md +24 -0
  33. package/ecc/rules/common/hooks.md +30 -0
  34. package/ecc/rules/common/patterns.md +31 -0
  35. package/ecc/rules/common/performance.md +55 -0
  36. package/ecc/rules/common/security.md +29 -0
  37. package/ecc/rules/common/testing.md +29 -0
  38. package/ecc/rules/golang/coding-style.md +32 -0
  39. package/ecc/rules/golang/hooks.md +17 -0
  40. package/ecc/rules/golang/patterns.md +45 -0
  41. package/ecc/rules/golang/security.md +34 -0
  42. package/ecc/rules/golang/testing.md +31 -0
  43. package/ecc/rules/kotlin/coding-style.md +86 -0
  44. package/ecc/rules/kotlin/patterns.md +146 -0
  45. package/ecc/rules/kotlin/security.md +82 -0
  46. package/ecc/rules/kotlin/testing.md +128 -0
  47. package/ecc/rules/perl/coding-style.md +46 -0
  48. package/ecc/rules/perl/hooks.md +22 -0
  49. package/ecc/rules/perl/patterns.md +76 -0
  50. package/ecc/rules/perl/security.md +69 -0
  51. package/ecc/rules/perl/testing.md +54 -0
  52. package/ecc/rules/php/coding-style.md +35 -0
  53. package/ecc/rules/php/hooks.md +24 -0
  54. package/ecc/rules/php/patterns.md +32 -0
  55. package/ecc/rules/php/security.md +33 -0
  56. package/ecc/rules/php/testing.md +34 -0
  57. package/ecc/rules/python/coding-style.md +42 -0
  58. package/ecc/rules/python/hooks.md +19 -0
  59. package/ecc/rules/python/patterns.md +39 -0
  60. package/ecc/rules/python/security.md +30 -0
  61. package/ecc/rules/python/testing.md +38 -0
  62. package/ecc/rules/swift/coding-style.md +47 -0
  63. package/ecc/rules/swift/hooks.md +20 -0
  64. package/ecc/rules/swift/patterns.md +66 -0
  65. package/ecc/rules/swift/security.md +33 -0
  66. package/ecc/rules/swift/testing.md +45 -0
  67. package/ecc/rules/typescript/coding-style.md +199 -0
  68. package/ecc/rules/typescript/hooks.md +22 -0
  69. package/ecc/rules/typescript/patterns.md +52 -0
  70. package/ecc/rules/typescript/security.md +28 -0
  71. package/ecc/rules/typescript/testing.md +18 -0
  72. package/ecc/scripts/hooks/check-hook-enabled.js +12 -0
  73. package/ecc/scripts/hooks/evaluate-session.js +100 -0
  74. package/ecc/scripts/hooks/pre-compact.js +48 -0
  75. package/ecc/scripts/hooks/run-with-flags-shell.sh +32 -0
  76. package/ecc/scripts/hooks/run-with-flags.js +120 -0
  77. package/ecc/scripts/hooks/session-end-marker.js +15 -0
  78. package/ecc/scripts/hooks/session-end.js +258 -0
  79. package/ecc/scripts/hooks/session-start.js +97 -0
  80. package/ecc/scripts/hooks/suggest-compact.js +80 -0
  81. package/ecc/scripts/lib/hook-flags.js +74 -0
  82. package/ecc/scripts/lib/package-manager.d.ts +119 -0
  83. package/ecc/scripts/lib/package-manager.js +431 -0
  84. package/ecc/scripts/lib/project-detect.js +428 -0
  85. package/ecc/scripts/lib/resolve-formatter.js +185 -0
  86. package/ecc/scripts/lib/session-aliases.d.ts +136 -0
  87. package/ecc/scripts/lib/session-aliases.js +481 -0
  88. package/ecc/scripts/lib/session-manager.d.ts +131 -0
  89. package/ecc/scripts/lib/session-manager.js +444 -0
  90. package/ecc/scripts/lib/shell-split.js +86 -0
  91. package/ecc/scripts/lib/utils.d.ts +183 -0
  92. package/ecc/scripts/lib/utils.js +543 -0
  93. package/ecc/skills/continuous-learning-v2/SKILL.md +365 -0
  94. package/ecc/skills/continuous-learning-v2/agents/observer-loop.sh +144 -0
  95. package/ecc/skills/continuous-learning-v2/agents/observer.md +198 -0
  96. package/ecc/skills/continuous-learning-v2/agents/start-observer.sh +194 -0
  97. package/ecc/skills/continuous-learning-v2/config.json +8 -0
  98. package/ecc/skills/continuous-learning-v2/hooks/observe.sh +246 -0
  99. package/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +218 -0
  100. package/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +1148 -0
  101. package/ecc/skills/continuous-learning-v2/scripts/test_parse_instinct.py +984 -0
  102. package/ecc/skills/strategic-compact/SKILL.md +103 -0
  103. package/ecc/skills/strategic-compact/suggest-compact.sh +54 -0
  104. package/ecc/skills/verification-loop-SKILL.md +126 -0
  105. package/gsd/LICENSE +21 -0
  106. package/gsd/agents/gsd-codebase-mapper.md +772 -0
  107. package/gsd/agents/gsd-debugger.md +1257 -0
  108. package/gsd/agents/gsd-executor.md +489 -0
  109. package/gsd/agents/gsd-integration-checker.md +445 -0
  110. package/gsd/agents/gsd-nyquist-auditor.md +178 -0
  111. package/gsd/agents/gsd-phase-researcher.md +555 -0
  112. package/gsd/agents/gsd-plan-checker.md +708 -0
  113. package/gsd/agents/gsd-planner.md +1309 -0
  114. package/gsd/agents/gsd-project-researcher.md +631 -0
  115. package/gsd/agents/gsd-research-synthesizer.md +249 -0
  116. package/gsd/agents/gsd-roadmapper.md +652 -0
  117. package/gsd/agents/gsd-verifier.md +581 -0
  118. package/gsd/commands-gsd/add-phase.md +43 -0
  119. package/gsd/commands-gsd/add-tests.md +41 -0
  120. package/gsd/commands-gsd/add-todo.md +47 -0
  121. package/gsd/commands-gsd/audit-milestone.md +36 -0
  122. package/gsd/commands-gsd/check-todos.md +45 -0
  123. package/gsd/commands-gsd/cleanup.md +18 -0
  124. package/gsd/commands-gsd/complete-milestone.md +136 -0
  125. package/gsd/commands-gsd/debug.md +168 -0
  126. package/gsd/commands-gsd/discuss-phase.md +90 -0
  127. package/gsd/commands-gsd/execute-phase.md +41 -0
  128. package/gsd/commands-gsd/health.md +22 -0
  129. package/gsd/commands-gsd/help.md +22 -0
  130. package/gsd/commands-gsd/insert-phase.md +32 -0
  131. package/gsd/commands-gsd/join-discord.md +18 -0
  132. package/gsd/commands-gsd/list-phase-assumptions.md +46 -0
  133. package/gsd/commands-gsd/map-codebase.md +71 -0
  134. package/gsd/commands-gsd/new-milestone.md +44 -0
  135. package/gsd/commands-gsd/new-project.md +42 -0
  136. package/gsd/commands-gsd/pause-work.md +38 -0
  137. package/gsd/commands-gsd/plan-milestone-gaps.md +34 -0
  138. package/gsd/commands-gsd/plan-phase.md +45 -0
  139. package/gsd/commands-gsd/progress.md +24 -0
  140. package/gsd/commands-gsd/quick.md +45 -0
  141. package/gsd/commands-gsd/reapply-patches.md +123 -0
  142. package/gsd/commands-gsd/remove-phase.md +31 -0
  143. package/gsd/commands-gsd/research-phase.md +190 -0
  144. package/gsd/commands-gsd/resume-work.md +40 -0
  145. package/gsd/commands-gsd/set-profile.md +34 -0
  146. package/gsd/commands-gsd/settings.md +36 -0
  147. package/gsd/commands-gsd/update.md +37 -0
  148. package/gsd/commands-gsd/validate-phase.md +35 -0
  149. package/gsd/commands-gsd/verify-work.md +38 -0
  150. package/gsd/get-shit-done/bin/gsd-tools.cjs +592 -0
  151. package/gsd/get-shit-done/bin/lib/commands.cjs +548 -0
  152. package/gsd/get-shit-done/bin/lib/config.cjs +169 -0
  153. package/gsd/get-shit-done/bin/lib/core.cjs +492 -0
  154. package/gsd/get-shit-done/bin/lib/frontmatter.cjs +299 -0
  155. package/gsd/get-shit-done/bin/lib/init.cjs +710 -0
  156. package/gsd/get-shit-done/bin/lib/milestone.cjs +241 -0
  157. package/gsd/get-shit-done/bin/lib/phase.cjs +901 -0
  158. package/gsd/get-shit-done/bin/lib/roadmap.cjs +298 -0
  159. package/gsd/get-shit-done/bin/lib/state.cjs +721 -0
  160. package/gsd/get-shit-done/bin/lib/template.cjs +222 -0
  161. package/gsd/get-shit-done/bin/lib/verify.cjs +820 -0
  162. package/gsd/get-shit-done/references/checkpoints.md +776 -0
  163. package/gsd/get-shit-done/references/continuation-format.md +249 -0
  164. package/gsd/get-shit-done/references/decimal-phase-calculation.md +65 -0
  165. package/gsd/get-shit-done/references/git-integration.md +248 -0
  166. package/gsd/get-shit-done/references/git-planning-commit.md +38 -0
  167. package/gsd/get-shit-done/references/model-profile-resolution.md +34 -0
  168. package/gsd/get-shit-done/references/model-profiles.md +93 -0
  169. package/gsd/get-shit-done/references/phase-argument-parsing.md +61 -0
  170. package/gsd/get-shit-done/references/planning-config.md +200 -0
  171. package/gsd/get-shit-done/references/questioning.md +162 -0
  172. package/gsd/get-shit-done/references/tdd.md +263 -0
  173. package/gsd/get-shit-done/references/ui-brand.md +160 -0
  174. package/gsd/get-shit-done/references/verification-patterns.md +612 -0
  175. package/gsd/get-shit-done/templates/DEBUG.md +164 -0
  176. package/gsd/get-shit-done/templates/UAT.md +247 -0
  177. package/gsd/get-shit-done/templates/VALIDATION.md +76 -0
  178. package/gsd/get-shit-done/templates/codebase/architecture.md +255 -0
  179. package/gsd/get-shit-done/templates/codebase/concerns.md +310 -0
  180. package/gsd/get-shit-done/templates/codebase/conventions.md +307 -0
  181. package/gsd/get-shit-done/templates/codebase/integrations.md +280 -0
  182. package/gsd/get-shit-done/templates/codebase/stack.md +186 -0
  183. package/gsd/get-shit-done/templates/codebase/structure.md +285 -0
  184. package/gsd/get-shit-done/templates/codebase/testing.md +480 -0
  185. package/gsd/get-shit-done/templates/config.json +37 -0
  186. package/gsd/get-shit-done/templates/context.md +297 -0
  187. package/gsd/get-shit-done/templates/continue-here.md +78 -0
  188. package/gsd/get-shit-done/templates/debug-subagent-prompt.md +91 -0
  189. package/gsd/get-shit-done/templates/discovery.md +146 -0
  190. package/gsd/get-shit-done/templates/milestone-archive.md +123 -0
  191. package/gsd/get-shit-done/templates/milestone.md +115 -0
  192. package/gsd/get-shit-done/templates/phase-prompt.md +569 -0
  193. package/gsd/get-shit-done/templates/planner-subagent-prompt.md +117 -0
  194. package/gsd/get-shit-done/templates/project.md +184 -0
  195. package/gsd/get-shit-done/templates/requirements.md +231 -0
  196. package/gsd/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
  197. package/gsd/get-shit-done/templates/research-project/FEATURES.md +147 -0
  198. package/gsd/get-shit-done/templates/research-project/PITFALLS.md +200 -0
  199. package/gsd/get-shit-done/templates/research-project/STACK.md +120 -0
  200. package/gsd/get-shit-done/templates/research-project/SUMMARY.md +170 -0
  201. package/gsd/get-shit-done/templates/research.md +552 -0
  202. package/gsd/get-shit-done/templates/retrospective.md +54 -0
  203. package/gsd/get-shit-done/templates/roadmap.md +202 -0
  204. package/gsd/get-shit-done/templates/state.md +176 -0
  205. package/gsd/get-shit-done/templates/summary-complex.md +59 -0
  206. package/gsd/get-shit-done/templates/summary-minimal.md +41 -0
  207. package/gsd/get-shit-done/templates/summary-standard.md +48 -0
  208. package/gsd/get-shit-done/templates/summary.md +248 -0
  209. package/gsd/get-shit-done/templates/user-setup.md +311 -0
  210. package/gsd/get-shit-done/templates/verification-report.md +322 -0
  211. package/gsd/get-shit-done/workflows/add-phase.md +112 -0
  212. package/gsd/get-shit-done/workflows/add-tests.md +351 -0
  213. package/gsd/get-shit-done/workflows/add-todo.md +158 -0
  214. package/gsd/get-shit-done/workflows/audit-milestone.md +332 -0
  215. package/gsd/get-shit-done/workflows/check-todos.md +177 -0
  216. package/gsd/get-shit-done/workflows/cleanup.md +152 -0
  217. package/gsd/get-shit-done/workflows/complete-milestone.md +764 -0
  218. package/gsd/get-shit-done/workflows/diagnose-issues.md +219 -0
  219. package/gsd/get-shit-done/workflows/discovery-phase.md +289 -0
  220. package/gsd/get-shit-done/workflows/discuss-phase.md +676 -0
  221. package/gsd/get-shit-done/workflows/execute-phase.md +459 -0
  222. package/gsd/get-shit-done/workflows/execute-plan.md +449 -0
  223. package/gsd/get-shit-done/workflows/health.md +159 -0
  224. package/gsd/get-shit-done/workflows/help.md +489 -0
  225. package/gsd/get-shit-done/workflows/insert-phase.md +130 -0
  226. package/gsd/get-shit-done/workflows/list-phase-assumptions.md +178 -0
  227. package/gsd/get-shit-done/workflows/map-codebase.md +316 -0
  228. package/gsd/get-shit-done/workflows/new-milestone.md +384 -0
  229. package/gsd/get-shit-done/workflows/new-project.md +1111 -0
  230. package/gsd/get-shit-done/workflows/pause-work.md +122 -0
  231. package/gsd/get-shit-done/workflows/plan-milestone-gaps.md +274 -0
  232. package/gsd/get-shit-done/workflows/plan-phase.md +560 -0
  233. package/gsd/get-shit-done/workflows/progress.md +382 -0
  234. package/gsd/get-shit-done/workflows/quick.md +601 -0
  235. package/gsd/get-shit-done/workflows/remove-phase.md +155 -0
  236. package/gsd/get-shit-done/workflows/research-phase.md +74 -0
  237. package/gsd/get-shit-done/workflows/resume-project.md +307 -0
  238. package/gsd/get-shit-done/workflows/set-profile.md +81 -0
  239. package/gsd/get-shit-done/workflows/settings.md +214 -0
  240. package/gsd/get-shit-done/workflows/transition.md +544 -0
  241. package/gsd/get-shit-done/workflows/update.md +240 -0
  242. package/gsd/get-shit-done/workflows/validate-phase.md +167 -0
  243. package/gsd/get-shit-done/workflows/verify-phase.md +243 -0
  244. package/gsd/get-shit-done/workflows/verify-work.md +583 -0
  245. package/gsd/hooks/gsd-check-update.js +81 -0
  246. package/gsd/hooks/gsd-context-monitor.js +141 -0
  247. package/gsd/hooks/gsd-statusline.js +115 -0
  248. package/kit/CLAUDE.md +43 -0
  249. package/kit/commands/kit/update.md +46 -0
  250. package/kit/commands/setup-refresh.md +50 -0
  251. package/kit/commands/setup.md +579 -0
  252. package/kit/commands/tool-guide.md +44 -0
  253. package/kit/hooks/kit-check-update.js +54 -0
  254. package/kit/mcp.json +10 -0
  255. package/kit/rules/code-style.md +24 -0
  256. package/manifest.json +30 -0
  257. package/package.json +36 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreCompact Hook - Save state before context compaction
4
+ *
5
+ * Cross-platform (Windows, macOS, Linux)
6
+ *
7
+ * Runs before Claude compacts context, giving you a chance to
8
+ * preserve important state that might get lost in summarization.
9
+ */
10
+
11
+ const path = require('path');
12
+ const {
13
+ getSessionsDir,
14
+ getDateTimeString,
15
+ getTimeString,
16
+ findFiles,
17
+ ensureDir,
18
+ appendFile,
19
+ log
20
+ } = require('../lib/utils');
21
+
22
+ async function main() {
23
+ const sessionsDir = getSessionsDir();
24
+ const compactionLog = path.join(sessionsDir, 'compaction-log.txt');
25
+
26
+ ensureDir(sessionsDir);
27
+
28
+ // Log compaction event with timestamp
29
+ const timestamp = getDateTimeString();
30
+ appendFile(compactionLog, `[${timestamp}] Context compaction triggered\n`);
31
+
32
+ // If there's an active session file, note the compaction
33
+ const sessions = findFiles(sessionsDir, '*-session.tmp');
34
+
35
+ if (sessions.length > 0) {
36
+ const activeSession = sessions[0].path;
37
+ const timeStr = getTimeString();
38
+ appendFile(activeSession, `\n---\n**[Compaction occurred at ${timeStr}]** - Context was summarized\n`);
39
+ }
40
+
41
+ log('[PreCompact] State saved before compaction');
42
+ process.exit(0);
43
+ }
44
+
45
+ main().catch(err => {
46
+ console.error('[PreCompact] Error:', err.message);
47
+ process.exit(0);
48
+ });
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ HOOK_ID="${1:-}"
5
+ REL_SCRIPT_PATH="${2:-}"
6
+ PROFILES_CSV="${3:-standard,strict}"
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
9
+
10
+ # Preserve stdin for passthrough or script execution
11
+ INPUT="$(cat)"
12
+
13
+ if [[ -z "$HOOK_ID" || -z "$REL_SCRIPT_PATH" ]]; then
14
+ printf '%s' "$INPUT"
15
+ exit 0
16
+ fi
17
+
18
+ # Ask Node helper if this hook is enabled
19
+ ENABLED="$(node "${PLUGIN_ROOT}/scripts/hooks/check-hook-enabled.js" "$HOOK_ID" "$PROFILES_CSV" 2>/dev/null || echo yes)"
20
+ if [[ "$ENABLED" != "yes" ]]; then
21
+ printf '%s' "$INPUT"
22
+ exit 0
23
+ fi
24
+
25
+ SCRIPT_PATH="${PLUGIN_ROOT}/${REL_SCRIPT_PATH}"
26
+ if [[ ! -f "$SCRIPT_PATH" ]]; then
27
+ echo "[Hook] Script not found for ${HOOK_ID}: ${SCRIPT_PATH}" >&2
28
+ printf '%s' "$INPUT"
29
+ exit 0
30
+ fi
31
+
32
+ printf '%s' "$INPUT" | "$SCRIPT_PATH"
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Executes a hook script only when enabled by ECC hook profile flags.
4
+ *
5
+ * Usage:
6
+ * node run-with-flags.js <hookId> <scriptRelativePath> [profilesCsv]
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { spawnSync } = require('child_process');
14
+ const { isHookEnabled } = require('../lib/hook-flags');
15
+
16
+ const MAX_STDIN = 1024 * 1024;
17
+
18
+ function readStdinRaw() {
19
+ return new Promise(resolve => {
20
+ let raw = '';
21
+ process.stdin.setEncoding('utf8');
22
+ process.stdin.on('data', chunk => {
23
+ if (raw.length < MAX_STDIN) {
24
+ const remaining = MAX_STDIN - raw.length;
25
+ raw += chunk.substring(0, remaining);
26
+ }
27
+ });
28
+ process.stdin.on('end', () => resolve(raw));
29
+ process.stdin.on('error', () => resolve(raw));
30
+ });
31
+ }
32
+
33
+ function getPluginRoot() {
34
+ if (process.env.CLAUDE_PLUGIN_ROOT && process.env.CLAUDE_PLUGIN_ROOT.trim()) {
35
+ return process.env.CLAUDE_PLUGIN_ROOT;
36
+ }
37
+ return path.resolve(__dirname, '..', '..');
38
+ }
39
+
40
+ async function main() {
41
+ const [, , hookId, relScriptPath, profilesCsv] = process.argv;
42
+ const raw = await readStdinRaw();
43
+
44
+ if (!hookId || !relScriptPath) {
45
+ process.stdout.write(raw);
46
+ process.exit(0);
47
+ }
48
+
49
+ if (!isHookEnabled(hookId, { profiles: profilesCsv })) {
50
+ process.stdout.write(raw);
51
+ process.exit(0);
52
+ }
53
+
54
+ const pluginRoot = getPluginRoot();
55
+ const resolvedRoot = path.resolve(pluginRoot);
56
+ const scriptPath = path.resolve(pluginRoot, relScriptPath);
57
+
58
+ // Prevent path traversal outside the plugin root
59
+ if (!scriptPath.startsWith(resolvedRoot + path.sep)) {
60
+ process.stderr.write(`[Hook] Path traversal rejected for ${hookId}: ${scriptPath}\n`);
61
+ process.stdout.write(raw);
62
+ process.exit(0);
63
+ }
64
+
65
+ if (!fs.existsSync(scriptPath)) {
66
+ process.stderr.write(`[Hook] Script not found for ${hookId}: ${scriptPath}\n`);
67
+ process.stdout.write(raw);
68
+ process.exit(0);
69
+ }
70
+
71
+ // Prefer direct require() when the hook exports a run(rawInput) function.
72
+ // This eliminates one Node.js process spawn (~50-100ms savings per hook).
73
+ //
74
+ // SAFETY: Only require() hooks that export run(). Legacy hooks execute
75
+ // side effects at module scope (stdin listeners, process.exit, main() calls)
76
+ // which would interfere with the parent process or cause double execution.
77
+ let hookModule;
78
+ const src = fs.readFileSync(scriptPath, 'utf8');
79
+ const hasRunExport = /\bmodule\.exports\b/.test(src) && /\brun\b/.test(src);
80
+
81
+ if (hasRunExport) {
82
+ try {
83
+ hookModule = require(scriptPath);
84
+ } catch (requireErr) {
85
+ process.stderr.write(`[Hook] require() failed for ${hookId}: ${requireErr.message}\n`);
86
+ // Fall through to legacy spawnSync path
87
+ }
88
+ }
89
+
90
+ if (hookModule && typeof hookModule.run === 'function') {
91
+ try {
92
+ const output = hookModule.run(raw);
93
+ if (output !== null && output !== undefined) process.stdout.write(output);
94
+ } catch (runErr) {
95
+ process.stderr.write(`[Hook] run() error for ${hookId}: ${runErr.message}\n`);
96
+ process.stdout.write(raw);
97
+ }
98
+ process.exit(0);
99
+ }
100
+
101
+ // Legacy path: spawn a child Node process for hooks without run() export
102
+ const result = spawnSync('node', [scriptPath], {
103
+ input: raw,
104
+ encoding: 'utf8',
105
+ env: process.env,
106
+ cwd: process.cwd(),
107
+ timeout: 30000
108
+ });
109
+
110
+ if (result.stdout) process.stdout.write(result.stdout);
111
+ if (result.stderr) process.stderr.write(result.stderr);
112
+
113
+ const code = Number.isInteger(result.status) ? result.status : 0;
114
+ process.exit(code);
115
+ }
116
+
117
+ main().catch(err => {
118
+ process.stderr.write(`[Hook] run-with-flags error: ${err.message}\n`);
119
+ process.exit(0);
120
+ });
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const MAX_STDIN = 1024 * 1024;
5
+ let raw = '';
6
+ process.stdin.setEncoding('utf8');
7
+ process.stdin.on('data', chunk => {
8
+ if (raw.length < MAX_STDIN) {
9
+ const remaining = MAX_STDIN - raw.length;
10
+ raw += chunk.substring(0, remaining);
11
+ }
12
+ });
13
+ process.stdin.on('end', () => {
14
+ process.stdout.write(raw);
15
+ });
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop Hook (Session End) - Persist learnings during active sessions
4
+ *
5
+ * Cross-platform (Windows, macOS, Linux)
6
+ *
7
+ * Runs on Stop events (after each response). Extracts a meaningful summary
8
+ * from the session transcript (via stdin JSON transcript_path) and updates a
9
+ * session file for cross-session continuity.
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const {
15
+ getSessionsDir,
16
+ getDateString,
17
+ getTimeString,
18
+ getSessionIdShort,
19
+ ensureDir,
20
+ readFile,
21
+ writeFile,
22
+ replaceInFile,
23
+ log
24
+ } = require('../lib/utils');
25
+
26
+ const SUMMARY_START_MARKER = '<!-- ECC:SUMMARY:START -->';
27
+ const SUMMARY_END_MARKER = '<!-- ECC:SUMMARY:END -->';
28
+
29
+ /**
30
+ * Extract a meaningful summary from the session transcript.
31
+ * Reads the JSONL transcript and pulls out key information:
32
+ * - User messages (tasks requested)
33
+ * - Tools used
34
+ * - Files modified
35
+ */
36
+ function extractSessionSummary(transcriptPath) {
37
+ const content = readFile(transcriptPath);
38
+ if (!content) return null;
39
+
40
+ const lines = content.split('\n').filter(Boolean);
41
+ const userMessages = [];
42
+ const toolsUsed = new Set();
43
+ const filesModified = new Set();
44
+ let parseErrors = 0;
45
+
46
+ for (const line of lines) {
47
+ try {
48
+ const entry = JSON.parse(line);
49
+
50
+ // Collect user messages (first 200 chars each)
51
+ if (entry.type === 'user' || entry.role === 'user' || entry.message?.role === 'user') {
52
+ // Support both direct content and nested message.content (Claude Code JSONL format)
53
+ const rawContent = entry.message?.content ?? entry.content;
54
+ const text = typeof rawContent === 'string'
55
+ ? rawContent
56
+ : Array.isArray(rawContent)
57
+ ? rawContent.map(c => (c && c.text) || '').join(' ')
58
+ : '';
59
+ if (text.trim()) {
60
+ userMessages.push(text.trim().slice(0, 200));
61
+ }
62
+ }
63
+
64
+ // Collect tool names and modified files (direct tool_use entries)
65
+ if (entry.type === 'tool_use' || entry.tool_name) {
66
+ const toolName = entry.tool_name || entry.name || '';
67
+ if (toolName) toolsUsed.add(toolName);
68
+
69
+ const filePath = entry.tool_input?.file_path || entry.input?.file_path || '';
70
+ if (filePath && (toolName === 'Edit' || toolName === 'Write')) {
71
+ filesModified.add(filePath);
72
+ }
73
+ }
74
+
75
+ // Extract tool uses from assistant message content blocks (Claude Code JSONL format)
76
+ if (entry.type === 'assistant' && Array.isArray(entry.message?.content)) {
77
+ for (const block of entry.message.content) {
78
+ if (block.type === 'tool_use') {
79
+ const toolName = block.name || '';
80
+ if (toolName) toolsUsed.add(toolName);
81
+
82
+ const filePath = block.input?.file_path || '';
83
+ if (filePath && (toolName === 'Edit' || toolName === 'Write')) {
84
+ filesModified.add(filePath);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ } catch {
90
+ parseErrors++;
91
+ }
92
+ }
93
+
94
+ if (parseErrors > 0) {
95
+ log(`[SessionEnd] Skipped ${parseErrors}/${lines.length} unparseable transcript lines`);
96
+ }
97
+
98
+ if (userMessages.length === 0) return null;
99
+
100
+ return {
101
+ userMessages: userMessages.slice(-10), // Last 10 user messages
102
+ toolsUsed: Array.from(toolsUsed).slice(0, 20),
103
+ filesModified: Array.from(filesModified).slice(0, 30),
104
+ totalMessages: userMessages.length
105
+ };
106
+ }
107
+
108
+ // Read hook input from stdin (Claude Code provides transcript_path via stdin JSON)
109
+ const MAX_STDIN = 1024 * 1024;
110
+ let stdinData = '';
111
+ process.stdin.setEncoding('utf8');
112
+
113
+ process.stdin.on('data', chunk => {
114
+ if (stdinData.length < MAX_STDIN) {
115
+ const remaining = MAX_STDIN - stdinData.length;
116
+ stdinData += chunk.substring(0, remaining);
117
+ }
118
+ });
119
+
120
+ process.stdin.on('end', () => {
121
+ runMain();
122
+ });
123
+
124
+ function runMain() {
125
+ main().catch(err => {
126
+ console.error('[SessionEnd] Error:', err.message);
127
+ process.exit(0);
128
+ });
129
+ }
130
+
131
+ async function main() {
132
+ // Parse stdin JSON to get transcript_path
133
+ let transcriptPath = null;
134
+ try {
135
+ const input = JSON.parse(stdinData);
136
+ transcriptPath = input.transcript_path;
137
+ } catch {
138
+ // Fallback: try env var for backwards compatibility
139
+ transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
140
+ }
141
+
142
+ const sessionsDir = getSessionsDir();
143
+ const today = getDateString();
144
+ const shortId = getSessionIdShort();
145
+ const sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
146
+
147
+ ensureDir(sessionsDir);
148
+
149
+ const currentTime = getTimeString();
150
+
151
+ // Try to extract summary from transcript
152
+ let summary = null;
153
+
154
+ if (transcriptPath) {
155
+ if (fs.existsSync(transcriptPath)) {
156
+ summary = extractSessionSummary(transcriptPath);
157
+ } else {
158
+ log(`[SessionEnd] Transcript not found: ${transcriptPath}`);
159
+ }
160
+ }
161
+
162
+ if (fs.existsSync(sessionFile)) {
163
+ // Update existing session file
164
+ const updated = replaceInFile(
165
+ sessionFile,
166
+ /\*\*Last Updated:\*\*.*/,
167
+ `**Last Updated:** ${currentTime}`
168
+ );
169
+ if (!updated) {
170
+ log(`[SessionEnd] Failed to update timestamp in ${sessionFile}`);
171
+ }
172
+
173
+ // If we have a new summary, update only the generated summary block.
174
+ // This keeps repeated Stop invocations idempotent and preserves
175
+ // user-authored sections in the same session file.
176
+ if (summary) {
177
+ const existing = readFile(sessionFile);
178
+ if (existing) {
179
+ const summaryBlock = buildSummaryBlock(summary);
180
+ let updatedContent = existing;
181
+
182
+ if (existing.includes(SUMMARY_START_MARKER) && existing.includes(SUMMARY_END_MARKER)) {
183
+ updatedContent = existing.replace(
184
+ new RegExp(`${escapeRegExp(SUMMARY_START_MARKER)}[\\s\\S]*?${escapeRegExp(SUMMARY_END_MARKER)}`),
185
+ summaryBlock
186
+ );
187
+ } else {
188
+ // Migration path for files created before summary markers existed.
189
+ updatedContent = existing.replace(
190
+ /## (?:Session Summary|Current State)[\s\S]*?$/,
191
+ `${summaryBlock}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\`\n`
192
+ );
193
+ }
194
+
195
+ writeFile(sessionFile, updatedContent);
196
+ }
197
+ }
198
+
199
+ log(`[SessionEnd] Updated session file: ${sessionFile}`);
200
+ } else {
201
+ // Create new session file
202
+ const summarySection = summary
203
+ ? `${buildSummaryBlock(summary)}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``
204
+ : `## Current State\n\n[Session context goes here]\n\n### Completed\n- [ ]\n\n### In Progress\n- [ ]\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\``;
205
+
206
+ const template = `# Session: ${today}
207
+ **Date:** ${today}
208
+ **Started:** ${currentTime}
209
+ **Last Updated:** ${currentTime}
210
+
211
+ ---
212
+
213
+ ${summarySection}
214
+ `;
215
+
216
+ writeFile(sessionFile, template);
217
+ log(`[SessionEnd] Created session file: ${sessionFile}`);
218
+ }
219
+
220
+ process.exit(0);
221
+ }
222
+
223
+ function buildSummarySection(summary) {
224
+ let section = '## Session Summary\n\n';
225
+
226
+ // Tasks (from user messages — collapse newlines and escape backticks to prevent markdown breaks)
227
+ section += '### Tasks\n';
228
+ for (const msg of summary.userMessages) {
229
+ section += `- ${msg.replace(/\n/g, ' ').replace(/`/g, '\\`')}\n`;
230
+ }
231
+ section += '\n';
232
+
233
+ // Files modified
234
+ if (summary.filesModified.length > 0) {
235
+ section += '### Files Modified\n';
236
+ for (const f of summary.filesModified) {
237
+ section += `- ${f}\n`;
238
+ }
239
+ section += '\n';
240
+ }
241
+
242
+ // Tools used
243
+ if (summary.toolsUsed.length > 0) {
244
+ section += `### Tools Used\n${summary.toolsUsed.join(', ')}\n\n`;
245
+ }
246
+
247
+ section += `### Stats\n- Total user messages: ${summary.totalMessages}\n`;
248
+
249
+ return section;
250
+ }
251
+
252
+ function buildSummaryBlock(summary) {
253
+ return `${SUMMARY_START_MARKER}\n${buildSummarySection(summary).trim()}\n${SUMMARY_END_MARKER}`;
254
+ }
255
+
256
+ function escapeRegExp(value) {
257
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
258
+ }
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart Hook - Load previous context on new session
4
+ *
5
+ * Cross-platform (Windows, macOS, Linux)
6
+ *
7
+ * Runs when a new Claude session starts. Loads the most recent session
8
+ * summary into Claude's context via stdout, and reports available
9
+ * sessions and learned skills.
10
+ */
11
+
12
+ const {
13
+ getSessionsDir,
14
+ getLearnedSkillsDir,
15
+ findFiles,
16
+ ensureDir,
17
+ readFile,
18
+ log,
19
+ output
20
+ } = require('../lib/utils');
21
+ const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager');
22
+ const { listAliases } = require('../lib/session-aliases');
23
+ const { detectProjectType } = require('../lib/project-detect');
24
+
25
+ async function main() {
26
+ const sessionsDir = getSessionsDir();
27
+ const learnedDir = getLearnedSkillsDir();
28
+
29
+ // Ensure directories exist
30
+ ensureDir(sessionsDir);
31
+ ensureDir(learnedDir);
32
+
33
+ // Check for recent session files (last 7 days)
34
+ const recentSessions = findFiles(sessionsDir, '*-session.tmp', { maxAge: 7 });
35
+
36
+ if (recentSessions.length > 0) {
37
+ const latest = recentSessions[0];
38
+ log(`[SessionStart] Found ${recentSessions.length} recent session(s)`);
39
+ log(`[SessionStart] Latest: ${latest.path}`);
40
+
41
+ // Read and inject the latest session content into Claude's context
42
+ const content = readFile(latest.path);
43
+ if (content && !content.includes('[Session context goes here]')) {
44
+ // Only inject if the session has actual content (not the blank template)
45
+ output(`Previous session summary:\n${content}`);
46
+ }
47
+ }
48
+
49
+ // Check for learned skills
50
+ const learnedSkills = findFiles(learnedDir, '*.md');
51
+
52
+ if (learnedSkills.length > 0) {
53
+ log(`[SessionStart] ${learnedSkills.length} learned skill(s) available in ${learnedDir}`);
54
+ }
55
+
56
+ // Check for available session aliases
57
+ const aliases = listAliases({ limit: 5 });
58
+
59
+ if (aliases.length > 0) {
60
+ const aliasNames = aliases.map(a => a.name).join(', ');
61
+ log(`[SessionStart] ${aliases.length} session alias(es) available: ${aliasNames}`);
62
+ log(`[SessionStart] Use /sessions load <alias> to continue a previous session`);
63
+ }
64
+
65
+ // Detect and report package manager
66
+ const pm = getPackageManager();
67
+ log(`[SessionStart] Package manager: ${pm.name} (${pm.source})`);
68
+
69
+ // If no explicit package manager config was found, show selection prompt
70
+ if (pm.source === 'default') {
71
+ log('[SessionStart] No package manager preference found.');
72
+ log(getSelectionPrompt());
73
+ }
74
+
75
+ // Detect project type and frameworks (#293)
76
+ const projectInfo = detectProjectType();
77
+ if (projectInfo.languages.length > 0 || projectInfo.frameworks.length > 0) {
78
+ const parts = [];
79
+ if (projectInfo.languages.length > 0) {
80
+ parts.push(`languages: ${projectInfo.languages.join(', ')}`);
81
+ }
82
+ if (projectInfo.frameworks.length > 0) {
83
+ parts.push(`frameworks: ${projectInfo.frameworks.join(', ')}`);
84
+ }
85
+ log(`[SessionStart] Project detected — ${parts.join('; ')}`);
86
+ output(`Project type: ${JSON.stringify(projectInfo)}`);
87
+ } else {
88
+ log('[SessionStart] No specific project type detected');
89
+ }
90
+
91
+ process.exit(0);
92
+ }
93
+
94
+ main().catch(err => {
95
+ console.error('[SessionStart] Error:', err.message);
96
+ process.exit(0); // Don't block on errors
97
+ });
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Strategic Compact Suggester
4
+ *
5
+ * Cross-platform (Windows, macOS, Linux)
6
+ *
7
+ * Runs on PreToolUse or periodically to suggest manual compaction at logical intervals
8
+ *
9
+ * Why manual over auto-compact:
10
+ * - Auto-compact happens at arbitrary points, often mid-task
11
+ * - Strategic compacting preserves context through logical phases
12
+ * - Compact after exploration, before execution
13
+ * - Compact after completing a milestone, before starting next
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const {
19
+ getTempDir,
20
+ writeFile,
21
+ log
22
+ } = require('../lib/utils');
23
+
24
+ async function main() {
25
+ // Track tool call count (increment in a temp file)
26
+ // Use a session-specific counter file based on session ID from environment
27
+ // or parent PID as fallback
28
+ const sessionId = (process.env.CLAUDE_SESSION_ID || 'default').replace(/[^a-zA-Z0-9_-]/g, '') || 'default';
29
+ const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
30
+ const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
31
+ const threshold = Number.isFinite(rawThreshold) && rawThreshold > 0 && rawThreshold <= 10000
32
+ ? rawThreshold
33
+ : 50;
34
+
35
+ let count = 1;
36
+
37
+ // Read existing count or start at 1
38
+ // Use fd-based read+write to reduce (but not eliminate) race window
39
+ // between concurrent hook invocations
40
+ try {
41
+ const fd = fs.openSync(counterFile, 'a+');
42
+ try {
43
+ const buf = Buffer.alloc(64);
44
+ const bytesRead = fs.readSync(fd, buf, 0, 64, 0);
45
+ if (bytesRead > 0) {
46
+ const parsed = parseInt(buf.toString('utf8', 0, bytesRead).trim(), 10);
47
+ // Clamp to reasonable range — corrupted files could contain huge values
48
+ // that pass Number.isFinite() (e.g., parseInt('9'.repeat(30)) => 1e+29)
49
+ count = (Number.isFinite(parsed) && parsed > 0 && parsed <= 1000000)
50
+ ? parsed + 1
51
+ : 1;
52
+ }
53
+ // Truncate and write new value
54
+ fs.ftruncateSync(fd, 0);
55
+ fs.writeSync(fd, String(count), 0);
56
+ } finally {
57
+ fs.closeSync(fd);
58
+ }
59
+ } catch {
60
+ // Fallback: just use writeFile if fd operations fail
61
+ writeFile(counterFile, String(count));
62
+ }
63
+
64
+ // Suggest compact after threshold tool calls
65
+ if (count === threshold) {
66
+ log(`[StrategicCompact] ${threshold} tool calls reached - consider /compact if transitioning phases`);
67
+ }
68
+
69
+ // Suggest at regular intervals after threshold (every 25 calls from threshold)
70
+ if (count > threshold && (count - threshold) % 25 === 0) {
71
+ log(`[StrategicCompact] ${count} tool calls - good checkpoint for /compact if context is stale`);
72
+ }
73
+
74
+ process.exit(0);
75
+ }
76
+
77
+ main().catch(err => {
78
+ console.error('[StrategicCompact] Error:', err.message);
79
+ process.exit(0);
80
+ });