supermind-claude 2.1.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +21 -0
- package/README.md +34 -46
- package/agents/code-reviewer.md +81 -0
- package/cli/commands/doctor.js +415 -79
- package/cli/commands/install.js +17 -18
- package/cli/commands/skill.js +164 -0
- package/cli/commands/uninstall.js +32 -3
- package/cli/commands/update.js +27 -5
- package/cli/index.js +16 -4
- package/cli/lib/agents.js +413 -0
- package/cli/lib/executor.js +365 -0
- package/cli/lib/hooks.js +8 -1
- package/cli/lib/logger.js +1 -1
- package/cli/lib/mcp.js +25 -5
- package/cli/lib/planning.js +502 -0
- package/cli/lib/platform.js +4 -0
- package/cli/lib/plugin.js +127 -0
- package/cli/lib/settings.js +2 -40
- package/cli/lib/skills.js +39 -2
- package/cli/lib/templates.js +48 -1
- package/cli/lib/vendor-skills.js +594 -0
- package/hooks/bash-permissions.js +196 -176
- package/hooks/context-monitor.js +79 -0
- package/hooks/improvement-logger.js +94 -0
- package/hooks/pre-merge-checklist.js +102 -0
- package/hooks/session-start.js +109 -5
- package/hooks/statusline-command.js +123 -29
- package/package.json +4 -2
- package/skills/anti-rationalization/SKILL.md +38 -0
- package/skills/brainstorming/SKILL.md +165 -0
- package/skills/code-review/SKILL.md +144 -0
- package/skills/executing-plans/SKILL.md +138 -0
- package/skills/finishing-branches/SKILL.md +144 -0
- package/skills/project/SKILL.md +533 -0
- package/skills/quick/SKILL.md +178 -0
- package/skills/supermind/SKILL.md +58 -4
- package/skills/supermind-init/SKILL.md +48 -2
- package/skills/systematic-debugging/SKILL.md +129 -0
- package/skills/tdd/SKILL.md +179 -0
- package/skills/using-git-worktrees/SKILL.md +138 -0
- package/skills/verification-before-completion/SKILL.md +54 -0
- package/skills/writing-plans/SKILL.md +169 -0
- package/templates/CLAUDE.md +124 -61
- package/cli/lib/plugins.js +0 -23
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Improvement Logger Hook — logs session git activity to JSONL
|
|
3
|
+
// Fires on: Stop (async)
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const { execFileSync } = require("child_process");
|
|
9
|
+
|
|
10
|
+
const LOG_FILE = path.join(os.homedir(), ".claude", "improvement-log.jsonl");
|
|
11
|
+
const MAX_SIZE = 10 * 1024 * 1024; // 10MB
|
|
12
|
+
|
|
13
|
+
function gitExec(args, cwd) {
|
|
14
|
+
return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getGitInfo(projectDir) {
|
|
18
|
+
const info = { branch: null, commits: [], filesChanged: 0 };
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
info.branch = gitExec(["rev-parse", "--abbrev-ref", "HEAD"], projectDir);
|
|
22
|
+
} catch {
|
|
23
|
+
// silently skip
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const logOutput = gitExec(
|
|
28
|
+
["log", "--oneline", "--since=4 hours ago"],
|
|
29
|
+
projectDir
|
|
30
|
+
);
|
|
31
|
+
info.commits = logOutput
|
|
32
|
+
? logOutput.split("\n").filter((line) => line.length > 0)
|
|
33
|
+
: [];
|
|
34
|
+
} catch {
|
|
35
|
+
// silently skip
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const diffOutput = gitExec(
|
|
40
|
+
["diff", "--name-only", "HEAD~1", "HEAD"],
|
|
41
|
+
projectDir
|
|
42
|
+
);
|
|
43
|
+
info.filesChanged = diffOutput
|
|
44
|
+
? diffOutput.split("\n").filter((line) => line.length > 0).length
|
|
45
|
+
: 0;
|
|
46
|
+
} catch {
|
|
47
|
+
try {
|
|
48
|
+
const diffOutput = gitExec(["diff", "--name-only"], projectDir);
|
|
49
|
+
info.filesChanged = diffOutput
|
|
50
|
+
? diffOutput.split("\n").filter((line) => line.length > 0).length
|
|
51
|
+
: 0;
|
|
52
|
+
} catch {
|
|
53
|
+
// silently skip
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return info;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function rotateLogs() {
|
|
61
|
+
try {
|
|
62
|
+
const stats = fs.statSync(LOG_FILE);
|
|
63
|
+
if (stats.size > MAX_SIZE) {
|
|
64
|
+
fs.renameSync(LOG_FILE, LOG_FILE + ".1");
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// file doesn't exist or stat failed — no rotation needed
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function main() {
|
|
72
|
+
const projectDir = process.env.PROJECT_DIR || process.cwd();
|
|
73
|
+
const sessionId = process.env.SESSION_ID || "unknown";
|
|
74
|
+
|
|
75
|
+
const gitInfo = getGitInfo(projectDir);
|
|
76
|
+
|
|
77
|
+
const entry = {
|
|
78
|
+
timestamp: new Date().toISOString(),
|
|
79
|
+
project: projectDir,
|
|
80
|
+
branch: gitInfo.branch,
|
|
81
|
+
sessionId,
|
|
82
|
+
commits: gitInfo.commits,
|
|
83
|
+
filesChanged: gitInfo.filesChanged,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
rotateLogs();
|
|
88
|
+
fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n");
|
|
89
|
+
} catch {
|
|
90
|
+
// Non-critical — silently fail
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook — advisory warnings before merging into main/master.
|
|
3
|
+
// Fires after any Bash command containing "git merge".
|
|
4
|
+
// Never blocks — outputs warnings only.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execFileSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function gitExec(args, cwd) {
|
|
13
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', timeout: 10000 }).trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isMergingToMainOrMaster(command) {
|
|
17
|
+
// Match "git merge" anywhere in the command (handles compound cmds)
|
|
18
|
+
if (!/\bgit\b.*\bmerge\b/.test(command)) return false;
|
|
19
|
+
|
|
20
|
+
// After "merge" keyword, find the target branch token
|
|
21
|
+
const afterMerge = command.replace(/^[\s\S]*?\bmerge\b/, '').trim();
|
|
22
|
+
const tokens = afterMerge.split(/\s+/).filter(t => t && !t.startsWith('-') && !t.startsWith('"') && !t.startsWith("'"));
|
|
23
|
+
for (const token of tokens) {
|
|
24
|
+
const bare = token.replace(/^[^/]+\//, ''); // strip "origin/" prefix
|
|
25
|
+
if (bare === 'main' || bare === 'master') return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getTargetBranch(command) {
|
|
31
|
+
const afterMerge = command.replace(/^[\s\S]*?\bmerge\b/, '').trim();
|
|
32
|
+
const tokens = afterMerge.split(/\s+/).filter(t => t && !t.startsWith('-'));
|
|
33
|
+
for (const token of tokens) {
|
|
34
|
+
const bare = token.replace(/^[^/]+\//, '');
|
|
35
|
+
if (bare === 'main' || bare === 'master') return token;
|
|
36
|
+
}
|
|
37
|
+
return 'main';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Check 1: ARCHITECTURE.md in branch diff ─────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function checkLivingDocs(projectDir, targetBranch) {
|
|
43
|
+
try {
|
|
44
|
+
const diffOutput = gitExec(['diff', '--name-only', `${targetBranch}...HEAD`], projectDir);
|
|
45
|
+
const changedFiles = diffOutput.split('\n').map(f => f.trim()).filter(Boolean);
|
|
46
|
+
const archChanged = changedFiles.some(f => f === 'ARCHITECTURE.md' || f.endsWith('/ARCHITECTURE.md'));
|
|
47
|
+
if (!archChanged) {
|
|
48
|
+
return 'ARCHITECTURE.md not updated in this branch';
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// Diff failed (e.g., no common history) — skip check
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function main() {
|
|
59
|
+
let input = '';
|
|
60
|
+
process.stdin.setEncoding('utf-8');
|
|
61
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
62
|
+
process.stdin.on('end', () => {
|
|
63
|
+
try {
|
|
64
|
+
const data = JSON.parse(input || '{}');
|
|
65
|
+
const command = data.tool_input?.command || '';
|
|
66
|
+
|
|
67
|
+
// Only fire on git merge commands targeting main/master
|
|
68
|
+
if (!command.includes('git') || !command.includes('merge')) {
|
|
69
|
+
process.stdout.write('{}');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!isMergingToMainOrMaster(command)) {
|
|
74
|
+
process.stdout.write('{}');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const projectDir = process.env.PROJECT_DIR || process.cwd();
|
|
79
|
+
const targetBranch = getTargetBranch(command);
|
|
80
|
+
const warnings = [];
|
|
81
|
+
|
|
82
|
+
// Check 1: ARCHITECTURE.md in diff
|
|
83
|
+
try {
|
|
84
|
+
const livingDocsWarning = checkLivingDocs(projectDir, targetBranch);
|
|
85
|
+
if (livingDocsWarning) warnings.push(livingDocsWarning);
|
|
86
|
+
} catch {
|
|
87
|
+
// skip
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (warnings.length === 0) {
|
|
91
|
+
process.stdout.write('{}');
|
|
92
|
+
} else {
|
|
93
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolUse', warnings } }));
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Hook errors must never surface — exit silently
|
|
97
|
+
process.stdout.write('{}');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main();
|
package/hooks/session-start.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Session Start Hook — loads previous session context + living docs summaries
|
|
3
3
|
// Fires on: SessionStart
|
|
4
|
-
// Outputs combined context: session summary + ARCHITECTURE.md + DESIGN.md
|
|
4
|
+
// Outputs combined context: session summary + ARCHITECTURE.md + DESIGN.md + .planning/
|
|
5
5
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
@@ -10,6 +10,36 @@ const os = require("os");
|
|
|
10
10
|
const SESSION_DIR = path.join(os.homedir(), ".claude", "sessions");
|
|
11
11
|
const MAX_AGE_DAYS = 7;
|
|
12
12
|
|
|
13
|
+
// ─── Path safety ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate that each segment is safe (no traversal, not absolute), then join
|
|
17
|
+
* onto a trusted base via string concatenation. This satisfies semgrep
|
|
18
|
+
* taint-tracking rules for path.join/path.resolve with untrusted input.
|
|
19
|
+
* NOTE: Duplicated from cli/lib/planning.js — hooks run standalone and cannot
|
|
20
|
+
* import from cli/lib/. Keep both copies in sync.
|
|
21
|
+
*/
|
|
22
|
+
function safeJoin(trustedBase, ...segments) {
|
|
23
|
+
for (const seg of segments) {
|
|
24
|
+
if (typeof seg !== "string" || seg.length === 0) {
|
|
25
|
+
throw new Error("safeJoin: segment must be a non-empty string");
|
|
26
|
+
}
|
|
27
|
+
if (path.isAbsolute(seg)) {
|
|
28
|
+
throw new Error("safeJoin: segment must not be an absolute path");
|
|
29
|
+
}
|
|
30
|
+
for (const part of seg.split(/[\\/]/)) {
|
|
31
|
+
if (part === "..") {
|
|
32
|
+
throw new Error("safeJoin: path traversal not allowed");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const joined = trustedBase + path.sep + segments.join(path.sep);
|
|
37
|
+
if (!joined.startsWith(trustedBase + path.sep)) {
|
|
38
|
+
throw new Error("safeJoin: resolved path escapes base directory");
|
|
39
|
+
}
|
|
40
|
+
return joined;
|
|
41
|
+
}
|
|
42
|
+
|
|
13
43
|
// ─── Session loading ─────────────────────────────────────────────────────────
|
|
14
44
|
|
|
15
45
|
function getLatestSession(projectDir) {
|
|
@@ -18,7 +48,7 @@ function getLatestSession(projectDir) {
|
|
|
18
48
|
const files = fs.readdirSync(SESSION_DIR)
|
|
19
49
|
.filter(f => f.endsWith(".json"))
|
|
20
50
|
.map(f => {
|
|
21
|
-
const filepath =
|
|
51
|
+
const filepath = safeJoin(SESSION_DIR, f);
|
|
22
52
|
const stat = fs.statSync(filepath);
|
|
23
53
|
return { filepath, mtime: stat.mtimeMs };
|
|
24
54
|
})
|
|
@@ -96,7 +126,7 @@ function formatLivingDocs(projectDir) {
|
|
|
96
126
|
const parts = [];
|
|
97
127
|
|
|
98
128
|
// ARCHITECTURE.md — always check
|
|
99
|
-
const archPath =
|
|
129
|
+
const archPath = safeJoin(projectDir, "ARCHITECTURE.md");
|
|
100
130
|
if (fs.existsSync(archPath)) {
|
|
101
131
|
try {
|
|
102
132
|
const content = fs.readFileSync(archPath, "utf-8");
|
|
@@ -108,7 +138,7 @@ function formatLivingDocs(projectDir) {
|
|
|
108
138
|
}
|
|
109
139
|
|
|
110
140
|
// DESIGN.md — only if it exists
|
|
111
|
-
const designPath =
|
|
141
|
+
const designPath = safeJoin(projectDir, "DESIGN.md");
|
|
112
142
|
if (fs.existsSync(designPath)) {
|
|
113
143
|
try {
|
|
114
144
|
const content = fs.readFileSync(designPath, "utf-8");
|
|
@@ -124,7 +154,7 @@ function formatLivingDocs(projectDir) {
|
|
|
124
154
|
|
|
125
155
|
function checkProjectHealth(projectDir) {
|
|
126
156
|
const missing = [];
|
|
127
|
-
if (!fs.existsSync(
|
|
157
|
+
if (!fs.existsSync(safeJoin(projectDir, "CLAUDE.md"))) {
|
|
128
158
|
missing.push("CLAUDE.md");
|
|
129
159
|
}
|
|
130
160
|
if (missing.length > 0) {
|
|
@@ -133,6 +163,76 @@ function checkProjectHealth(projectDir) {
|
|
|
133
163
|
return null;
|
|
134
164
|
}
|
|
135
165
|
|
|
166
|
+
// ─── Planning session detection ─────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
function formatPlanningContext(projectDir) {
|
|
169
|
+
const planningDir = safeJoin(projectDir, ".planning");
|
|
170
|
+
if (!fs.existsSync(planningDir)) return null;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Read roadmap to find active phase
|
|
174
|
+
const roadmapPath = safeJoin(planningDir, "roadmap.md");
|
|
175
|
+
if (!fs.existsSync(roadmapPath)) {
|
|
176
|
+
return "[Planning] Active .planning/ directory detected (initializing)";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const roadmapContent = fs.readFileSync(roadmapPath, "utf-8");
|
|
180
|
+
const phases = [];
|
|
181
|
+
for (const line of roadmapContent.split("\n")) {
|
|
182
|
+
const match = line.match(/^\|\s*(\d+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*$/);
|
|
183
|
+
if (match && match[2].trim()) {
|
|
184
|
+
phases.push({
|
|
185
|
+
phase: parseInt(match[1], 10),
|
|
186
|
+
status: match[2].trim(),
|
|
187
|
+
description: match[3].trim(),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (phases.length === 0) {
|
|
193
|
+
return "[Planning] Active .planning/ directory detected (no phases yet)";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const completed = phases.filter(p => p.status === "completed").length;
|
|
197
|
+
const active = phases.find(p => p.status !== "completed" && p.status !== "skipped");
|
|
198
|
+
|
|
199
|
+
if (!active) {
|
|
200
|
+
return `[Planning] All ${phases.length} phases completed`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Read progress.md from the active phase — validate phase number
|
|
204
|
+
const phaseNum = Number(active.phase);
|
|
205
|
+
if (!Number.isInteger(phaseNum) || phaseNum < 1) {
|
|
206
|
+
return "[Planning] Active .planning/ directory detected (invalid phase number)";
|
|
207
|
+
}
|
|
208
|
+
const phasesDir = safeJoin(planningDir, "phases");
|
|
209
|
+
const phaseDir = safeJoin(phasesDir, `phase-${phaseNum}`);
|
|
210
|
+
const progressPath = safeJoin(phaseDir, "progress.md");
|
|
211
|
+
let progressSummary = "";
|
|
212
|
+
if (fs.existsSync(progressPath)) {
|
|
213
|
+
const progressContent = fs.readFileSync(progressPath, "utf-8");
|
|
214
|
+
const entries = [];
|
|
215
|
+
for (const line of progressContent.split("\n")) {
|
|
216
|
+
const match = line.match(/^\|\s*(\d+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*$/);
|
|
217
|
+
if (match) {
|
|
218
|
+
entries.push({ wave: parseInt(match[1], 10), status: match[3].trim() });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (entries.length > 0) {
|
|
222
|
+
const done = entries.filter(e => e.status === "completed").length;
|
|
223
|
+
const currentWave = Math.max(
|
|
224
|
+
...entries.filter(e => e.status !== "completed").map(e => e.wave).concat([0])
|
|
225
|
+
);
|
|
226
|
+
progressSummary = `, Wave ${currentWave}, ${done}/${entries.length} tasks complete`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return `[Planning] Active planning session detected — Phase ${phaseNum} (${active.description})${progressSummary} [${completed}/${phases.length} phases done]`;
|
|
231
|
+
} catch {
|
|
232
|
+
return "[Planning] Active .planning/ directory detected (could not read state)";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
136
236
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
137
237
|
|
|
138
238
|
function main() {
|
|
@@ -153,6 +253,10 @@ function main() {
|
|
|
153
253
|
const docsContext = formatLivingDocs(projectDir);
|
|
154
254
|
if (docsContext) outputParts.push(docsContext);
|
|
155
255
|
|
|
256
|
+
// Planning session awareness
|
|
257
|
+
const planningContext = formatPlanningContext(projectDir);
|
|
258
|
+
if (planningContext) outputParts.push(planningContext);
|
|
259
|
+
|
|
156
260
|
console.log(outputParts.join("\n---\n"));
|
|
157
261
|
} catch (err) {
|
|
158
262
|
console.log("[Session] Hook error: " + err.message);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Claude Code status line — sleek terminal aesthetic
|
|
3
|
-
// Two-line display
|
|
3
|
+
// Two-line display: identity + context bar, wave progress + executors + cost
|
|
4
|
+
// Reads .planning/ state for wave progress, context-metrics.json for enhanced context display
|
|
4
5
|
|
|
5
6
|
const { execSync } = require("child_process");
|
|
6
|
-
const { readFileSync, statSync, openSync, readSync, closeSync } = require("fs");
|
|
7
|
+
const { readFileSync, writeFileSync, statSync, existsSync, openSync, readSync, closeSync } = require("fs");
|
|
7
8
|
const { join } = require("path");
|
|
8
9
|
|
|
9
10
|
let input = "";
|
|
@@ -22,12 +23,12 @@ process.stdin.on("end", () => {
|
|
|
22
23
|
let cwd = data?.workspace?.current_dir || data?.cwd || process.cwd();
|
|
23
24
|
let cwdUnix = cwd.replace(/\\/g, "/");
|
|
24
25
|
const home = (process.env.HOME || process.env.USERPROFILE || "").replace(/\\/g, "/");
|
|
25
|
-
let cwdDisplay = cwdUnix;
|
|
26
|
-
if (home && cwdDisplay.startsWith(home)) cwdDisplay = "~" + cwdDisplay.slice(home.length);
|
|
27
|
-
|
|
28
26
|
const model = data?.model?.display_name || process.env.CLAUDE_MODEL_NAME || "";
|
|
29
27
|
|
|
30
28
|
// Git branch (symbolic-ref with short hash fallback)
|
|
29
|
+
// Note: execSync is used here with hardcoded commands only (no user input),
|
|
30
|
+
// so shell injection is not a concern. This is a status line hook with no
|
|
31
|
+
// interactive input — all values come from process.env or the Claude Code data pipe.
|
|
31
32
|
let gitBranch = "";
|
|
32
33
|
try {
|
|
33
34
|
gitBranch = execSync("git symbolic-ref --short HEAD", {
|
|
@@ -122,6 +123,78 @@ process.stdin.on("end", () => {
|
|
|
122
123
|
const windowSize = data?.context_window?.context_window_size;
|
|
123
124
|
const cost = process.env.CLAUDE_SESSION_COST_USD;
|
|
124
125
|
|
|
126
|
+
// ─── Write context metrics for context-monitor hook ─────────────────────
|
|
127
|
+
|
|
128
|
+
let percentRemaining = null;
|
|
129
|
+
if (usedPct != null && windowSize) {
|
|
130
|
+
percentRemaining = Math.round((100 - usedPct) * 10) / 10;
|
|
131
|
+
try {
|
|
132
|
+
const metricsPath = join(process.env.HOME || process.env.USERPROFILE, ".claude", "context-metrics.json");
|
|
133
|
+
writeFileSync(metricsPath, JSON.stringify({
|
|
134
|
+
percentRemaining,
|
|
135
|
+
tokensUsed: usedTokens || 0,
|
|
136
|
+
tokensTotal: windowSize,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
}));
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Wave progress (from .planning/) ────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
let waveProgress = null;
|
|
145
|
+
try {
|
|
146
|
+
const planningDir = join(cwdUnix, ".planning");
|
|
147
|
+
if (existsSync(planningDir)) {
|
|
148
|
+
// Find active phase from roadmap.md
|
|
149
|
+
const roadmapPath = join(planningDir, "roadmap.md");
|
|
150
|
+
if (existsSync(roadmapPath)) {
|
|
151
|
+
const roadmapContent = readFileSync(roadmapPath, "utf8");
|
|
152
|
+
const roadmapLines = roadmapContent.split("\n");
|
|
153
|
+
let activePhase = null;
|
|
154
|
+
for (const line of roadmapLines) {
|
|
155
|
+
const match = line.match(/^\|\s*(\d+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*$/);
|
|
156
|
+
if (match && match[2].trim() !== "completed" && match[2].trim() !== "skipped") {
|
|
157
|
+
activePhase = parseInt(match[1], 10);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (activePhase != null) {
|
|
163
|
+
const progressPath = join(planningDir, "phases", `phase-${activePhase}`, "progress.md");
|
|
164
|
+
if (existsSync(progressPath)) {
|
|
165
|
+
const progressContent = readFileSync(progressPath, "utf8");
|
|
166
|
+
const entries = [];
|
|
167
|
+
for (const line of progressContent.split("\n")) {
|
|
168
|
+
const match = line.match(/^\|\s*(\d+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*$/);
|
|
169
|
+
if (match) {
|
|
170
|
+
entries.push({
|
|
171
|
+
wave: parseInt(match[1], 10),
|
|
172
|
+
status: match[3].trim(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (entries.length > 0) {
|
|
178
|
+
const totalTasks = entries.length;
|
|
179
|
+
const doneTasks = entries.filter(e => e.status === "completed").length;
|
|
180
|
+
const maxWave = Math.max(...entries.map(e => e.wave));
|
|
181
|
+
// Use the lowest incomplete wave as "current"
|
|
182
|
+
const incompleteWaves = entries.filter(e => e.status !== "completed").map(e => e.wave);
|
|
183
|
+
const currentWave = incompleteWaves.length > 0 ? Math.min(...incompleteWaves) : maxWave;
|
|
184
|
+
|
|
185
|
+
waveProgress = {
|
|
186
|
+
currentWave,
|
|
187
|
+
totalWaves: maxWave,
|
|
188
|
+
doneTasks,
|
|
189
|
+
totalTasks,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
197
|
+
|
|
125
198
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
126
199
|
|
|
127
200
|
function fmt(n) {
|
|
@@ -136,37 +209,37 @@ process.stdin.on("end", () => {
|
|
|
136
209
|
|
|
137
210
|
// 256-color ANSI sequences
|
|
138
211
|
const c = (n) => `\x1b[38;5;${n}m`;
|
|
139
|
-
const bg = (n) => `\x1b[48;5;${n}m`;
|
|
140
212
|
const R = "\x1b[0m";
|
|
141
213
|
const BOLD = "\x1b[1m";
|
|
142
|
-
const DIM = "\x1b[2m";
|
|
143
214
|
|
|
144
215
|
// Palette
|
|
145
216
|
const TEAL = c(80); // user@host
|
|
146
217
|
const ROSE = c(204); // model
|
|
147
|
-
const AMBER = c(215); // path
|
|
148
218
|
const MINT = c(114); // branch
|
|
149
|
-
const SKY = c(117); // ctx bar filled
|
|
150
219
|
const SLATE = c(239); // ctx bar empty / separators
|
|
151
220
|
const LILAC = c(183); // thinking
|
|
152
221
|
const CORAL = c(209); // supabase
|
|
153
222
|
const GRAY = c(245); // labels
|
|
154
223
|
const WHITE = c(255); // bright text
|
|
155
224
|
const DKGRAY = c(237); // box chars
|
|
225
|
+
const GREEN = c(114); // context bar >50% remaining
|
|
226
|
+
const YELLOW = c(220); // context bar 25-50% remaining
|
|
227
|
+
const RED = c(196); // context bar <25% remaining
|
|
156
228
|
|
|
157
|
-
// ───
|
|
229
|
+
// ─── Context bar (color-coded by remaining capacity) ────────────────────
|
|
158
230
|
|
|
159
|
-
function
|
|
160
|
-
const filled = Math.round((
|
|
231
|
+
function contextBar(usedPercent, width = 10) {
|
|
232
|
+
const filled = Math.round((usedPercent / 100) * width);
|
|
161
233
|
const empty = width - filled;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
234
|
+
const remaining = 100 - usedPercent;
|
|
235
|
+
|
|
236
|
+
// Color by remaining capacity: green >50%, yellow 25-50%, red <25%
|
|
237
|
+
let barColor;
|
|
238
|
+
if (remaining > 50) barColor = GREEN;
|
|
239
|
+
else if (remaining >= 25) barColor = YELLOW;
|
|
240
|
+
else barColor = RED;
|
|
241
|
+
|
|
242
|
+
return `${SLATE}[${R}${barColor}${"\u2588".repeat(filled)}${SLATE}${"\u2591".repeat(empty)}${R}${SLATE}]${R}`;
|
|
170
243
|
}
|
|
171
244
|
|
|
172
245
|
// ─── Separators ─────────────────────────────────────────────────────────
|
|
@@ -174,24 +247,44 @@ process.stdin.on("end", () => {
|
|
|
174
247
|
const SEP = `${DKGRAY} \u2502 ${R}`;
|
|
175
248
|
const DOT = `${DKGRAY} \u00b7 ${R}`;
|
|
176
249
|
|
|
177
|
-
// ─── Line 1: identity +
|
|
250
|
+
// ─── Line 1: identity + branch + context bar ───────────────────────────
|
|
178
251
|
|
|
179
252
|
let line1 = `${DKGRAY}\u256d${R} `;
|
|
180
253
|
line1 += `${TEAL}${BOLD}${user}${R}${GRAY}@${R}${TEAL}${host}${R}`;
|
|
181
254
|
if (model) line1 += `${SEP}${ROSE}${model}${R}`;
|
|
182
|
-
line1 += `${SEP}${
|
|
183
|
-
|
|
255
|
+
if (gitBranch) line1 += `${SEP}${MINT}${BOLD}${gitBranch}${R}`;
|
|
256
|
+
|
|
257
|
+
if (usedPct != null) {
|
|
258
|
+
const pct = Math.round(usedPct);
|
|
259
|
+
const remaining = 100 - pct;
|
|
260
|
+
const bar = contextBar(pct);
|
|
261
|
+
|
|
262
|
+
// Color the percentage by remaining capacity
|
|
263
|
+
let pctColor;
|
|
264
|
+
if (remaining > 50) pctColor = GREEN;
|
|
265
|
+
else if (remaining >= 25) pctColor = YELLOW;
|
|
266
|
+
else pctColor = RED;
|
|
184
267
|
|
|
185
|
-
|
|
268
|
+
line1 += `${SEP}${bar} ${pctColor}${BOLD}${pct}%${R}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ─── Line 2: wave progress + executors + cost ──────────────────────────
|
|
186
272
|
|
|
187
273
|
let line2 = `${DKGRAY}\u2570${R} `;
|
|
188
274
|
const parts2 = [];
|
|
189
275
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
276
|
+
// Token counts (compact)
|
|
277
|
+
if (usedTokens != null && windowSize) {
|
|
278
|
+
parts2.push(
|
|
279
|
+
`${WHITE}${fmt(usedTokens)}${R}${GRAY}/${R}${WHITE}${fmt(windowSize)}${R}${GRAY} tokens${R}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Wave progress (only when .planning/ has active progress)
|
|
284
|
+
if (waveProgress) {
|
|
285
|
+
const VIOLET = c(141);
|
|
193
286
|
parts2.push(
|
|
194
|
-
`${
|
|
287
|
+
`${VIOLET}\u25b8 Wave ${waveProgress.currentWave}/${waveProgress.totalWaves}${R}${DOT}${WHITE}${waveProgress.doneTasks}/${waveProgress.totalTasks}${R}${GRAY} tasks${R}`,
|
|
195
288
|
);
|
|
196
289
|
}
|
|
197
290
|
|
|
@@ -204,6 +297,7 @@ process.stdin.on("end", () => {
|
|
|
204
297
|
parts2.push(`${CORAL}\u25c8 ${R}${GRAY}sb:${R}${CORAL}${supabaseRef}${R}`);
|
|
205
298
|
}
|
|
206
299
|
|
|
300
|
+
// Active executors / agents
|
|
207
301
|
if (activeAgents.length) {
|
|
208
302
|
const CYAN = c(81);
|
|
209
303
|
const names = activeAgents
|
|
@@ -214,7 +308,7 @@ process.stdin.on("end", () => {
|
|
|
214
308
|
Math.floor(Date.now() / 100) % 10
|
|
215
309
|
];
|
|
216
310
|
parts2.push(
|
|
217
|
-
`${CYAN}${spinner} ${activeAgents.length}
|
|
311
|
+
`${CYAN}${spinner} ${activeAgents.length} executor${activeAgents.length > 1 ? "s" : ""}${R}${GRAY}: ${names}${R}`,
|
|
218
312
|
);
|
|
219
313
|
}
|
|
220
314
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supermind-claude",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
|
+
"description": "Unified skill engine for Claude Code — execution infrastructure meets behavioral discipline",
|
|
5
5
|
"bin": {
|
|
6
6
|
"supermind-claude": "cli/index.js",
|
|
7
7
|
"supermind": "cli/index.js"
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
"cli/",
|
|
11
11
|
"hooks/",
|
|
12
12
|
"skills/",
|
|
13
|
+
"agents/",
|
|
13
14
|
"templates/",
|
|
14
15
|
"airis/",
|
|
16
|
+
".claude-plugin/",
|
|
15
17
|
".env.example"
|
|
16
18
|
],
|
|
17
19
|
"keywords": [
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!-- Forked from obra/superpowers (MIT license) by Jesse Vincent and Prime Radiant. Adapted for Supermind executor injection. -->
|
|
2
|
+
---
|
|
3
|
+
name: anti-rationalization
|
|
4
|
+
description: Blocks common LLM rationalizations for skipping steps — injected into all executors
|
|
5
|
+
injects_into: [all]
|
|
6
|
+
forked_from: obra/superpowers (MIT)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Anti-Rationalization
|
|
10
|
+
|
|
11
|
+
**These are not suggestions. They are constraints.** The executor follows them or fails the completion contract.
|
|
12
|
+
|
|
13
|
+
## The Rule
|
|
14
|
+
|
|
15
|
+
If you catch yourself rationalizing why a step can be skipped, that is the step most likely to matter.
|
|
16
|
+
|
|
17
|
+
## Common Rationalizations
|
|
18
|
+
|
|
19
|
+
| Rationalization | Reality |
|
|
20
|
+
|----------------|---------|
|
|
21
|
+
| "This is too simple to need tests" | Simple code with tests stays simple. Simple code without tests becomes complex bugs. |
|
|
22
|
+
| "I'll just do this one quick thing" | Quick things done carelessly create slow debugging later. |
|
|
23
|
+
| "The user didn't ask for tests" | The completion contract requires tests. Follow the contract. |
|
|
24
|
+
| "I can skip verification, it obviously works" | Obvious correctness is the most common source of bugs. Verify. |
|
|
25
|
+
| "This refactor doesn't need tests since behavior isn't changing" | Refactors without tests have no proof behavior was preserved. |
|
|
26
|
+
| "Let me just fix this real quick without investigating" | Fixes without investigation are guesses. Investigate first. |
|
|
27
|
+
| "I know what the problem is" | Knowing is not proving. Show evidence. |
|
|
28
|
+
| "This is just a config change" | Config changes can break everything. Verify the system still works. |
|
|
29
|
+
|
|
30
|
+
## How to Apply
|
|
31
|
+
|
|
32
|
+
When you form a thought that matches any rationalization above — or any thought that argues for skipping a required step:
|
|
33
|
+
|
|
34
|
+
1. **Stop.** Recognize it as a rationalization.
|
|
35
|
+
2. **Do the step anyway.** The rationalization is evidence the step matters.
|
|
36
|
+
3. **If the step turns out to be unnecessary,** it cost seconds. If skipping it introduced a bug, it costs minutes or hours.
|
|
37
|
+
|
|
38
|
+
The cost of doing an "unnecessary" step is always lower than the cost of skipping a necessary one.
|