supermind-claude 2.1.1 → 4.0.2

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 (42) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/README.md +34 -46
  3. package/agents/code-reviewer.md +81 -0
  4. package/cli/commands/doctor.js +415 -79
  5. package/cli/commands/install.js +16 -17
  6. package/cli/commands/skill.js +164 -0
  7. package/cli/commands/uninstall.js +32 -3
  8. package/cli/commands/update.js +25 -4
  9. package/cli/index.js +16 -4
  10. package/cli/lib/agents.js +413 -0
  11. package/cli/lib/executor.js +365 -0
  12. package/cli/lib/hooks.js +8 -1
  13. package/cli/lib/logger.js +1 -1
  14. package/cli/lib/planning.js +502 -0
  15. package/cli/lib/platform.js +4 -0
  16. package/cli/lib/plugin.js +127 -0
  17. package/cli/lib/settings.js +2 -40
  18. package/cli/lib/skills.js +39 -2
  19. package/cli/lib/vendor-skills.js +594 -0
  20. package/hooks/bash-permissions.js +196 -176
  21. package/hooks/context-monitor.js +79 -0
  22. package/hooks/improvement-logger.js +94 -0
  23. package/hooks/pre-merge-checklist.js +102 -0
  24. package/hooks/session-start.js +109 -5
  25. package/hooks/statusline-command.js +115 -29
  26. package/package.json +4 -2
  27. package/skills/anti-rationalization/SKILL.md +38 -0
  28. package/skills/brainstorming/SKILL.md +165 -0
  29. package/skills/code-review/SKILL.md +144 -0
  30. package/skills/executing-plans/SKILL.md +138 -0
  31. package/skills/finishing-branches/SKILL.md +144 -0
  32. package/skills/project/SKILL.md +533 -0
  33. package/skills/quick/SKILL.md +178 -0
  34. package/skills/supermind/SKILL.md +58 -4
  35. package/skills/supermind-init/SKILL.md +48 -2
  36. package/skills/systematic-debugging/SKILL.md +129 -0
  37. package/skills/tdd/SKILL.md +179 -0
  38. package/skills/using-git-worktrees/SKILL.md +138 -0
  39. package/skills/verification-before-completion/SKILL.md +54 -0
  40. package/skills/writing-plans/SKILL.md +169 -0
  41. package/templates/CLAUDE.md +124 -62
  42. 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();
