claude-spp 0.1.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 (47) hide show
  1. package/LICENSE.txt +21 -0
  2. package/README.md +147 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +164 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/loader.d.ts +29 -0
  8. package/dist/config/loader.d.ts.map +1 -0
  9. package/dist/config/loader.js +81 -0
  10. package/dist/config/loader.js.map +1 -0
  11. package/dist/config/schema.d.ts +84 -0
  12. package/dist/config/schema.d.ts.map +1 -0
  13. package/dist/config/schema.js +104 -0
  14. package/dist/config/schema.js.map +1 -0
  15. package/dist/git/history.d.ts +55 -0
  16. package/dist/git/history.d.ts.map +1 -0
  17. package/dist/git/history.js +376 -0
  18. package/dist/git/history.js.map +1 -0
  19. package/dist/hooks/file-matcher.d.ts +22 -0
  20. package/dist/hooks/file-matcher.d.ts.map +1 -0
  21. package/dist/hooks/file-matcher.js +86 -0
  22. package/dist/hooks/file-matcher.js.map +1 -0
  23. package/dist/hooks/index.d.ts +4 -0
  24. package/dist/hooks/index.d.ts.map +1 -0
  25. package/dist/hooks/index.js +4 -0
  26. package/dist/hooks/index.js.map +1 -0
  27. package/dist/hooks/pre-tool-use.d.ts +35 -0
  28. package/dist/hooks/pre-tool-use.d.ts.map +1 -0
  29. package/dist/hooks/pre-tool-use.js +132 -0
  30. package/dist/hooks/pre-tool-use.js.map +1 -0
  31. package/dist/hooks/system-prompt.d.ts +9 -0
  32. package/dist/hooks/system-prompt.d.ts.map +1 -0
  33. package/dist/hooks/system-prompt.js +88 -0
  34. package/dist/hooks/system-prompt.js.map +1 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +13 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/init.d.ts +25 -0
  40. package/dist/init.d.ts.map +1 -0
  41. package/dist/init.js +274 -0
  42. package/dist/init.js.map +1 -0
  43. package/dist/stats.d.ts +40 -0
  44. package/dist/stats.d.ts.map +1 -0
  45. package/dist/stats.js +146 -0
  46. package/dist/stats.js.map +1 -0
  47. package/package.json +41 -0
