cc4pm 1.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.
Files changed (108) hide show
  1. package/.claude-plugin/README.md +17 -0
  2. package/.claude-plugin/plugin.json +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +157 -0
  5. package/README.zh-CN.md +134 -0
  6. package/contexts/dev.md +20 -0
  7. package/contexts/research.md +26 -0
  8. package/contexts/review.md +22 -0
  9. package/examples/CLAUDE.md +100 -0
  10. package/examples/statusline.json +19 -0
  11. package/examples/user-CLAUDE.md +109 -0
  12. package/install.sh +17 -0
  13. package/manifests/install-components.json +173 -0
  14. package/manifests/install-modules.json +335 -0
  15. package/manifests/install-profiles.json +75 -0
  16. package/package.json +117 -0
  17. package/schemas/ecc-install-config.schema.json +58 -0
  18. package/schemas/hooks.schema.json +197 -0
  19. package/schemas/install-components.schema.json +56 -0
  20. package/schemas/install-modules.schema.json +105 -0
  21. package/schemas/install-profiles.schema.json +45 -0
  22. package/schemas/install-state.schema.json +210 -0
  23. package/schemas/package-manager.schema.json +23 -0
  24. package/schemas/plugin.schema.json +58 -0
  25. package/scripts/ci/catalog.js +83 -0
  26. package/scripts/ci/validate-agents.js +81 -0
  27. package/scripts/ci/validate-commands.js +135 -0
  28. package/scripts/ci/validate-hooks.js +239 -0
  29. package/scripts/ci/validate-install-manifests.js +211 -0
  30. package/scripts/ci/validate-no-personal-paths.js +63 -0
  31. package/scripts/ci/validate-rules.js +81 -0
  32. package/scripts/ci/validate-skills.js +54 -0
  33. package/scripts/claw.js +468 -0
  34. package/scripts/doctor.js +110 -0
  35. package/scripts/ecc.js +194 -0
  36. package/scripts/hooks/auto-tmux-dev.js +88 -0
  37. package/scripts/hooks/check-console-log.js +71 -0
  38. package/scripts/hooks/check-hook-enabled.js +12 -0
  39. package/scripts/hooks/cost-tracker.js +78 -0
  40. package/scripts/hooks/doc-file-warning.js +63 -0
  41. package/scripts/hooks/evaluate-session.js +100 -0
  42. package/scripts/hooks/insaits-security-monitor.py +269 -0
  43. package/scripts/hooks/insaits-security-wrapper.js +88 -0
  44. package/scripts/hooks/post-bash-build-complete.js +27 -0
  45. package/scripts/hooks/post-bash-pr-created.js +36 -0
  46. package/scripts/hooks/post-edit-console-warn.js +54 -0
  47. package/scripts/hooks/post-edit-format.js +109 -0
  48. package/scripts/hooks/post-edit-typecheck.js +96 -0
  49. package/scripts/hooks/pre-bash-dev-server-block.js +187 -0
  50. package/scripts/hooks/pre-bash-git-push-reminder.js +28 -0
  51. package/scripts/hooks/pre-bash-tmux-reminder.js +33 -0
  52. package/scripts/hooks/pre-compact.js +48 -0
  53. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  54. package/scripts/hooks/quality-gate.js +168 -0
  55. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  56. package/scripts/hooks/run-with-flags.js +120 -0
  57. package/scripts/hooks/session-end-marker.js +15 -0
  58. package/scripts/hooks/session-end.js +299 -0
  59. package/scripts/hooks/session-start.js +97 -0
  60. package/scripts/hooks/suggest-compact.js +80 -0
  61. package/scripts/install-apply.js +137 -0
  62. package/scripts/install-plan.js +254 -0
  63. package/scripts/lib/hook-flags.js +74 -0
  64. package/scripts/lib/install/apply.js +23 -0
  65. package/scripts/lib/install/config.js +82 -0
  66. package/scripts/lib/install/request.js +113 -0
  67. package/scripts/lib/install/runtime.js +42 -0
  68. package/scripts/lib/install-executor.js +605 -0
  69. package/scripts/lib/install-lifecycle.js +763 -0
  70. package/scripts/lib/install-manifests.js +305 -0
  71. package/scripts/lib/install-state.js +120 -0
  72. package/scripts/lib/install-targets/antigravity-project.js +9 -0
  73. package/scripts/lib/install-targets/claude-home.js +10 -0
  74. package/scripts/lib/install-targets/codex-home.js +10 -0
  75. package/scripts/lib/install-targets/cursor-project.js +10 -0
  76. package/scripts/lib/install-targets/helpers.js +89 -0
  77. package/scripts/lib/install-targets/opencode-home.js +10 -0
  78. package/scripts/lib/install-targets/registry.js +64 -0
  79. package/scripts/lib/orchestration-session.js +299 -0
  80. package/scripts/lib/package-manager.d.ts +119 -0
  81. package/scripts/lib/package-manager.js +431 -0
  82. package/scripts/lib/project-detect.js +428 -0
  83. package/scripts/lib/resolve-formatter.js +185 -0
  84. package/scripts/lib/session-adapters/canonical-session.js +138 -0
  85. package/scripts/lib/session-adapters/claude-history.js +149 -0
  86. package/scripts/lib/session-adapters/dmux-tmux.js +80 -0
  87. package/scripts/lib/session-adapters/registry.js +111 -0
  88. package/scripts/lib/session-aliases.d.ts +136 -0
  89. package/scripts/lib/session-aliases.js +481 -0
  90. package/scripts/lib/session-manager.d.ts +131 -0
  91. package/scripts/lib/session-manager.js +464 -0
  92. package/scripts/lib/shell-split.js +86 -0
  93. package/scripts/lib/skill-improvement/amendify.js +89 -0
  94. package/scripts/lib/skill-improvement/evaluate.js +59 -0
  95. package/scripts/lib/skill-improvement/health.js +118 -0
  96. package/scripts/lib/skill-improvement/observations.js +108 -0
  97. package/scripts/lib/tmux-worktree-orchestrator.js +491 -0
  98. package/scripts/lib/utils.d.ts +183 -0
  99. package/scripts/lib/utils.js +543 -0
  100. package/scripts/list-installed.js +90 -0
  101. package/scripts/orchestrate-codex-worker.sh +92 -0
  102. package/scripts/orchestrate-worktrees.js +108 -0
  103. package/scripts/orchestration-status.js +62 -0
  104. package/scripts/repair.js +97 -0
  105. package/scripts/session-inspect.js +150 -0
  106. package/scripts/setup-package-manager.js +204 -0
  107. package/scripts/skill-create-output.js +244 -0
  108. package/scripts/uninstall.js +96 -0