@@ -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 = path.join(SESSION_DIR, f);
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 = path.join(projectDir, "ARCHITECTURE.md");
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 = path.join(projectDir, "DESIGN.md");
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(path.join(projectDir, "CLAUDE.md"))) {
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 with box-drawing chars, context bar gradient, subagent tracking
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,38 @@ 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
222
+ const ORANGE = c(214); // folder name
153
223
  const GRAY = c(245); // labels
154
224
  const WHITE = c(255); // bright text
155
225
  const DKGRAY = c(237); // box chars
226
+ const GREEN = c(114); // context bar >50% remaining
227
+ const YELLOW = c(220); // context bar 25-50% remaining
228
+ const RED = c(196); // context bar <25% remaining
156
229
 
157
- // ─── Progress bar (teal -> sky -> rose gradient at 75%) ─────────────────
230
+ // ─── Context bar (color-coded by remaining capacity) ────────────────────
158
231
 
159
- function progressBar(pct, width = 20) {
160
- const filled = Math.round((pct / 100) * width);
232
+ function contextBar(usedPercent, width = 10) {
233
+ const filled = Math.round((usedPercent / 100) * width);
161
234
  const empty = width - filled;
162
- // Gradient: low=teal, mid=sky, high=rose
163
- let barColor = SKY;
164
- if (pct > 75) barColor = c(204);
165
- else if (pct > 50) barColor = c(220);
166
-
167
- const filledStr = barColor + "\u2501".repeat(filled);
168
- const emptyStr = SLATE + "\u2501".repeat(empty);
169
- return filledStr + emptyStr + R;
235
+ const remaining = 100 - usedPercent;
236
+
237
+ // Color by remaining capacity: green >50%, yellow 25-50%, red <25%
238
+ let barColor;
239
+ if (remaining > 50) barColor = GREEN;
240
+ else if (remaining >= 25) barColor = YELLOW;
241
+ else barColor = RED;
242
+
243
+ return `${SLATE}[${R}${barColor}${"\u2588".repeat(filled)}${SLATE}${"\u2591".repeat(empty)}${R}${SLATE}]${R}`;
170
244
  }
171
245
 
172
246
  // ─── Separators ─────────────────────────────────────────────────────────
@@ -174,24 +248,35 @@ process.stdin.on("end", () => {
174
248
  const SEP = `${DKGRAY} \u2502 ${R}`;
175
249
  const DOT = `${DKGRAY} \u00b7 ${R}`;
176
250
 
177
- // ─── Line 1: identity + location ────────────────────────────────────────
251
+ // ─── Line 1: identity + branch + context bar ───────────────────────────
178
252
 
179
253
  let line1 = `${DKGRAY}\u256d${R} `;
180
254
  line1 += `${TEAL}${BOLD}${user}${R}${GRAY}@${R}${TEAL}${host}${R}`;
181
255
  if (model) line1 += `${SEP}${ROSE}${model}${R}`;
182
- line1 += `${SEP}${AMBER}${cwdDisplay}${R}`;
183
- if (gitBranch) line1 += `${DOT}${MINT}${BOLD}${gitBranch}${R}`;
256
+ if (gitBranch) line1 += `${SEP}${MINT}${BOLD}${gitBranch}${R}`;
257
+
258
+ // Folder name (basename of cwd)
259
+ const folderName = cwdUnix.split("/").filter(Boolean).pop() || "";
260
+ if (folderName) line1 += `${SEP}${ORANGE}${BOLD}${folderName}${R}`;
184
261
 
185
- // ─── Line 2: metrics ───────────────────────────────────────────────────
262
+ // ─── Line 2: wave progress + executors + cost ──────────────────────────
186
263
 
187
264
  let line2 = `${DKGRAY}\u2570${R} `;
188
265
  const parts2 = [];
189
266
 
190
- if (usedPct != null) {
191
- const pct = Math.round(usedPct);
192
- const bar = progressBar(pct);
267
+ // Context bar + token counts (compact)
268
+ if (usedTokens != null && windowSize) {
269
+ const barPrefix = usedPct != null ? `${contextBar(Math.round(usedPct))} ` : "";
270
+ parts2.push(
271
+ `${barPrefix}${WHITE}${fmt(usedTokens)}${R}${GRAY}/${R}${WHITE}${fmt(windowSize)}${R}${GRAY} tokens${R}`,
272
+ );
273
+ }
274
+
275
+ // Wave progress (only when .planning/ has active progress)
276
+ if (waveProgress) {
277
+ const VIOLET = c(141);
193
278
  parts2.push(
194
- `${bar} ${WHITE}${BOLD}${pct}%${R}${GRAY} ctx${R}${DOT}${WHITE}${fmt(usedTokens)}${R}${GRAY}/${R}${WHITE}${fmt(windowSize)}${R}`,
279
+ `${VIOLET}\u25b8 Wave ${waveProgress.currentWave}/${waveProgress.totalWaves}${R}${DOT}${WHITE}${waveProgress.doneTasks}/${waveProgress.totalTasks}${R}${GRAY} tasks${R}`,
195
280
  );
196
281
  }
197
282
 
@@ -204,6 +289,7 @@ process.stdin.on("end", () => {
204
289
  parts2.push(`${CORAL}\u25c8 ${R}${GRAY}sb:${R}${CORAL}${supabaseRef}${R}`);
205
290
  }
206
291
 
292
+ // Active executors / agents
207
293
  if (activeAgents.length) {
208
294
  const CYAN = c(81);
209
295
  const names = activeAgents
@@ -214,7 +300,7 @@ process.stdin.on("end", () => {
214
300
  Math.floor(Date.now() / 100) % 10
215
301
  ];
216
302
  parts2.push(
217
- `${CYAN}${spinner} ${activeAgents.length} agent${activeAgents.length > 1 ? "s" : ""}${R}${GRAY}: ${names}${R}`,
303
+ `${CYAN}${spinner} ${activeAgents.length} executor${activeAgents.length > 1 ? "s" : ""}${R}${GRAY}: ${names}${R}`,
218
304
  );
219
305
  }
220
306
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "supermind-claude",
3
- "version": "2.1.1",
4
- "description": "Complete, opinionated Claude Code setup hooks, skills, status line, MCP servers, and living documentation",
3
+ "version": "4.0.2",
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.