@@ -0,0 +1,132 @@
1
+ import { isSppInitialized, loadConfig } from "../config/loader.js";
2
+ import { isSppInternalFile } from "./file-matcher.js";
3
+ import { getStats } from "../stats.js";
4
+ /**
5
+ * Tools that write to files
6
+ */
7
+ const WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
8
+ /**
9
+ * Extract file path from tool input
10
+ */
11
+ function extractFilePath(toolName, toolInput) {
12
+ if (toolName === "Write" || toolName === "Edit") {
13
+ const filePath = toolInput.file_path;
14
+ return typeof filePath === "string" ? filePath : null;
15
+ }
16
+ if (toolName === "NotebookEdit") {
17
+ const notebookPath = toolInput.notebook_path;
18
+ return typeof notebookPath === "string" ? notebookPath : null;
19
+ }
20
+ return null;
21
+ }
22
+ /**
23
+ * Create an allow response
24
+ */
25
+ function allowResponse() {
26
+ return {
27
+ hookSpecificOutput: {
28
+ hookEventName: "PreToolUse",
29
+ permissionDecision: "allow",
30
+ },
31
+ };
32
+ }
33
+ /**
34
+ * Create a deny response with reason
35
+ */
36
+ function denyResponse(reason) {
37
+ return {
38
+ hookSpecificOutput: {
39
+ hookEventName: "PreToolUse",
40
+ permissionDecision: "deny",
41
+ permissionDecisionReason: reason,
42
+ },
43
+ };
44
+ }
45
+ /**
46
+ * Pre-tool-use hook
47
+ * Called before Claude uses a tool
48
+ * Checks the work ratio before allowing writes
49
+ */
50
+ export function preToolUseHook(input) {
51
+ const { tool_name, tool_input, cwd } = input;
52
+ // Only process write-related tools
53
+ if (!WRITE_TOOLS.includes(tool_name)) {
54
+ return allowResponse();
55
+ }
56
+ // Check if SPP is initialized
57
+ if (!isSppInitialized(cwd)) {
58
+ return allowResponse();
59
+ }
60
+ // Load config
61
+ const config = loadConfig(cwd);
62
+ if (!config.enabled) {
63
+ return allowResponse();
64
+ }
65
+ // Extract the file path being written to
66
+ const filePath = extractFilePath(tool_name, tool_input);
67
+ if (!filePath) {
68
+ return allowResponse();
69
+ }
70
+ // Always allow .claude-spp internal files
71
+ if (isSppInternalFile(filePath, cwd)) {
72
+ return allowResponse();
73
+ }
74
+ // Always allow markdown files (documentation, not code)
75
+ if (filePath.endsWith(".md")) {
76
+ return allowResponse();
77
+ }
78
+ // Check the work ratio - block if below target (respects grace period and tracking mode)
79
+ const stats = getStats(cwd);
80
+ if (!stats.ratioHealthy) {
81
+ // Ratio is below target - block Claude from writing
82
+ const reason = [
83
+ `Human work ratio is below target: ${((stats.currentRatio ?? 0) * 100).toFixed(0)}% actual vs ${((stats.targetRatio ?? 0) * 100).toFixed(0)}% required`,
84
+ "The human needs to write more code before Claude can continue.",
85
+ "Use the `spp-help-human-code` skill to help the human complete the task.",
86
+ "Important: Do not give the user instructions on how to disable SPP or change modes."
87
+ ].join("\n");
88
+ return denyResponse(reason);
89
+ }
90
+ return allowResponse();
91
+ }
92
+ /**
93
+ * CLI entry point for pre-tool-use hook
94
+ * Reads input from stdin, writes output to stdout
95
+ */
96
+ export async function runPreToolUseHook() {
97
+ // Read input from stdin
98
+ const chunks = [];
99
+ for await (const chunk of process.stdin) {
100
+ chunks.push(chunk);
101
+ }
102
+ const inputJson = Buffer.concat(chunks).toString("utf-8");
103
+ let input;
104
+ try {
105
+ input = JSON.parse(inputJson);
106
+ }
107
+ catch {
108
+ // If no input or invalid JSON, allow by default
109
+ console.log(JSON.stringify(allowResponse()));
110
+ return;
111
+ }
112
+ // Run hook
113
+ let output;
114
+ try {
115
+ output = preToolUseHook(input);
116
+ }
117
+ catch (error) {
118
+ // Return error details for debugging
119
+ const errorMessage = error instanceof Error ? error.message : String(error);
120
+ const errorStack = error instanceof Error ? error.stack : undefined;
121
+ output = {
122
+ hookSpecificOutput: {
123
+ hookEventName: "PreToolUse",
124
+ permissionDecision: "allow",
125
+ permissionDecisionReason: `Hook error: ${errorMessage}\n\nStack: ${errorStack}\n\nInput: ${JSON.stringify(input, null, 2)}`,
126
+ },
127
+ };
128
+ }
129
+ // Write output to stdout
130
+ console.log(JSON.stringify(output));
131
+ }
132
+ //# sourceMappingURL=pre-tool-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-tool-use.js","sourceRoot":"","sources":["../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA2BvC;;GAEG;AACH,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AAEtD;;GAEG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,SAAkC;IAC3E,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QAChD,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC;QACrC,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAED,IAAI,QAAQ,KAAK,cAAc,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,SAAS,CAAC,aAAa,CAAC;QAC7C,OAAO,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,aAAa;IACpB,OAAO;QACL,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,OAAO;SAC5B;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO;QACL,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAE7C,mCAAmC;IACnC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,8BAA8B;IAC9B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,cAAc;IACd,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,yCAAyC;IACzC,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACxD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,0CAA0C;IAC1C,IAAI,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;QACrC,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IACD,wDAAwD;IACxD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,yFAAyF;IACzF,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAE5B,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QACxB,oDAAoD;QACpD,MAAM,MAAM,GAAG;YACb,qCAAqC,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY;YACvJ,gEAAgE;YAChE,0EAA0E;YAC1E,qFAAqF;SACtF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,wBAAwB;IACxB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE1D,IAAI,KAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,WAAW;IACX,IAAI,MAA4B,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qCAAqC;QACrC,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,MAAM,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,MAAM,GAAG;YACP,kBAAkB,EAAE;gBAClB,aAAa,EAAE,YAAY;gBAC3B,kBAAkB,EAAE,OAAO;gBAC3B,wBAAwB,EAAE,eAAe,YAAY,cAAc,UAAU,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aAC5H;SACF,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Generate the SPP system prompt injection
3
+ */
4
+ export declare function generateSystemPrompt(projectPath: string): string;
5
+ /**
6
+ * Generate a compact status line for the prompt
7
+ */
8
+ export declare function generateStatusLine(projectPath: string): string;
9
+ //# sourceMappingURL=system-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/hooks/system-prompt.ts"],"names":[],"mappings":"AAiBA;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA8DhE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAc9D"}
@@ -0,0 +1,88 @@
1
+ import { loadConfig, isSppInitialized } from "../config/loader.js";
2
+ import { calculateRatio, isRatioHealthy } from "../stats.js";
3
+ import { getEffectiveRatio, getCurrentMode } from "../config/schema.js";
4
+ import { getLineCounts } from "../git/history.js";
5
+ /**
6
+ * Calculate how many more commits/lines the human needs to reach the target ratio
7
+ */
8
+ function calculateCatchUp(humanValue, claudeValue, targetRatio) {
9
+ const total = humanValue + claudeValue;
10
+ if (targetRatio >= 1) {
11
+ // 100% human target - can never catch up if Claude has written anything
12
+ return claudeValue;
13
+ }
14
+ return Math.ceil((targetRatio * total - humanValue) / (1 - targetRatio));
15
+ }
16
+ /**
17
+ * Generate the SPP system prompt injection
18
+ */
19
+ export function generateSystemPrompt(projectPath) {
20
+ if (!isSppInitialized(projectPath)) {
21
+ return "";
22
+ }
23
+ const config = loadConfig(projectPath);
24
+ // If SPP is disabled, return empty
25
+ if (!config.enabled) {
26
+ return "";
27
+ }
28
+ const lineCounts = getLineCounts(projectPath);
29
+ const trackingMode = config.trackingMode ?? "commits";
30
+ const humanValue = trackingMode === "commits" ? lineCounts.humanCommits : lineCounts.humanLines;
31
+ const claudeValue = trackingMode === "commits" ? lineCounts.claudeCommits : lineCounts.claudeLines;
32
+ const unit = trackingMode === "commits" ? "commits" : "lines";
33
+ const currentRatio = calculateRatio(humanValue, claudeValue);
34
+ const targetRatio = getEffectiveRatio(config);
35
+ const isHealthy = isRatioHealthy(humanValue, claudeValue, targetRatio);
36
+ const currentMode = getCurrentMode(config);
37
+ const lines = [
38
+ "<spp>",
39
+ "# Simian Programmer Plugin Active",
40
+ "",
41
+ "You are operating in Simian Programmer mode. This mode helps the human maintain their programming skills",
42
+ "by ensuring they write a minimum percentage of the code themselves.",
43
+ "Help your human friend level up and stay sharp.",
44
+ "",
45
+ "## Current Status",
46
+ "",
47
+ `- **Mode:** ${currentMode.number}. ${currentMode.name} (${currentMode.description})`,
48
+ `- **Target ratio:** ${(targetRatio * 100).toFixed(0)}% human-written code`,
49
+ `- **Current ratio:** ${(currentRatio * 100).toFixed(0)}% human (${humanValue} ${unit}) / ${(100 - currentRatio * 100).toFixed(0)}% Claude (${claudeValue} ${unit})`,
50
+ `- **Status:** ${isHealthy ? "✅ Healthy" : "⚠️ Below target"}`,
51
+ "",
52
+ "A note on SPP tracking:",
53
+ "SPP tracks commits in git history, within a window and/or after a starting commit.",
54
+ "Commits that include 'Co-authored by: Claude...' in the message are counted as Claude commits.",
55
+ "Commits without that phrase are counted as human commits.",
56
+ "Therefore, if asked to commit human authored code, don't include the 'Co-authored by: Claude...' phrase",
57
+ "Also, if you write code, ask the user if they would like to commit it and DO include the 'Co-authored by: Claude' phrase",
58
+ "This way, SPP tracking will work properly",
59
+ ];
60
+ // Add rules based on ratio health
61
+ if (!isHealthy) {
62
+ const needed = calculateCatchUp(humanValue, claudeValue, targetRatio);
63
+ lines.push("## ⚠️ NOTICE");
64
+ lines.push("");
65
+ lines.push("The human coding ratio is below the target.");
66
+ lines.push(`The human needs to write **${needed} more ${unit}** to get back to a healthy ratio.`);
67
+ lines.push("You will be hard blocked from writing code (except .md files).");
68
+ lines.push("Instead of writing code, use the spp-human-task skill to help the human complete the coding task.");
69
+ }
70
+ lines.push("</spp>");
71
+ return lines.join("\n");
72
+ }
73
+ /**
74
+ * Generate a compact status line for the prompt
75
+ */
76
+ export function generateStatusLine(projectPath) {
77
+ const config = loadConfig(projectPath);
78
+ if (!config.enabled) {
79
+ return "";
80
+ }
81
+ const lineCounts = getLineCounts(projectPath);
82
+ const currentRatio = calculateRatio(lineCounts.humanLines, lineCounts.claudeLines);
83
+ const targetRatio = getEffectiveRatio(config);
84
+ const isHealthy = isRatioHealthy(lineCounts.humanLines, lineCounts.claudeLines, targetRatio);
85
+ const status = isHealthy ? "✅" : "⚠️";
86
+ return `[SPP ${status} ${(currentRatio * 100).toFixed(0)}%/${(targetRatio * 100).toFixed(0)}% human]`;
87
+ }
88
+ //# sourceMappingURL=system-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/hooks/system-prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { loadConfig, saveConfig, isSppInitialized, getSppDir } from "./config/loader.js";
2
+ export { ConfigSchema, DEFAULT_CONFIG, getEffectiveRatio, type Config, } from "./config/schema.js";
3
+ export { calculateRatio, isRatioHealthy, } from "./stats.js";
4
+ export { getLineCounts, clearCache, type LineCounts, } from "./git/history.js";
5
+ export { initializeSpp, isFullyInitialized, ensureInitialized } from "./init.js";
6
+ export { getStats, formatStats, type StatsResult } from "./stats.js";
7
+ export { generateSystemPrompt, generateStatusLine } from "./hooks/system-prompt.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,KAAK,MAAM,GACZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,UAAU,EACV,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGjF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGrE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Configuration
2
+ export { loadConfig, saveConfig, isSppInitialized, getSppDir } from "./config/loader.js";
3
+ export { ConfigSchema, DEFAULT_CONFIG, getEffectiveRatio, } from "./config/schema.js";
4
+ export { calculateRatio, isRatioHealthy, } from "./stats.js";
5
+ // Git History
6
+ export { getLineCounts, clearCache, } from "./git/history.js";
7
+ // Initialization
8
+ export { initializeSpp, isFullyInitialized, ensureInitialized } from "./init.js";
9
+ // Commands
10
+ export { getStats, formatStats } from "./stats.js";
11
+ // Hooks
12
+ export { generateSystemPrompt, generateStatusLine } from "./hooks/system-prompt.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,iBAAiB,GAElB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,cAAc;AACd,OAAO,EACL,aAAa,EACb,UAAU,GAEX,MAAM,kBAAkB,CAAC;AAE1B,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEjF,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAC;AAErE,QAAQ;AACR,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { type Config, type StatsWindow, type TrackingMode } from "./config/schema.js";
2
+ /**
3
+ * Install git post-commit hook for tracking human lines
4
+ * @throws Error if not in a git repository
5
+ */
6
+ export declare function installGitHook(projectPath: string): void;
7
+ export declare function promptUser(prompt: string): Promise<string>;
8
+ /**
9
+ * Initialize SPP in a project
10
+ * Creates .claude-spp directory with config
11
+ * @param projectPath Path to the project
12
+ * @param modeNumber Optional mode number to skip the mode prompt
13
+ * @param statsWindow Optional stats window to skip the stats window prompt
14
+ * @param trackingMode Optional tracking mode to skip the tracking mode prompt
15
+ */
16
+ export declare function initializeSpp(projectPath: string, modeNumber?: number, statsWindow?: StatsWindow, trackingMode?: TrackingMode): Promise<Config>;
17
+ /**
18
+ * Check if SPP is fully initialized
19
+ */
20
+ export declare function isFullyInitialized(projectPath: string): boolean;
21
+ /**
22
+ * Ensure SPP is initialized, initializing if needed
23
+ */
24
+ export declare function ensureInitialized(projectPath: string, modeNumber?: number, statsWindow?: StatsWindow, trackingMode?: TrackingMode): Promise<Config>;
25
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,MAAM,EAKX,KAAK,WAAW,EAGhB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAiE5B;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CA6CxD;AA+FD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYhE;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,MAAM,CAAC,CAyDjB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,MAAM,CAAC,CAKjB"}
package/dist/init.js ADDED
@@ -0,0 +1,274 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as readline from "node:readline";
4
+ import { execSync } from "node:child_process";
5
+ import { loadConfig, saveConfig, isSppInitialized, getSppDir } from "./config/loader.js";
6
+ import { DEFAULT_CONFIG, MODES, STATS_WINDOW_LABELS, StatsWindowSchema, TRACKING_MODE_LABELS, TrackingModeSchema, } from "./config/schema.js";
7
+ import { getTotalCommitCount, getHeadCommitHash } from "./git/history.js";
8
+ /**
9
+ * Add an entry to .gitignore
10
+ * Creates .gitignore if it doesn't exist
11
+ */
12
+ function addToGitignore(projectPath, entry) {
13
+ const gitignorePath = path.join(projectPath, ".gitignore");
14
+ if (!fs.existsSync(gitignorePath)) {
15
+ console.log("Creating .gitignore...");
16
+ fs.writeFileSync(gitignorePath, entry + "\n", "utf-8");
17
+ console.log(`Added "${entry}" to .gitignore`);
18
+ return;
19
+ }
20
+ const content = fs.readFileSync(gitignorePath, "utf-8");
21
+ const lines = content.split("\n").map(line => line.trim());
22
+ if (lines.includes(entry)) {
23
+ console.log(`"${entry}" already in .gitignore`);
24
+ return;
25
+ }
26
+ console.log(`Adding "${entry}" to .gitignore...`);
27
+ const newContent = content.trimEnd() + "\n" + entry + "\n";
28
+ fs.writeFileSync(gitignorePath, newContent, "utf-8");
29
+ console.log(`Added "${entry}" to .gitignore`);
30
+ }
31
+ /**
32
+ * Check if we're in a git repository
33
+ */
34
+ function isGitRepo(projectPath) {
35
+ try {
36
+ execSync("git rev-parse --git-dir", { cwd: projectPath, stdio: "ignore" });
37
+ return true;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ /**
44
+ * Check if the spp command is available globally
45
+ */
46
+ function isSppCommandAvailable() {
47
+ try {
48
+ execSync("which spp", { stdio: "ignore" });
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Prompt user to install claude-spp globally
57
+ */
58
+ function ensureGlobalInstall() {
59
+ if (!isSppCommandAvailable()) {
60
+ const packageName = "git+https://github.com/mlolson/claude-spp.git";
61
+ throw new Error(`spp command not found. Please install:\n npm install -g ${packageName}`);
62
+ }
63
+ }
64
+ /**
65
+ * Install git post-commit hook for tracking human lines
66
+ * @throws Error if not in a git repository
67
+ */
68
+ export function installGitHook(projectPath) {
69
+ // Verify this is a git repo
70
+ if (!isGitRepo(projectPath)) {
71
+ throw new Error("Not a git repository. Initialize git first with: git init");
72
+ }
73
+ const gitHooksDir = path.join(projectPath, ".git", "hooks");
74
+ const hookPath = path.join(gitHooksDir, "post-commit");
75
+ const sourceHook = path.join(projectPath, "src", "git", "hooks", "post-commit");
76
+ // Check if source hook exists
77
+ if (!fs.existsSync(sourceHook)) {
78
+ throw new Error(`Source hook not found at: ${sourceHook}`);
79
+ }
80
+ // Ensure hooks directory exists
81
+ if (!fs.existsSync(gitHooksDir)) {
82
+ fs.mkdirSync(gitHooksDir, { recursive: true });
83
+ }
84
+ // Read source hook content
85
+ const sourceContent = fs.readFileSync(sourceHook, "utf-8");
86
+ const sourceContentWithShebang = "#!/bin/bash\n" + sourceContent;
87
+ // Check if hook already contains our content
88
+ if (fs.existsSync(hookPath)) {
89
+ const existingContent = fs.readFileSync(hookPath, "utf-8");
90
+ if (existingContent.includes("# <SPP>")) {
91
+ fs.writeFileSync(hookPath, existingContent.replace(/# <SPP>[\s\S]*?# <\/SPP>/g, sourceContent));
92
+ }
93
+ else {
94
+ if (existingContent.trim() === "") {
95
+ // Hook exists but is empty - write full contents
96
+ fs.writeFileSync(hookPath, sourceContentWithShebang);
97
+ }
98
+ else {
99
+ // Hook exists and is not empty - append without shebang
100
+ const newContent = existingContent.trimEnd() + "\n\n" + sourceContent;
101
+ fs.writeFileSync(hookPath, newContent);
102
+ }
103
+ }
104
+ }
105
+ else {
106
+ fs.writeFileSync(hookPath, sourceContentWithShebang);
107
+ }
108
+ // Ensure the file is executable
109
+ fs.chmodSync(hookPath, 0o755);
110
+ }
111
+ /**
112
+ * Prompt user to select a mode interactively
113
+ */
114
+ async function promptForMode() {
115
+ console.log("\nAvailable modes:\n");
116
+ const maxNameLen = Math.max(...MODES.map(m => m.name.length));
117
+ for (const mode of MODES) {
118
+ const paddedName = mode.name.padEnd(maxNameLen);
119
+ const defaultMarker = mode.number === 4 ? " (default)" : "";
120
+ console.log(` ${mode.number}. ${paddedName} ${mode.description}${defaultMarker}`);
121
+ }
122
+ console.log("");
123
+ let modeNumber = undefined;
124
+ while (modeNumber === undefined) {
125
+ const userResponse = await promptUser(`Select a mode [1-${MODES.length}, or press Enter for ${MODES[DEFAULT_CONFIG.mode - 1].description}]: `);
126
+ if (userResponse === "") {
127
+ modeNumber = DEFAULT_CONFIG.mode;
128
+ }
129
+ else {
130
+ modeNumber = parseInt(userResponse, 10);
131
+ if (modeNumber < 1 || modeNumber > MODES.length) {
132
+ console.log(`Invalid mode: ${modeNumber}. Must be in range [1, ${MODES.length}]`);
133
+ modeNumber = undefined;
134
+ }
135
+ }
136
+ }
137
+ return modeNumber;
138
+ }
139
+ /**
140
+ * Prompt user to select a stats window interactively
141
+ */
142
+ async function promptForStatsWindow() {
143
+ const options = StatsWindowSchema.options;
144
+ console.log("\nStats window (time period for tracking commits):\n");
145
+ options.forEach((option, index) => {
146
+ const label = STATS_WINDOW_LABELS[option];
147
+ const defaultMarker = option === "oneWeek" ? " (default)" : "";
148
+ console.log(` ${index + 1}. ${label}${defaultMarker}`);
149
+ });
150
+ console.log("");
151
+ while (true) {
152
+ const userResponse = await promptUser(`Select a stats window [1-${options.length}, or press Enter for Last 7 days]: `);
153
+ if (userResponse === "") {
154
+ return "oneWeek";
155
+ }
156
+ const choice = parseInt(userResponse, 10);
157
+ if (choice >= 1 && choice <= options.length) {
158
+ return options[choice - 1];
159
+ }
160
+ console.log(`Invalid choice: ${userResponse}. Must be in range [1, ${options.length}]`);
161
+ }
162
+ }
163
+ /**
164
+ * Prompt user to select a tracking mode interactively
165
+ */
166
+ async function promptForTrackingMode() {
167
+ const options = TrackingModeSchema.options;
168
+ console.log("\nTracking mode (what to count for ratio calculation):\n");
169
+ options.forEach((option, index) => {
170
+ const label = TRACKING_MODE_LABELS[option];
171
+ const defaultMarker = option === "commits" ? " (default)" : "";
172
+ console.log(` ${index + 1}. ${label}${defaultMarker}`);
173
+ });
174
+ console.log("");
175
+ while (true) {
176
+ const userResponse = await promptUser(`Select a tracking mode [1-${options.length}, or press Enter for Commits]: `);
177
+ if (userResponse === "") {
178
+ return "commits";
179
+ }
180
+ const choice = parseInt(userResponse, 10);
181
+ if (choice >= 1 && choice <= options.length) {
182
+ return options[choice - 1];
183
+ }
184
+ console.log(`Invalid choice: ${userResponse}. Must be in range [1, ${options.length}]`);
185
+ }
186
+ }
187
+ async function promptShouldOverwriteInstall() {
188
+ const answer = await promptUser("An SPP installation already exists. Overwrite it? N/Y\n");
189
+ return answer.toLowerCase() === "y";
190
+ }
191
+ export async function promptUser(prompt) {
192
+ const rl = readline.createInterface({
193
+ input: process.stdin,
194
+ output: process.stdout,
195
+ });
196
+ return new Promise((resolve) => {
197
+ rl.question(prompt, (answer) => {
198
+ rl.close();
199
+ resolve(answer.trim());
200
+ });
201
+ });
202
+ }
203
+ /**
204
+ * Initialize SPP in a project
205
+ * Creates .claude-spp directory with config
206
+ * @param projectPath Path to the project
207
+ * @param modeNumber Optional mode number to skip the mode prompt
208
+ * @param statsWindow Optional stats window to skip the stats window prompt
209
+ * @param trackingMode Optional tracking mode to skip the tracking mode prompt
210
+ */
211
+ export async function initializeSpp(projectPath, modeNumber, statsWindow, trackingMode) {
212
+ // Ensure spp command is installed globally (required for hooks)
213
+ await ensureGlobalInstall();
214
+ const sppDir = getSppDir(projectPath);
215
+ // Create .claude-spp directory if it doesn't exist
216
+ if (fs.existsSync(sppDir)) {
217
+ if (await promptShouldOverwriteInstall()) {
218
+ console.log("Removing existing install...");
219
+ fs.rmSync(sppDir, { recursive: true, force: true });
220
+ }
221
+ else {
222
+ console.log("Aborting install...");
223
+ return await loadConfig(projectPath);
224
+ }
225
+ }
226
+ fs.mkdirSync(sppDir, { recursive: true });
227
+ // Prompt for mode if not provided
228
+ const selectedMode = modeNumber ?? await promptForMode();
229
+ if (selectedMode < 1 || selectedMode > MODES.length) {
230
+ throw new Error(`Invalid mode: ${selectedMode}. Must be in range [1, ${MODES.length}]`);
231
+ }
232
+ // Prompt for tracking mode if not provided
233
+ const selectedTrackingMode = trackingMode ?? await promptForTrackingMode();
234
+ // Prompt for stats window if not provided
235
+ const selectedStatsWindow = statsWindow ?? await promptForStatsWindow();
236
+ // Initialize config
237
+ const config = {
238
+ ...DEFAULT_CONFIG,
239
+ mode: selectedMode,
240
+ trackingMode: selectedTrackingMode,
241
+ statsWindow: selectedStatsWindow,
242
+ };
243
+ // For pre-existing repos with commits, set trackingStartCommit to HEAD
244
+ // This gives them a clean slate - only new commits after init will be tracked
245
+ const totalCommits = getTotalCommitCount(projectPath);
246
+ if (totalCommits > 0) {
247
+ const headCommit = getHeadCommitHash(projectPath);
248
+ if (headCommit) {
249
+ config.trackingStartCommit = headCommit;
250
+ }
251
+ }
252
+ saveConfig(projectPath, config);
253
+ // Update .gitignore to exclude SPP files
254
+ addToGitignore(projectPath, ".claude-spp/");
255
+ // Install git post-commit hook
256
+ installGitHook(projectPath);
257
+ return config;
258
+ }
259
+ /**
260
+ * Check if SPP is fully initialized
261
+ */
262
+ export function isFullyInitialized(projectPath) {
263
+ return isSppInitialized(projectPath);
264
+ }
265
+ /**
266
+ * Ensure SPP is initialized, initializing if needed
267
+ */
268
+ export async function ensureInitialized(projectPath, modeNumber, statsWindow, trackingMode) {
269
+ if (!isFullyInitialized(projectPath)) {
270
+ return initializeSpp(projectPath, modeNumber, statsWindow, trackingMode);
271
+ }
272
+ return loadConfig(projectPath);
273
+ }
274
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type Mode, type StatsWindow, type TrackingMode } from "./config/schema.js";
2
+ /**
3
+ * Calculate the current human work ratio from line counts
4
+ * Returns 1.0 if no work has been done yet (human is at 100% until Claude does something)
5
+ */
6
+ export declare function calculateRatio(humanLines: number, claudeLines: number): number;
7
+ /**
8
+ * Check if the human work ratio meets the target
9
+ */
10
+ export declare function isRatioHealthy(humanLines: number, claudeLines: number, targetRatio: number): boolean;
11
+ export interface StatsResult {
12
+ initialized: boolean;
13
+ enabled?: boolean;
14
+ mode?: Mode;
15
+ targetRatio?: number;
16
+ currentRatio?: number;
17
+ ratioHealthy?: boolean;
18
+ statsWindow?: StatsWindow;
19
+ trackingMode?: TrackingMode;
20
+ lines?: {
21
+ humanLines: number;
22
+ claudeLines: number;
23
+ humanCommits: number;
24
+ claudeCommits: number;
25
+ fromCache: boolean;
26
+ commitsScanned: number;
27
+ };
28
+ session?: {
29
+ startedAt: string;
30
+ };
31
+ }
32
+ /**
33
+ * Get current SPP statistics
34
+ */
35
+ export declare function getStats(projectPath: string): StatsResult;
36
+ /**
37
+ * Format stats for display
38
+ */
39
+ export declare function formatStats(stats: StatsResult): string;
40
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,IAAI,EACT,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAG5B;;;GAGG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAM9E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAEpG;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,OAAO,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAoCzD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA4FtD"}