package/scripts/ecc.js ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const path = require('path');
5
+ const { listAvailableLanguages } = require('./lib/install-executor');
6
+
7
+ const COMMANDS = {
8
+ install: {
9
+ script: 'install-apply.js',
10
+ description: 'Install cc4pm content into a supported target',
11
+ },
12
+ plan: {
13
+ script: 'install-plan.js',
14
+ description: 'Inspect selective-install manifests and resolved plans',
15
+ },
16
+ 'install-plan': {
17
+ script: 'install-plan.js',
18
+ description: 'Alias for plan',
19
+ },
20
+ 'list-installed': {
21
+ script: 'list-installed.js',
22
+ description: 'Inspect install-state files for the current context',
23
+ },
24
+ doctor: {
25
+ script: 'doctor.js',
26
+ description: 'Diagnose missing or drifted ECC-managed files',
27
+ },
28
+ repair: {
29
+ script: 'repair.js',
30
+ description: 'Restore drifted or missing ECC-managed files',
31
+ },
32
+ 'session-inspect': {
33
+ script: 'session-inspect.js',
34
+ description: 'Emit canonical cc4pm session snapshots from dmux or Claude history targets',
35
+ },
36
+ uninstall: {
37
+ script: 'uninstall.js',
38
+ description: 'Remove ECC-managed files recorded in install-state',
39
+ },
40
+ };
41
+
42
+ const PRIMARY_COMMANDS = [
43
+ 'install',
44
+ 'plan',
45
+ 'list-installed',
46
+ 'doctor',
47
+ 'repair',
48
+ 'session-inspect',
49
+ 'uninstall',
50
+ ];
51
+
52
+ function showHelp(exitCode = 0) {
53
+ console.log(`
54
+ cc4pm selective-install CLI
55
+
56
+ Usage:
57
+ ecc <command> [args...]
58
+ ecc [install args...]
59
+
60
+ Commands:
61
+ ${PRIMARY_COMMANDS.map(command => ` ${command.padEnd(15)} ${COMMANDS[command].description}`).join('\n')}
62
+
63
+ Compatibility:
64
+ ecc-install Legacy install entrypoint retained for existing flows
65
+ ecc [args...] Without a command, args are routed to "install"
66
+ ecc help <command> Show help for a specific command
67
+
68
+ Examples:
69
+ ecc typescript
70
+ ecc install --profile developer --target claude
71
+ ecc plan --profile core --target cursor
72
+ ecc list-installed --json
73
+ ecc doctor --target cursor
74
+ ecc repair --dry-run
75
+ ecc session-inspect claude:latest
76
+ ecc uninstall --target antigravity --dry-run
77
+ `);
78
+
79
+ process.exit(exitCode);
80
+ }
81
+
82
+ function resolveCommand(argv) {
83
+ const args = argv.slice(2);
84
+
85
+ if (args.length === 0) {
86
+ return { mode: 'help' };
87
+ }
88
+
89
+ const [firstArg, ...restArgs] = args;
90
+
91
+ if (firstArg === '--help' || firstArg === '-h') {
92
+ return { mode: 'help' };
93
+ }
94
+
95
+ if (firstArg === 'help') {
96
+ return {
97
+ mode: 'help-command',
98
+ command: restArgs[0] || null,
99
+ };
100
+ }
101
+
102
+ if (COMMANDS[firstArg]) {
103
+ return {
104
+ mode: 'command',
105
+ command: firstArg,
106
+ args: restArgs,
107
+ };
108
+ }
109
+
110
+ const knownLegacyLanguages = listAvailableLanguages();
111
+ const shouldTreatAsImplicitInstall = (
112
+ firstArg.startsWith('-')
113
+ || knownLegacyLanguages.includes(firstArg)
114
+ );
115
+
116
+ if (!shouldTreatAsImplicitInstall) {
117
+ throw new Error(`Unknown command: ${firstArg}`);
118
+ }
119
+
120
+ return {
121
+ mode: 'command',
122
+ command: 'install',
123
+ args,
124
+ };
125
+ }
126
+
127
+ function runCommand(commandName, args) {
128
+ const command = COMMANDS[commandName];
129
+ if (!command) {
130
+ throw new Error(`Unknown command: ${commandName}`);
131
+ }
132
+
133
+ const result = spawnSync(
134
+ process.execPath,
135
+ [path.join(__dirname, command.script), ...args],
136
+ {
137
+ cwd: process.cwd(),
138
+ env: process.env,
139
+ encoding: 'utf8',
140
+ }
141
+ );
142
+
143
+ if (result.error) {
144
+ throw result.error;
145
+ }
146
+
147
+ if (result.stdout) {
148
+ process.stdout.write(result.stdout);
149
+ }
150
+
151
+ if (result.stderr) {
152
+ process.stderr.write(result.stderr);
153
+ }
154
+
155
+ if (typeof result.status === 'number') {
156
+ return result.status;
157
+ }
158
+
159
+ if (result.signal) {
160
+ throw new Error(`Command "${commandName}" terminated by signal ${result.signal}`);
161
+ }
162
+
163
+ return 1;
164
+ }
165
+
166
+ function main() {
167
+ try {
168
+ const resolution = resolveCommand(process.argv);
169
+
170
+ if (resolution.mode === 'help') {
171
+ showHelp(0);
172
+ }
173
+
174
+ if (resolution.mode === 'help-command') {
175
+ if (!resolution.command) {
176
+ showHelp(0);
177
+ }
178
+
179
+ if (!COMMANDS[resolution.command]) {
180
+ throw new Error(`Unknown command: ${resolution.command}`);
181
+ }
182
+
183
+ process.exitCode = runCommand(resolution.command, ['--help']);
184
+ return;
185
+ }
186
+
187
+ process.exitCode = runCommand(resolution.command, resolution.args);
188
+ } catch (error) {
189
+ console.error(`Error: ${error.message}`);
190
+ process.exit(1);
191
+ }
192
+ }
193
+
194
+ main();
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Auto-Tmux Dev Hook - Start dev servers in tmux/cmd automatically
4
+ *
5
+ * macOS/Linux: Runs dev server in a named tmux session (non-blocking).
6
+ * Falls back to original command if tmux is not installed.
7
+ * Windows: Opens dev server in a new cmd window (non-blocking).
8
+ *
9
+ * Runs before Bash tool use. If command is a dev server (npm run dev, pnpm dev, yarn dev, bun run dev),
10
+ * transforms it to run in a detached session.
11
+ *
12
+ * Benefits:
13
+ * - Dev server runs detached (doesn't block Claude Code)
14
+ * - Session persists (can run `tmux capture-pane -t <session> -p` to see logs on Unix)
15
+ * - Session name matches project directory (allows multiple projects simultaneously)
16
+ *
17
+ * Session management (Unix):
18
+ * - Checks tmux availability before transforming
19
+ * - Kills any existing session with the same name (clean restart)
20
+ * - Creates new detached session
21
+ * - Reports session name and how to view logs
22
+ *
23
+ * Session management (Windows):
24
+ * - Opens new cmd window with descriptive title
25
+ * - Allows multiple dev servers to run simultaneously
26
+ */
27
+
28
+ const path = require('path');
29
+ const { spawnSync } = require('child_process');
30
+
31
+ const MAX_STDIN = 1024 * 1024; // 1MB limit
32
+ let data = '';
33
+ process.stdin.setEncoding('utf8');
34
+
35
+ process.stdin.on('data', chunk => {
36
+ if (data.length < MAX_STDIN) {
37
+ const remaining = MAX_STDIN - data.length;
38
+ data += chunk.substring(0, remaining);
39
+ }
40
+ });
41
+
42
+ process.stdin.on('end', () => {
43
+ let input;
44
+ try {
45
+ input = JSON.parse(data);
46
+ const cmd = input.tool_input?.command || '';
47
+
48
+ // Detect dev server commands: npm run dev, pnpm dev, yarn dev, bun run dev
49
+ // Use word boundary (\b) to avoid matching partial commands
50
+ const devServerRegex = /(npm run dev\b|pnpm( run)? dev\b|yarn dev\b|bun run dev\b)/;
51
+
52
+ if (devServerRegex.test(cmd)) {
53
+ // Get session name from current directory basename, sanitize for shell safety
54
+ // e.g., /home/user/Portfolio → "Portfolio", /home/user/my-app-v2 → "my-app-v2"
55
+ const rawName = path.basename(process.cwd());
56
+ // Replace non-alphanumeric characters (except - and _) with underscore to prevent shell injection
57
+ const sessionName = rawName.replace(/[^a-zA-Z0-9_-]/g, '_') || 'dev';
58
+
59
+ if (process.platform === 'win32') {
60
+ // Windows: open in a new cmd window (non-blocking)
61
+ // Escape double quotes in cmd for cmd /k syntax
62
+ const escapedCmd = cmd.replace(/"/g, '""');
63
+ input.tool_input.command = `start "DevServer-${sessionName}" cmd /k "${escapedCmd}"`;
64
+ } else {
65
+ // Unix (macOS/Linux): Check tmux is available before transforming
66
+ const tmuxCheck = spawnSync('which', ['tmux'], { encoding: 'utf8' });
67
+ if (tmuxCheck.status === 0) {
68
+ // Escape single quotes for shell safety: 'text' -> 'text'\''text'
69
+ const escapedCmd = cmd.replace(/'/g, "'\\''");
70
+
71
+ // Build the transformed command:
72
+ // 1. Kill existing session (silent if doesn't exist)
73
+ // 2. Create new detached session with the dev command
74
+ // 3. Echo confirmation message with instructions for viewing logs
75
+ const transformedCmd = `SESSION="${sessionName}"; tmux kill-session -t "$SESSION" 2>/dev/null || true; tmux new-session -d -s "$SESSION" '${escapedCmd}' && echo "[Hook] Dev server started in tmux session '${sessionName}'. View logs: tmux capture-pane -t ${sessionName} -p -S -100"`;
76
+
77
+ input.tool_input.command = transformedCmd;
78
+ }
79
+ // else: tmux not found, pass through original command unchanged
80
+ }
81
+ }
82
+ process.stdout.write(JSON.stringify(input));
83
+ } catch {
84
+ // Invalid input — pass through original data unchanged
85
+ process.stdout.write(data);
86
+ }
87
+ process.exit(0);
88
+ });
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Stop Hook: Check for console.log statements in modified files
5
+ *
6
+ * Cross-platform (Windows, macOS, Linux)
7
+ *
8
+ * Runs after each response and checks if any modified JavaScript/TypeScript
9
+ * files contain console.log statements. Provides warnings to help developers
10
+ * remember to remove debug statements before committing.
11
+ *
12
+ * Exclusions: test files, config files, and scripts/ directory (where
13
+ * console.log is often intentional).
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const { isGitRepo, getGitModifiedFiles, readFile, log } = require('../lib/utils');
18
+
19
+ // Files where console.log is expected and should not trigger warnings
20
+ const EXCLUDED_PATTERNS = [
21
+ /\.test\.[jt]sx?$/,
22
+ /\.spec\.[jt]sx?$/,
23
+ /\.config\.[jt]s$/,
24
+ /scripts\//,
25
+ /__tests__\//,
26
+ /__mocks__\//,
27
+ ];
28
+
29
+ const MAX_STDIN = 1024 * 1024; // 1MB limit
30
+ let data = '';
31
+ process.stdin.setEncoding('utf8');
32
+
33
+ process.stdin.on('data', chunk => {
34
+ if (data.length < MAX_STDIN) {
35
+ const remaining = MAX_STDIN - data.length;
36
+ data += chunk.substring(0, remaining);
37
+ }
38
+ });
39
+
40
+ process.stdin.on('end', () => {
41
+ try {
42
+ if (!isGitRepo()) {
43
+ process.stdout.write(data);
44
+ process.exit(0);
45
+ }
46
+
47
+ const files = getGitModifiedFiles(['\\.tsx?$', '\\.jsx?$'])
48
+ .filter(f => fs.existsSync(f))
49
+ .filter(f => !EXCLUDED_PATTERNS.some(pattern => pattern.test(f)));
50
+
51
+ let hasConsole = false;
52
+
53
+ for (const file of files) {
54
+ const content = readFile(file);
55
+ if (content && content.includes('console.log')) {
56
+ log(`[Hook] WARNING: console.log found in ${file}`);
57
+ hasConsole = true;
58
+ }
59
+ }
60
+
61
+ if (hasConsole) {
62
+ log('[Hook] Remove console.log statements before committing');
63
+ }
64
+ } catch (err) {
65
+ log(`[Hook] check-console-log error: ${err.message}`);
66
+ }
67
+
68
+ // Always output the original data
69
+ process.stdout.write(data);
70
+ process.exit(0);
71
+ });
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { isHookEnabled } = require('../lib/hook-flags');
5
+
6
+ const [, , hookId, profilesCsv] = process.argv;
7
+ if (!hookId) {
8
+ process.stdout.write('yes');
9
+ process.exit(0);
10
+ }
11
+
12
+ process.stdout.write(isHookEnabled(hookId, { profiles: profilesCsv }) ? 'yes' : 'no');
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cost Tracker Hook
4
+ *
5
+ * Appends lightweight session usage metrics to ~/.claude/metrics/costs.jsonl.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const path = require('path');
11
+ const {
12
+ ensureDir,
13
+ appendFile,
14
+ getClaudeDir,
15
+ } = require('../lib/utils');
16
+
17
+ const MAX_STDIN = 1024 * 1024;
18
+ let raw = '';
19
+
20
+ function toNumber(value) {
21
+ const n = Number(value);
22
+ return Number.isFinite(n) ? n : 0;
23
+ }
24
+
25
+ function estimateCost(model, inputTokens, outputTokens) {
26
+ // Approximate per-1M-token blended rates. Conservative defaults.
27
+ const table = {
28
+ 'haiku': { in: 0.8, out: 4.0 },
29
+ 'sonnet': { in: 3.0, out: 15.0 },
30
+ 'opus': { in: 15.0, out: 75.0 },
31
+ };
32
+
33
+ const normalized = String(model || '').toLowerCase();
34
+ let rates = table.sonnet;
35
+ if (normalized.includes('haiku')) rates = table.haiku;
36
+ if (normalized.includes('opus')) rates = table.opus;
37
+
38
+ const cost = (inputTokens / 1_000_000) * rates.in + (outputTokens / 1_000_000) * rates.out;
39
+ return Math.round(cost * 1e6) / 1e6;
40
+ }
41
+
42
+ process.stdin.setEncoding('utf8');
43
+ process.stdin.on('data', chunk => {
44
+ if (raw.length < MAX_STDIN) {
45
+ const remaining = MAX_STDIN - raw.length;
46
+ raw += chunk.substring(0, remaining);
47
+ }
48
+ });
49
+
50
+ process.stdin.on('end', () => {
51
+ try {
52
+ const input = raw.trim() ? JSON.parse(raw) : {};
53
+ const usage = input.usage || input.token_usage || {};
54
+ const inputTokens = toNumber(usage.input_tokens || usage.prompt_tokens || 0);
55
+ const outputTokens = toNumber(usage.output_tokens || usage.completion_tokens || 0);
56
+
57
+ const model = String(input.model || input._cursor?.model || process.env.CLAUDE_MODEL || 'unknown');
58
+ const sessionId = String(process.env.CLAUDE_SESSION_ID || 'default');
59
+
60
+ const metricsDir = path.join(getClaudeDir(), 'metrics');
61
+ ensureDir(metricsDir);
62
+
63
+ const row = {
64
+ timestamp: new Date().toISOString(),
65
+ session_id: sessionId,
66
+ model,
67
+ input_tokens: inputTokens,
68
+ output_tokens: outputTokens,
69
+ estimated_cost_usd: estimateCost(model, inputTokens, outputTokens),
70
+ };
71
+
72
+ appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
73
+ } catch {
74
+ // Keep hook non-blocking.
75
+ }
76
+
77
+ process.stdout.write(raw);
78
+ });
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Doc file warning hook (PreToolUse - Write)
4
+ * Warns about non-standard documentation files.
5
+ * Exit code 0 always (warns only, never blocks).
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const path = require('path');
11
+
12
+ const MAX_STDIN = 1024 * 1024;
13
+ let data = '';
14
+
15
+ function isAllowedDocPath(filePath) {
16
+ const normalized = filePath.replace(/\\/g, '/');
17
+ const basename = path.basename(filePath);
18
+
19
+ if (!/\.(md|txt)$/i.test(filePath)) return true;
20
+
21
+ if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL|MEMORY|WORKLOG)\.md$/i.test(basename)) {
22
+ return true;
23
+ }
24
+
25
+ if (/\.claude\/(commands|plans|projects)\//.test(normalized)) {
26
+ return true;
27
+ }
28
+
29
+ if (/(^|\/)(docs|skills|\.history|memory)\//.test(normalized)) {
30
+ return true;
31
+ }
32
+
33
+ if (/\.plan\.md$/i.test(basename)) {
34
+ return true;
35
+ }
36
+
37
+ return false;
38
+ }
39
+
40
+ process.stdin.setEncoding('utf8');
41
+ process.stdin.on('data', c => {
42
+ if (data.length < MAX_STDIN) {
43
+ const remaining = MAX_STDIN - data.length;
44
+ data += c.substring(0, remaining);
45
+ }
46
+ });
47
+
48
+ process.stdin.on('end', () => {
49
+ try {
50
+ const input = JSON.parse(data);
51
+ const filePath = String(input.tool_input?.file_path || '');
52
+
53
+ if (filePath && !isAllowedDocPath(filePath)) {
54
+ console.error('[Hook] WARNING: Non-standard documentation file detected');
55
+ console.error(`[Hook] File: ${filePath}`);
56
+ console.error('[Hook] Consider consolidating into README.md or docs/ directory');
57
+ }
58
+ } catch {
59
+ // ignore parse errors
60
+ }
61
+
62
+ process.stdout.write(data);
63
+ });
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Continuous Learning - Session Evaluator
4
+ *
5
+ * Cross-platform (Windows, macOS, Linux)
6
+ *
7
+ * Runs on Stop hook to extract reusable patterns from Claude Code sessions.
8
+ * Reads transcript_path from stdin JSON (Claude Code hook input).
9
+ *
10
+ * Why Stop hook instead of UserPromptSubmit:
11
+ * - Stop runs once at session end (lightweight)
12
+ * - UserPromptSubmit runs every message (heavy, adds latency)
13
+ */
14
+
15
+ const path = require('path');
16
+ const fs = require('fs');
17
+ const {
18
+ getLearnedSkillsDir,
19
+ ensureDir,
20
+ readFile,
21
+ countInFile,
22
+ log
23
+ } = require('../lib/utils');
24
+
25
+ // Read hook input from stdin (Claude Code provides transcript_path via stdin JSON)
26
+ const MAX_STDIN = 1024 * 1024;
27
+ let stdinData = '';
28
+ process.stdin.setEncoding('utf8');
29
+
30
+ process.stdin.on('data', chunk => {
31
+ if (stdinData.length < MAX_STDIN) {
32
+ const remaining = MAX_STDIN - stdinData.length;
33
+ stdinData += chunk.substring(0, remaining);
34
+ }
35
+ });
36
+
37
+ process.stdin.on('end', () => {
38
+ main().catch(err => {
39
+ console.error('[ContinuousLearning] Error:', err.message);
40
+ process.exit(0);
41
+ });
42
+ });
43
+
44
+ async function main() {
45
+ // Parse stdin JSON to get transcript_path
46
+ let transcriptPath = null;
47
+ try {
48
+ const input = JSON.parse(stdinData);
49
+ transcriptPath = input.transcript_path;
50
+ } catch {
51
+ // Fallback: try env var for backwards compatibility
52
+ transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
53
+ }
54
+
55
+ // Get script directory to find config
56
+ const scriptDir = __dirname;
57
+ const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
58
+
59
+ // Default configuration
60
+ let minSessionLength = 10;
61
+ let learnedSkillsPath = getLearnedSkillsDir();
62
+
63
+ // Load config if exists
64
+ const configContent = readFile(configFile);
65
+ if (configContent) {
66
+ try {
67
+ const config = JSON.parse(configContent);
68
+ minSessionLength = config.min_session_length ?? 10;
69
+
70
+ if (config.learned_skills_path) {
71
+ // Handle ~ in path
72
+ learnedSkillsPath = config.learned_skills_path.replace(/^~/, require('os').homedir());
73
+ }
74
+ } catch (err) {
75
+ log(`[ContinuousLearning] Failed to parse config: ${err.message}, using defaults`);
76
+ }
77
+ }
78
+
79
+ // Ensure learned skills directory exists
80
+ ensureDir(learnedSkillsPath);
81
+
82
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
83
+ process.exit(0);
84
+ }
85
+
86
+ // Count user messages in session (allow optional whitespace around colon)
87
+ const messageCount = countInFile(transcriptPath, /"type"\s*:\s*"user"/g);
88
+
89
+ // Skip short sessions
90
+ if (messageCount < minSessionLength) {
91
+ log(`[ContinuousLearning] Session too short (${messageCount} messages), skipping`);
92
+ process.exit(0);
93
+ }
94
+
95
+ // Signal to Claude that session should be evaluated for extractable patterns
96
+ log(`[ContinuousLearning] Session has ${messageCount} messages - evaluate for extractable patterns`);
97
+ log(`[ContinuousLearning] Save learned skills to: ${learnedSkillsPath}`);
98
+
99
+ process.exit(0);
100
+ }