@slamb2k/mad-skills 2.0.44 → 2.0.46

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.
@@ -18,6 +18,21 @@
18
18
  "category": "development",
19
19
  "homepage": "https://github.com/slamb2k/mad-skills",
20
20
  "tags": ["planning", "tdd", "architecture", "llm-review", "implementation"]
21
+ },
22
+ {
23
+ "name": "squiz",
24
+ "description": "Browser automation that works on Windows, WSL, remote VMs, and macOS — no Chrome extension required. Token-efficient agent-browser CLI with context isolation.",
25
+ "author": {
26
+ "name": "slamb2k",
27
+ "url": "https://github.com/slamb2k"
28
+ },
29
+ "source": {
30
+ "source": "github",
31
+ "repo": "slamb2k/squiz"
32
+ },
33
+ "category": "browser-automation",
34
+ "homepage": "https://github.com/slamb2k/squiz",
35
+ "tags": ["browser", "automation", "testing", "scraping", "screenshots"]
21
36
  }
22
37
  ]
23
38
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mad-skills",
3
3
  "description": "AI-assisted planning, development and governance tools",
4
- "version": "2.0.44",
4
+ "version": "2.0.46",
5
5
  "author": {
6
6
  "name": "slamb2k",
7
7
  "url": "https://github.com/slamb2k"
@@ -53,4 +53,17 @@ module.exports = {
53
53
  pythonFiles: ['pyproject.toml', 'requirements.txt', 'setup.py'],
54
54
 
55
55
  taskList: { minCommits: 20, minFiles: 30 },
56
+
57
+ pluginHealth: {
58
+ claudeMem: {
59
+ maxObservations: 20,
60
+ maxSessionCount: 5,
61
+ recommendedSkipTools: [
62
+ 'Read', 'Glob', 'Grep', 'ToolSearch', 'Agent', 'WebSearch', 'WebFetch',
63
+ ],
64
+ },
65
+ hookify: {
66
+ rulePattern: /^hookify\..*\.local\.md$/,
67
+ },
68
+ },
56
69
  };
@@ -1,10 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  const { existsSync } = require('fs');
4
- const { join, dirname, basename } = require('path');
4
+ const { join, dirname, basename, resolve } = require('path');
5
5
  const config = require('./config.cjs');
6
6
  const { git, readJson, countFiles } = require('./utils.cjs');
7
7
 
8
+ /** Normalize path separators for reliable comparison on Windows. */
9
+ function normalizePath(p) {
10
+ return resolve(p).replace(/\\/g, '/');
11
+ }
12
+
8
13
  /**
9
14
  * Validate git repository state.
10
15
  * Returns { gitRoot: string|null }.
@@ -25,7 +30,7 @@ function checkGit(projectDir, output) {
25
30
  return { gitRoot: null };
26
31
  }
27
32
 
28
- if (gitRoot !== projectDir) {
33
+ if (normalizePath(gitRoot) !== normalizePath(projectDir)) {
29
34
  checkNestedGit(projectDir, gitRoot, output);
30
35
  }
31
36
 
@@ -33,11 +38,12 @@ function checkGit(projectDir, output) {
33
38
  }
34
39
 
35
40
  function checkNestedGit(projectDir, gitRoot, output) {
36
- // Calculate depth
41
+ // Calculate depth — normalize separators for reliable comparison on Windows
37
42
  let depth = 0;
38
- let check = projectDir;
39
- while (check !== gitRoot && check !== '/') {
40
- check = dirname(check);
43
+ let check = normalizePath(projectDir);
44
+ const normalizedRoot = normalizePath(gitRoot);
45
+ while (check !== normalizedRoot && check !== '/' && depth < 20) {
46
+ check = normalizePath(dirname(check));
41
47
  depth++;
42
48
  }
43
49
 
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const { join } = require('path');
4
+ const { homedir } = require('os');
5
+ const { readdirSync } = require('fs');
6
+ const config = require('./config.cjs');
7
+ const state = require('./state.cjs');
8
+ const { readJson } = require('./utils.cjs');
9
+
10
+ /**
11
+ * Plugin Health — detect performance anti-patterns in companion plugins.
12
+ *
13
+ * Checks:
14
+ * - Hookify enabled with no rules (pure overhead)
15
+ * - claude-mem missing SKIP_TOOLS for read-only tools
16
+ * - claude-mem context injection too high (when OMC also active)
17
+ *
18
+ * Signals are weight=0 (informational only, won't trigger staleness prompt).
19
+ */
20
+
21
+ function checkPluginHealth(projectDir, output) {
22
+ const prefs = state.loadPrefs(projectDir);
23
+ if (prefs.pluginHealthDismissed) return;
24
+
25
+ const settings = readJson(join(homedir(), '.claude', 'settings.json'));
26
+ if (!settings) return; // No global settings — nothing to check
27
+
28
+ const plugins = settings.enabledPlugins || {};
29
+
30
+ checkHookify(projectDir, plugins, output);
31
+ checkClaudeMem(plugins, output);
32
+ }
33
+
34
+ // ─── hookify ──────────────────────────────────────────────────────────
35
+
36
+ function checkHookify(projectDir, plugins, output) {
37
+ const hookifyKey = Object.keys(plugins).find(k => k.includes('hookify'));
38
+ if (!hookifyKey || plugins[hookifyKey] !== true) return; // Not enabled
39
+
40
+ // Count rule files in project .claude/ directory
41
+ let ruleCount = 0;
42
+ try {
43
+ const projectClaudeDir = join(projectDir, '.claude');
44
+ const files = readdirSync(projectClaudeDir);
45
+ ruleCount = files.filter(f => config.pluginHealth.hookify.rulePattern.test(f)).length;
46
+ } catch { /* directory doesn't exist — zero rules */ }
47
+
48
+ // Also check global .claude/ directory
49
+ try {
50
+ const globalClaudeDir = join(homedir(), '.claude');
51
+ const files = readdirSync(globalClaudeDir);
52
+ ruleCount += files.filter(f => config.pluginHealth.hookify.rulePattern.test(f)).length;
53
+ } catch { /* noop */ }
54
+
55
+ if (ruleCount === 0) {
56
+ output.signals.push(
57
+ '\u26A0 Hookify enabled but no rules configured \u2014 fires on every tool call for nothing. Run /brace or disable in settings',
58
+ );
59
+ }
60
+ }
61
+
62
+ // ─── claude-mem ───────────────────────────────────────────────────────
63
+
64
+ function checkClaudeMem(plugins, output) {
65
+ const memKey = Object.keys(plugins).find(k => k.includes('claude-mem'));
66
+ if (!memKey || plugins[memKey] !== true) return; // Not enabled
67
+
68
+ const memSettings = readJson(join(homedir(), '.claude-mem', 'settings.json'));
69
+ if (!memSettings) return; // No settings file
70
+
71
+ // Check SKIP_TOOLS for read-only tools
72
+ const currentSkip = (memSettings.CLAUDE_MEM_SKIP_TOOLS || '')
73
+ .split(',')
74
+ .map(t => t.trim())
75
+ .filter(Boolean);
76
+
77
+ const missing = config.pluginHealth.claudeMem.recommendedSkipTools
78
+ .filter(t => !currentSkip.includes(t));
79
+
80
+ if (missing.length > 0) {
81
+ output.signals.push(
82
+ `\u26A0 claude-mem observing read-only tools (${missing.join(', ')}) \u2014 run /brace to optimise`,
83
+ );
84
+ }
85
+
86
+ // Check context injection levels (only flag if OMC is also active)
87
+ const omcKey = Object.keys(plugins).find(k => k.includes('oh-my-claudecode'));
88
+ const omcEnabled = omcKey && plugins[omcKey] === true;
89
+
90
+ if (omcEnabled) {
91
+ const obs = parseInt(memSettings.CLAUDE_MEM_CONTEXT_OBSERVATIONS, 10);
92
+ if (!isNaN(obs) && obs > config.pluginHealth.claudeMem.maxObservations) {
93
+ output.signals.push(
94
+ `\u26A0 claude-mem CONTEXT_OBSERVATIONS=${obs} (recommended \u226420) \u2014 may bloat session context`,
95
+ );
96
+ }
97
+
98
+ const sessions = parseInt(memSettings.CLAUDE_MEM_CONTEXT_SESSION_COUNT, 10);
99
+ if (!isNaN(sessions) && sessions > config.pluginHealth.claudeMem.maxSessionCount) {
100
+ output.signals.push(
101
+ `\u26A0 claude-mem CONTEXT_SESSION_COUNT=${sessions} (recommended \u22645) \u2014 may bloat session context`,
102
+ );
103
+ }
104
+ }
105
+ }
106
+
107
+ module.exports = { checkPluginHealth };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { mkdirSync, writeFileSync, readFileSync, unlinkSync, existsSync } = require('fs');
3
+ const { mkdirSync, writeFileSync, readFileSync, unlinkSync, existsSync, renameSync } = require('fs');
4
4
  const { join } = require('path');
5
5
  const { createHash } = require('crypto');
6
6
  const { homedir } = require('os');
@@ -19,13 +19,45 @@ function statePath(projectDir) {
19
19
  return join(STATE_DIR, `${projectKey(projectDir)}.json`);
20
20
  }
21
21
 
22
+ /** Atomic write: write to .tmp then rename to avoid partial reads. */
22
23
  function save(projectDir, data) {
23
24
  ensureDir();
24
- writeFileSync(statePath(projectDir), JSON.stringify({
25
+ const target = statePath(projectDir);
26
+ const tmp = `${target}.tmp`;
27
+ writeFileSync(tmp, JSON.stringify({
25
28
  ...data,
26
29
  projectDir,
27
30
  timestamp: Date.now(),
28
31
  }, null, 2));
32
+ try {
33
+ renameSync(tmp, target);
34
+ } catch {
35
+ // Rename failed (rare on Windows if target locked) — fall back to direct write
36
+ writeFileSync(target, readFileSync(tmp, 'utf-8'));
37
+ try { unlinkSync(tmp); } catch { /* noop */ }
38
+ }
39
+ }
40
+
41
+ /** Write an in-progress marker so remind() knows to wait. */
42
+ function saveInProgress(projectDir) {
43
+ ensureDir();
44
+ const target = statePath(projectDir);
45
+ const tmp = `${target}.tmp`;
46
+ writeFileSync(tmp, JSON.stringify({
47
+ status: 'in-progress',
48
+ projectDir,
49
+ timestamp: Date.now(),
50
+ }, null, 2));
51
+ try {
52
+ renameSync(tmp, target);
53
+ } catch {
54
+ writeFileSync(target, JSON.stringify({
55
+ status: 'in-progress',
56
+ projectDir,
57
+ timestamp: Date.now(),
58
+ }, null, 2));
59
+ try { unlinkSync(tmp); } catch { /* noop */ }
60
+ }
29
61
  }
30
62
 
31
63
  function load(projectDir) {
@@ -42,13 +74,40 @@ function clear(projectDir) {
42
74
  try { unlinkSync(statePath(projectDir)); } catch { /* noop */ }
43
75
  }
44
76
 
45
- /** Dedup: true if check ran within the last `seconds`. */
77
+ /**
78
+ * Dedup: true if check ran (or is running) within the last `seconds`.
79
+ * Also returns true if a background check is in-progress.
80
+ */
46
81
  function isRecentlyChecked(projectDir, seconds = 5) {
47
82
  const data = load(projectDir);
48
83
  if (!data) return false;
84
+ if (data.status === 'in-progress') return true;
49
85
  return (Date.now() - data.timestamp) < seconds * 1000;
50
86
  }
51
87
 
88
+ /**
89
+ * Wait for the background check to complete.
90
+ * Polls until state file has actual results (not in-progress), or timeout.
91
+ * Returns loaded data or null on timeout / no data.
92
+ */
93
+ function waitForReady(projectDir, timeoutMs = 4000, intervalMs = 200) {
94
+ const deadline = Date.now() + timeoutMs;
95
+ while (Date.now() < deadline) {
96
+ const data = load(projectDir);
97
+ if (!data) return null; // No state file at all — nothing pending
98
+ if (data.status === 'in-progress') {
99
+ // Check for stale in-progress marker (background worker crashed)
100
+ if (Date.now() - data.timestamp > 30000) return null;
101
+ // Busy-wait with sleep
102
+ const sleepUntil = Date.now() + intervalMs;
103
+ while (Date.now() < sleepUntil) { /* spin */ }
104
+ continue;
105
+ }
106
+ return data; // Ready — has actual results
107
+ }
108
+ return null; // Timed out
109
+ }
110
+
52
111
  // ─── persistent per-project preferences ───────────────────────────────
53
112
 
54
113
  function prefsPath(projectDir) {
@@ -70,4 +129,33 @@ function savePrefs(projectDir, prefs) {
70
129
  writeFileSync(prefsPath(projectDir), JSON.stringify(prefs, null, 2));
71
130
  }
72
131
 
73
- module.exports = { save, load, clear, isRecentlyChecked, loadPrefs, savePrefs };
132
+ // ─── pending build marker ─────────────────────────────────────────────
133
+
134
+ function pendingBuildPath(projectDir) {
135
+ return join(STATE_DIR, `${projectKey(projectDir)}-pending-build.json`);
136
+ }
137
+
138
+ function savePendingBuild(projectDir, specPath) {
139
+ ensureDir();
140
+ writeFileSync(pendingBuildPath(projectDir), JSON.stringify({
141
+ specPath,
142
+ projectDir,
143
+ timestamp: Date.now(),
144
+ }, null, 2));
145
+ }
146
+
147
+ function loadPendingBuild(projectDir) {
148
+ const path = pendingBuildPath(projectDir);
149
+ if (!existsSync(path)) return null;
150
+ try {
151
+ return JSON.parse(readFileSync(path, 'utf-8'));
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ function clearPendingBuild(projectDir) {
158
+ try { unlinkSync(pendingBuildPath(projectDir)); } catch { /* noop */ }
159
+ }
160
+
161
+ module.exports = { save, saveInProgress, load, clear, isRecentlyChecked, waitForReady, loadPrefs, savePrefs, savePendingBuild, loadPendingBuild, clearPendingBuild };
@@ -27,6 +27,7 @@ function git(args, cwd) {
27
27
  encoding: 'utf-8',
28
28
  stdio: ['pipe', 'pipe', 'pipe'],
29
29
  timeout: 10000,
30
+ windowsHide: true,
30
31
  }).trim();
31
32
  } catch {
32
33
  return null;
@@ -19,6 +19,7 @@
19
19
 
20
20
  const { existsSync } = require('fs');
21
21
  const { join } = require('path');
22
+ const { spawn } = require('child_process');
22
23
 
23
24
  const config = require('./lib/config.cjs');
24
25
  const state = require('./lib/state.cjs');
@@ -27,12 +28,14 @@ const { getBanner } = require('./lib/banner.cjs');
27
28
  const { checkGit } = require('./lib/git-checks.cjs');
28
29
  const { checkTaskList } = require('./lib/task-checks.cjs');
29
30
  const { checkStaleness } = require('./lib/staleness.cjs');
31
+ const { checkPluginHealth } = require('./lib/plugin-health.cjs');
30
32
 
31
33
  const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
32
34
  const CLAUDE_MD = join(PROJECT_DIR, 'CLAUDE.md');
33
35
 
34
36
  // ─── check ─────────────────────────────────────────────────────────────
35
- // Runs at SessionStart. Validates project health and emits context.
37
+ // Runs at SessionStart. Spawns background worker and exits immediately
38
+ // so the Claude Code UI stays responsive.
36
39
 
37
40
  function check() {
38
41
  // Dedup: skip if recently checked (handles dual global+project registration)
@@ -41,6 +44,28 @@ function check() {
41
44
  return;
42
45
  }
43
46
 
47
+ // Write in-progress marker immediately (also serves as dedup guard)
48
+ state.saveInProgress(PROJECT_DIR);
49
+
50
+ // Emit empty response — SessionStart returns instantly
51
+ console.log(JSON.stringify({}));
52
+
53
+ // Spawn background worker for heavy checks
54
+ // windowsHide: true prevents a console window from flashing on Windows
55
+ const worker = spawn(process.execPath, [__filename, 'check-bg'], {
56
+ detached: true,
57
+ stdio: 'ignore',
58
+ windowsHide: true,
59
+ env: { ...process.env, CLAUDE_PROJECT_DIR: PROJECT_DIR },
60
+ });
61
+ worker.unref();
62
+ }
63
+
64
+ // ─── check-bg ──────────────────────────────────────────────────────────
65
+ // Runs as a detached background process. Performs all validation and
66
+ // writes results to the state file for remind() to pick up.
67
+
68
+ function checkBackground() {
44
69
  const output = new OutputBuilder();
45
70
 
46
71
  // Banner — shown once per session at start
@@ -62,7 +87,7 @@ function check() {
62
87
  '"Skip" \u2014 continue without one',
63
88
  ],
64
89
  );
65
- emit(output);
90
+ saveState(output);
66
91
  return;
67
92
  }
68
93
 
@@ -80,7 +105,13 @@ function check() {
80
105
  // 3) Staleness evaluation
81
106
  checkStaleness(PROJECT_DIR, CLAUDE_MD, gitRoot, output);
82
107
 
83
- // 4) Staleness summary
108
+ // 4) Pending build check
109
+ checkPendingBuild(PROJECT_DIR, output);
110
+
111
+ // 5) Plugin health check
112
+ checkPluginHealth(PROJECT_DIR, output);
113
+
114
+ // 6) Staleness summary
84
115
  if (output.score >= config.staleness.threshold) {
85
116
  output.blank();
86
117
  output.add(`[SESSION GUARD] \u26A0\uFE0F CLAUDE.md appears STALE (score: ${output.score}/${config.staleness.threshold})`);
@@ -102,14 +133,15 @@ function check() {
102
133
  output.signals.forEach(sig => output.add(` ${sig}`));
103
134
  }
104
135
 
105
- emit(output);
136
+ saveState(output);
106
137
  }
107
138
 
108
139
  // ─── remind ────────────────────────────────────────────────────────────
109
140
  // Runs at UserPromptSubmit. Re-emits pending context from check, once.
110
141
 
111
142
  function remind() {
112
- const pending = state.load(PROJECT_DIR);
143
+ // Wait for background check to complete (polls up to 4s at 200ms intervals)
144
+ const pending = state.waitForReady(PROJECT_DIR);
113
145
 
114
146
  if (!pending || !pending.context) {
115
147
  console.log(JSON.stringify({}));
@@ -231,10 +263,30 @@ function checkRig(projectDir, output) {
231
263
  );
232
264
  }
233
265
 
266
+ // ─── pending build check ──────────────────────────────────────────────
267
+
268
+ function checkPendingBuild(projectDir, output) {
269
+ const pending = state.loadPendingBuild(projectDir);
270
+ if (!pending) return;
271
+
272
+ const specPath = pending.specPath;
273
+ const specExists = existsSync(join(projectDir, specPath));
274
+
275
+ if (!specExists) {
276
+ // Spec file was deleted — clean up stale marker
277
+ state.clearPendingBuild(projectDir);
278
+ return;
279
+ }
280
+
281
+ output.blank();
282
+ output.add(`[SESSION GUARD] 📋 Pending spec ready for build: ${specPath}`);
283
+ output.add(`[SESSION GUARD] → Run: /build ${specPath}`);
284
+ }
285
+
234
286
  // ─── helpers ───────────────────────────────────────────────────────────
235
287
 
236
- function emit(output) {
237
- console.log(output.toJson());
288
+ /** Save check results to state file (used by background worker, no stdout). */
289
+ function saveState(output) {
238
290
  state.save(PROJECT_DIR, {
239
291
  context: output.parts.join('\n'),
240
292
  score: output.score,
@@ -253,6 +305,15 @@ switch (command) {
253
305
  case 'remind':
254
306
  remind();
255
307
  break;
308
+ case 'check-bg':
309
+ try {
310
+ checkBackground();
311
+ } catch {
312
+ // Background worker failed — clear in-progress marker so remind()
313
+ // doesn't hang waiting. Graceful degradation: no context this session.
314
+ state.clear(PROJECT_DIR);
315
+ }
316
+ break;
256
317
  case 'dismiss-brace': {
257
318
  const prefs = state.loadPrefs(PROJECT_DIR);
258
319
  prefs.braceDismissed = true;
@@ -267,8 +328,15 @@ switch (command) {
267
328
  console.log(`Rig prompt dismissed for ${PROJECT_DIR}`);
268
329
  break;
269
330
  }
331
+ case 'dismiss-plugin-health': {
332
+ const prefs = state.loadPrefs(PROJECT_DIR);
333
+ prefs.pluginHealthDismissed = true;
334
+ state.savePrefs(PROJECT_DIR, prefs);
335
+ console.log(`Plugin health checks dismissed for ${PROJECT_DIR}`);
336
+ break;
337
+ }
270
338
  default:
271
339
  console.error(`Session Guard v${config.version}`);
272
- console.error('Usage: node session-guard.js <check|remind|dismiss-brace|dismiss-rig>');
340
+ console.error('Usage: node session-guard.js <check|remind|dismiss-brace|dismiss-rig|dismiss-plugin-health>');
273
341
  process.exit(1);
274
342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slamb2k/mad-skills",
3
- "version": "2.0.44",
3
+ "version": "2.0.46",
4
4
  "description": "Claude Code skills collection — full lifecycle development tools",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: brace
3
3
  description: 'Initialize any project directory with a standard scaffold for AI-assisted development. Creates specs/ and context/ directories, a project CLAUDE.md with development workflow and guardrails, .gitignore, and branch protection. Recommends claude-mem for persistent memory. Idempotent — safe to run on existing projects. Triggers: "init project", "setup brace", "brace", "initialize", "bootstrap", "scaffold".'
4
- argument-hint: "[--force]"
4
+ argument-hint: "[--force] [--skip-plugin-tuning]"
5
5
  allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Agent, AskUserQuestion
6
6
  ---
7
7
 
@@ -75,6 +75,7 @@ Report format: `references/report-template.md`
75
75
 
76
76
  Parse optional flags from the request:
77
77
  - `--force` — Overwrite existing files without prompting
78
+ - `--skip-plugin-tuning` — Skip Phase 7 plugin performance tuning
78
79
 
79
80
  ---
80
81
 
@@ -85,6 +86,7 @@ Before starting, check all dependencies in this table:
85
86
  | Dependency | Type | Check | Required | Resolution | Detail |
86
87
  |-----------|------|-------|----------|------------|--------|
87
88
  | claude-mem | plugin | — | no | ask | `claude plugin install claude-mem` |
89
+ | oh-my-claudecode | plugin | — | no | ask | `claude plugin install oh-my-claudecode` |
88
90
 
89
91
  For each row, in order:
90
92
  1. Run the Check command (for cli/npm) or test file existence (for agent/skill)
@@ -97,6 +99,11 @@ For each row, in order:
97
99
  - **fallback**: notify user with Detail, continue with degraded behavior
98
100
  4. After all checks: summarize what's available and what's degraded
99
101
 
102
+ **Plugin detection:** For plugin dependencies (Type = plugin), check
103
+ `~/.claude/settings.json` → `enabledPlugins` for a key containing the plugin
104
+ name set to `true`. Store results as `PLUGIN_STATE` (`claude_mem_installed`,
105
+ `omc_installed`, `hookify_installed`) for use in Phase 4 and Phase 7.
106
+
100
107
  1. Capture **FLAGS** from the user's request
101
108
 
102
109
  ---
@@ -246,6 +253,11 @@ Before sending the prompt, substitute these variables:
246
253
  - `{GITIGNORE_CONTENT}` — read from `assets/gitignore-template`
247
254
  - `{GLOBAL_PREFERENCES_CONTENT}` — read from `assets/global-preferences-template.md`
248
255
  (the section between BEGIN TEMPLATE and END TEMPLATE)
256
+ - `{PLUGIN_ROLE_SEPARATION}` — if both claude-mem AND oh-my-claudecode are
257
+ detected as enabled (from Phase 7 PLUGIN_REPORT, or by checking
258
+ `~/.claude/settings.json`), substitute with the content from
259
+ `references/plugin-tuning-steps.md#plugin-role-separation-content`.
260
+ Otherwise, substitute with an empty string.
249
261
 
250
262
  Parse SCAFFOLD_REPORT. If status is "failed", report to user and stop.
251
263
 
@@ -288,150 +300,105 @@ Parse VERIFY_REPORT. Present the final summary using the format in
288
300
 
289
301
  **Only if the project is a git repo** (from SCAN_REPORT `git_initialized`).
290
302
 
291
- Detect the default branch and hosting platform:
292
-
293
- ```bash
294
- default_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
295
-
296
- REMOTE_URL=$(git remote get-url origin 2>/dev/null)
297
- if echo "$REMOTE_URL" | grep -qiE 'dev\.azure\.com|visualstudio\.com'; then
298
- PLATFORM="azdo"
299
- elif echo "$REMOTE_URL" | grep -qi 'github\.com'; then
300
- PLATFORM="github"
301
- else
302
- PLATFORM="unknown"
303
- fi
304
- ```
305
-
306
- ### GitHub
303
+ Detect the default branch and hosting platform (GitHub, Azure DevOps, or
304
+ unknown). Platform detection and all CLI/REST commands are in
305
+ `references/branch-protection-steps.md`.
307
306
 
308
- If `PLATFORM == github`:
309
- 1. Check for existing branch protection via `gh api repos/{owner}/{repo}/branches/{default_branch}/protection` (404 = unprotected)
310
- 2. If unprotected, ask via AskUserQuestion:
307
+ ### Flow
311
308
 
312
- Question: "Default branch `{default_branch}` has no branch protection. Add it?"
313
- Options:
309
+ 1. Detect platform from `git remote get-url origin`
310
+ 2. **GitHub:** Check existing protection via `gh api`. If unprotected, ask via
311
+ AskUserQuestion:
314
312
  - "Yes, require PR reviews (Recommended)" — require 1 approval, block force push
315
313
  - "Skip" — leave unprotected
314
+ 3. **Azure DevOps:** Extract org/project from remote URL. Check existing
315
+ policies via `az repos` CLI or REST fallback. If no minimum reviewer
316
+ policy, ask via AskUserQuestion (same options as GitHub).
317
+ 4. **Unknown platform:** Skip and report.
316
318
 
317
- 3. If user accepts, apply via:
318
- ```bash
319
- gh api repos/{owner}/{repo}/branches/{default_branch}/protection \
320
- -X PUT -f required_pull_request_reviews='{"required_approving_review_count":1}' \
321
- -f enforce_admins=false \
322
- -f restrictions=null \
323
- -f required_status_checks=null \
324
- -F allow_force_pushes=false \
325
- -F allow_deletions=false
326
- ```
319
+ If user accepts, apply using the procedures in
320
+ `references/branch-protection-steps.md`.
327
321
 
328
- ### Azure DevOps
329
-
330
- If `PLATFORM == azdo`:
331
-
332
- Extract org and project from the remote URL (same pattern as `/ship`):
333
- ```bash
334
- if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
335
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
336
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
337
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
338
- elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
339
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
340
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
341
- AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
342
- elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
343
- AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
344
- AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
345
- AZDO_ORG_URL="https://${AZDO_ORG}.visualstudio.com"
346
- fi
347
- # URL-decode for CLI/display; keep URL-safe versions for REST API paths
348
- AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
349
- AZDO_ORG=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_ORG'))")
350
- AZDO_PROJECT=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_PROJECT_URL_SAFE'))")
351
- REPO_NAME=$(basename -s .git "$REMOTE_URL")
352
- ```
322
+ Include result in the final report under a "🔒 Branch protection" section.
353
323
 
354
- If org/project extraction fails, report ⚠️ and skip branch policies.
324
+ ---
355
325
 
356
- 1. Check for existing branch policies. Use `az repos` CLI if available,
357
- otherwise fall back to REST API:
326
+ ## Phase 7: Plugin Performance Tuning
358
327
 
359
- **CLI:**
360
- ```bash
361
- az repos policy list \
362
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
363
- --repository-id "$REPO_NAME" --branch "$default_branch" \
364
- --query "[].type.displayName" -o tsv
365
- ```
328
+ **Skip this phase if `--skip-plugin-tuning` flag is set.**
366
329
 
367
- **REST fallback:**
368
- ```bash
369
- AUTH="Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')"
370
- # Get repository ID first
371
- REPO_ID=$(curl -s -H "$AUTH" \
372
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/git/repositories/$REPO_NAME?api-version=7.0" \
373
- | jq -r '.id')
374
- # List branch policies
375
- curl -s -H "$AUTH" \
376
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/configurations?api-version=7.0" \
377
- | jq "[.value[] | select(.settings.scope[]?.refName == \"refs/heads/$default_branch\" and .settings.scope[]?.repositoryId == \"$REPO_ID\")]"
378
- ```
330
+ Detect companion Claude Code plugins and recommend performance optimisations.
331
+ This phase modifies user-level settings files (`~/.claude/settings.json`,
332
+ `~/.claude-mem/settings.json`) not repo-level files.
379
333
 
380
- 2. If no "Minimum number of reviewers" policy exists, ask via AskUserQuestion:
334
+ If `--force` is set, apply all recommendations without prompting.
381
335
 
382
- Question: "Default branch `{default_branch}` has no minimum reviewer policy. Add it?"
383
- Options:
384
- - "Yes, require PR reviews (Recommended)" require 1 approval, block direct push
385
- - "Skip" leave unprotected
336
+ **Plugin presence guards:** Only audit plugins detected as installed during
337
+ pre-flight (`PLUGIN_STATE`). Skip H1/H2 if hookify is absent. Skip M1/M2/M3
338
+ if claude-mem is absent. Skip M2 if OMC is absent (M2 requires both). The
339
+ `{PLUGIN_ROLE_SEPARATION}` content in CLAUDE.md is only injected when both
340
+ claude-mem and OMC are confirmed enabled.
386
341
 
387
- 3. If user accepts, create the policy:
342
+ If no companion plugins are installed at all, output:
343
+ ```
344
+ ━━ 7 · Plugin Performance ━━━━━━━━━━━━━━━━━━━━
345
+ ⏭️ No companion plugins installed — skipping
346
+ ```
347
+ and skip to the report.
388
348
 
389
- **CLI:**
390
- ```bash
391
- REPO_ID=$(az repos show --repository "$REPO_NAME" \
392
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
393
- --query 'id' -o tsv)
394
-
395
- az repos policy approver-count create \
396
- --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
397
- --repository-id "$REPO_ID" --branch "$default_branch" \
398
- --minimum-approver-count 1 \
399
- --creator-vote-counts false \
400
- --allow-downvotes false \
401
- --reset-on-source-push true \
402
- --blocking true --enabled true
403
- ```
349
+ ### Detection
404
350
 
405
- **REST fallback:**
406
- ```bash
407
- curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
408
- "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/configurations?api-version=7.0" \
409
- -d "{
410
- \"isEnabled\": true,
411
- \"isBlocking\": true,
412
- \"type\": {\"id\": \"fa4e907d-c16b-4a4c-9dfa-4906e5d171dd\"},
413
- \"settings\": {
414
- \"minimumApproverCount\": 1,
415
- \"creatorVoteCounts\": false,
416
- \"allowDownvotes\": false,
417
- \"resetOnSourcePush\": true,
418
- \"scope\": [{
419
- \"repositoryId\": \"$REPO_ID\",
420
- \"refName\": \"refs/heads/$default_branch\",
421
- \"matchKind\": \"exact\"
422
- }]
423
- }
424
- }"
425
- ```
351
+ Launch **Bash** subagent (**haiku**):
352
+
353
+ ```
354
+ Task(
355
+ subagent_type: "Bash",
356
+ model: "haiku",
357
+ description: "Detect installed plugins and audit performance",
358
+ prompt: <read from references/phase-prompts.md#phase-7>
359
+ )
360
+ ```
361
+
362
+ Parse PLUGIN_REPORT.
426
363
 
427
- ### Unknown Platform
364
+ ### Present Findings
428
365
 
429
- If `PLATFORM == unknown`, skip branch protection and report:
366
+ If PLUGIN_REPORT contains zero findings, output:
430
367
  ```
431
- ⏭️ Branch protection skipped (unrecognized remote, not GitHub or Azure DevOps)
368
+ ━━ 7 · Plugin Performance ━━━━━━━━━━━━━━━━━━━━
369
+ ⏭️ No plugin optimisations needed
432
370
  ```
371
+ Skip to the report.
433
372
 
434
- Include result in the final report under a "🔒 Branch protection" section.
373
+ Otherwise, present findings with **AskUserQuestion**:
374
+
375
+ Options:
376
+ - "Apply all recommendations (Recommended)"
377
+ - "Let me choose which to apply"
378
+ - "Skip plugin tuning"
379
+
380
+ If "Let me choose", present individual findings as multi-select.
381
+
382
+ ### Audit Rules
383
+
384
+ | Code | Check | Condition | Severity |
385
+ |------|-------|-----------|----------|
386
+ | H1 | Hookify: no rules | enabled + zero `hookify.*.local.md` files | high |
387
+ | H2 | Hookify: python3 broken | enabled + `python3 --version` fails | high |
388
+ | M1 | claude-mem: read-only tools | SKIP_TOOLS missing Read/Glob/Grep/ToolSearch/Agent/WebSearch/WebFetch | medium |
389
+ | M2 | claude-mem: high context | observations > 10 or sessions > 3, AND OMC also enabled | medium |
390
+ | M3 | claude-mem: provider=claude | provider is "claude" (SDK spawn known-broken) | low |
391
+
392
+ ### Apply Approved Changes
393
+
394
+ For each approved finding, follow the procedures in
395
+ `references/plugin-tuning-steps.md`. Each script is idempotent — re-reads
396
+ the target file before writing and checks current value before modifying.
397
+
398
+ If both claude-mem AND oh-my-claudecode are detected as enabled, also inject
399
+ the Plugin Role Separation section into the project CLAUDE.md (see
400
+ `references/plugin-tuning-steps.md#plugin-role-separation-content`).
401
+ Skip if section already exists (idempotent).
435
402
 
436
403
  ---
437
404
 
@@ -15,6 +15,10 @@ data/
15
15
  # Temp & scratch
16
16
  .tmp/
17
17
 
18
+ # AI tooling local state
19
+ .claude/
20
+ .omc/
21
+
18
22
  # Python
19
23
  __pycache__/
20
24
  *.pyc
@@ -0,0 +1,135 @@
1
+ # Branch Protection Steps
2
+
3
+ Procedural reference for Phase 6 — platform-specific branch protection commands.
4
+
5
+ ---
6
+
7
+ ## Platform Detection
8
+
9
+ ```bash
10
+ default_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
11
+
12
+ REMOTE_URL=$(git remote get-url origin 2>/dev/null)
13
+ if echo "$REMOTE_URL" | grep -qiE 'dev\.azure\.com|visualstudio\.com'; then
14
+ PLATFORM="azdo"
15
+ elif echo "$REMOTE_URL" | grep -qi 'github\.com'; then
16
+ PLATFORM="github"
17
+ else
18
+ PLATFORM="unknown"
19
+ fi
20
+ ```
21
+
22
+ ---
23
+
24
+ ## GitHub
25
+
26
+ ### Check existing protection
27
+
28
+ ```bash
29
+ gh api repos/{owner}/{repo}/branches/{default_branch}/protection
30
+ ```
31
+ 404 = unprotected.
32
+
33
+ ### Apply protection
34
+
35
+ ```bash
36
+ gh api repos/{owner}/{repo}/branches/{default_branch}/protection \
37
+ -X PUT -f required_pull_request_reviews='{"required_approving_review_count":1}' \
38
+ -f enforce_admins=false \
39
+ -f restrictions=null \
40
+ -f required_status_checks=null \
41
+ -F allow_force_pushes=false \
42
+ -F allow_deletions=false
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Azure DevOps
48
+
49
+ ### Extract org and project
50
+
51
+ ```bash
52
+ if echo "$REMOTE_URL" | grep -q 'dev\.azure\.com'; then
53
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/\([^/]*\)/.*|\1|p')
54
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*dev\.azure\.com/[^/]*/\([^/]*\)/.*|\1|p')
55
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
56
+ elif echo "$REMOTE_URL" | grep -q 'vs-ssh\.visualstudio\.com'; then
57
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/\([^/]*\)/.*|\1|p')
58
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*vs-ssh\.visualstudio\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
59
+ AZDO_ORG_URL="https://dev.azure.com/$AZDO_ORG"
60
+ elif echo "$REMOTE_URL" | grep -q 'visualstudio\.com'; then
61
+ AZDO_ORG=$(echo "$REMOTE_URL" | sed -n 's|.*//\([^.]*\)\.visualstudio\.com.*|\1|p')
62
+ AZDO_PROJECT=$(echo "$REMOTE_URL" | sed -n 's|.*/\([^/]*\)/_git/.*|\1|p')
63
+ AZDO_ORG_URL="https://${AZDO_ORG}.visualstudio.com"
64
+ fi
65
+ # URL-decode for CLI/display; keep URL-safe versions for REST API paths
66
+ AZDO_PROJECT_URL_SAFE="$AZDO_PROJECT"
67
+ AZDO_ORG=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_ORG'))")
68
+ AZDO_PROJECT=$(python3 -c "import urllib.parse; print(urllib.parse.unquote('$AZDO_PROJECT_URL_SAFE'))")
69
+ REPO_NAME=$(basename -s .git "$REMOTE_URL")
70
+ ```
71
+
72
+ If org/project extraction fails, report ⚠️ and skip branch policies.
73
+
74
+ ### Check existing policies
75
+
76
+ **CLI:**
77
+ ```bash
78
+ az repos policy list \
79
+ --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
80
+ --repository-id "$REPO_NAME" --branch "$default_branch" \
81
+ --query "[].type.displayName" -o tsv
82
+ ```
83
+
84
+ **REST fallback:**
85
+ ```bash
86
+ AUTH="Authorization: Basic $(printf ":%s" "$PAT" | base64 | tr -d '\n')"
87
+ # Get repository ID first
88
+ REPO_ID=$(curl -s -H "$AUTH" \
89
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/git/repositories/$REPO_NAME?api-version=7.0" \
90
+ | jq -r '.id')
91
+ # List branch policies
92
+ curl -s -H "$AUTH" \
93
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/configurations?api-version=7.0" \
94
+ | jq "[.value[] | select(.settings.scope[]?.refName == \"refs/heads/$default_branch\" and .settings.scope[]?.repositoryId == \"$REPO_ID\")]"
95
+ ```
96
+
97
+ ### Create minimum reviewer policy
98
+
99
+ **CLI:**
100
+ ```bash
101
+ REPO_ID=$(az repos show --repository "$REPO_NAME" \
102
+ --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
103
+ --query 'id' -o tsv)
104
+
105
+ az repos policy approver-count create \
106
+ --org "$AZDO_ORG_URL" --project "$AZDO_PROJECT" \
107
+ --repository-id "$REPO_ID" --branch "$default_branch" \
108
+ --minimum-approver-count 1 \
109
+ --creator-vote-counts false \
110
+ --allow-downvotes false \
111
+ --reset-on-source-push true \
112
+ --blocking true --enabled true
113
+ ```
114
+
115
+ **REST fallback:**
116
+ ```bash
117
+ curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
118
+ "$AZDO_ORG_URL/$AZDO_PROJECT_URL_SAFE/_apis/policy/configurations?api-version=7.0" \
119
+ -d "{
120
+ \"isEnabled\": true,
121
+ \"isBlocking\": true,
122
+ \"type\": {\"id\": \"fa4e907d-c16b-4a4c-9dfa-4906e5d171dd\"},
123
+ \"settings\": {
124
+ \"minimumApproverCount\": 1,
125
+ \"creatorVoteCounts\": false,
126
+ \"allowDownvotes\": false,
127
+ \"resetOnSourcePush\": true,
128
+ \"scope\": [{
129
+ \"repositoryId\": \"$REPO_ID\",
130
+ \"refName\": \"refs/heads/$default_branch\",
131
+ \"matchKind\": \"exact\"
132
+ }]
133
+ }
134
+ }"
135
+ ```
@@ -49,6 +49,8 @@ MCP tools for search, timeline, and observation management. Claude Code's
49
49
  built-in auto memory (`~/.claude/projects/<project>/memory/MEMORY.md`)
50
50
  handles curated facts.
51
51
 
52
+ {PLUGIN_ROLE_SEPARATION}
53
+
52
54
  {UNIVERSAL_PRINCIPLES}
53
55
 
54
56
  ## Branch Discipline
@@ -170,6 +170,10 @@ empty string (principles are in the global config instead).
170
170
 
171
171
  {GITIGNORE_CONTENT}
172
172
 
173
+ ### Plugin Role Separation Content
174
+
175
+ {PLUGIN_ROLE_SEPARATION}
176
+
173
177
  ### Global Preferences Content
174
178
 
175
179
  {GLOBAL_PREFERENCES_CONTENT}
@@ -254,3 +258,110 @@ VERIFY_REPORT:
254
258
  legacy_cleaned: true|false|not_applicable
255
259
  issues: [any problems found]
256
260
  ```
261
+
262
+ ---
263
+
264
+ ## Phase 7: Plugin Performance Detection
265
+
266
+ **Agent:** Bash | **Model:** haiku
267
+
268
+ ```
269
+ Detect installed Claude Code plugins and audit their performance configuration.
270
+
271
+ Limit PLUGIN_REPORT to 30 lines maximum.
272
+
273
+ ## Checks
274
+
275
+ 1. **Read plugin registry**
276
+ SETTINGS_FILE="$HOME/.claude/settings.json"
277
+ if [ ! -f "$SETTINGS_FILE" ]; then
278
+ echo "no_settings_file"
279
+ echo "PLUGIN_REPORT:"
280
+ echo " settings_file_found: false"
281
+ echo " findings: none"
282
+ exit 0
283
+ fi
284
+
285
+ Use node to parse JSON and extract installed/enabled plugin status:
286
+ node -e "
287
+ const s = JSON.parse(require('fs').readFileSync('$HOME/.claude/settings.json','utf8'));
288
+ const p = s.enabledPlugins || {};
289
+ const hookify = Object.keys(p).find(k => k.includes('hookify'));
290
+ const mem = Object.keys(p).find(k => k.includes('claude-mem'));
291
+ const omc = Object.keys(p).find(k => k.includes('oh-my-claudecode'));
292
+ console.log('hookify_installed:' + !!hookify);
293
+ console.log('hookify_enabled:' + (hookify && p[hookify] === true));
294
+ console.log('claude_mem_installed:' + !!mem);
295
+ console.log('claude_mem_enabled:' + (mem && p[mem] === true));
296
+ console.log('omc_installed:' + !!omc);
297
+ console.log('omc_enabled:' + (omc && p[omc] === true));
298
+ "
299
+ Record: hookify_installed/enabled, claude_mem_installed/enabled, omc_installed/enabled
300
+
301
+ If a plugin is not installed (not in enabledPlugins at all), skip its
302
+ entire audit section below and report all its fields as "N/A".
303
+
304
+ 2. **Hookify audit** (only if hookify_enabled == true)
305
+ a. Check python3 availability:
306
+ python3 --version >/dev/null 2>&1 && echo "python3_available:true" || echo "python3_available:false"
307
+ Record: python3_available
308
+
309
+ b. Count hookify rule files:
310
+ RULE_COUNT=0
311
+ for f in "$HOME/.claude"/hookify.*.local.md .claude/hookify.*.local.md; do
312
+ [ -f "$f" ] && RULE_COUNT=$((RULE_COUNT + 1))
313
+ done
314
+ echo "hookify_rule_count:$RULE_COUNT"
315
+ Record: hookify_rule_count
316
+
317
+ c. Determine findings:
318
+ - If python3_available == false → finding H2
319
+ - If hookify_rule_count == 0 → finding H1
320
+
321
+ 3. **claude-mem audit** (only if claude_mem_enabled == true)
322
+ MEM_SETTINGS="$HOME/.claude-mem/settings.json"
323
+ if [ -f "$MEM_SETTINGS" ]; then
324
+ node -e "
325
+ const s = JSON.parse(require('fs').readFileSync('$HOME/.claude-mem/settings.json','utf8'));
326
+ console.log('skip_tools:' + (s.CLAUDE_MEM_SKIP_TOOLS || ''));
327
+ console.log('observations:' + (s.CLAUDE_MEM_CONTEXT_OBSERVATIONS || '50'));
328
+ console.log('session_count:' + (s.CLAUDE_MEM_CONTEXT_SESSION_COUNT || '10'));
329
+ console.log('provider:' + (s.CLAUDE_MEM_PROVIDER || 'claude'));
330
+ console.log('has_openrouter_key:' + !!(s.CLAUDE_MEM_OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY));
331
+ "
332
+ fi
333
+
334
+ a. Check SKIP_TOOLS for read-only tools:
335
+ Required: Read,Glob,Grep,ToolSearch,Agent,WebSearch,WebFetch
336
+ For each, check if it appears in the skip_tools value.
337
+ Record: missing_skip_tools (comma-separated list, or "none")
338
+ If any missing → finding M1
339
+
340
+ b. Check context injection levels (only if omc_enabled == true):
341
+ If observations > 10 OR session_count > 3 → finding M2
342
+ Record: current_observations, current_session_count
343
+
344
+ c. Check provider:
345
+ If provider == "claude" → finding M3
346
+ Record: current_provider
347
+
348
+ ## Output Format
349
+
350
+ PLUGIN_REPORT:
351
+ settings_file_found: true|false
352
+ hookify_installed: true|false
353
+ hookify_enabled: true|false
354
+ hookify_python3_available: true|false|N/A
355
+ hookify_rule_count: {number}|N/A
356
+ claude_mem_installed: true|false
357
+ claude_mem_enabled: true|false
358
+ claude_mem_skip_tools: {current value}|N/A
359
+ claude_mem_missing_skip_tools: {list}|none|N/A
360
+ claude_mem_observations: {number}|N/A
361
+ claude_mem_session_count: {number}|N/A
362
+ claude_mem_provider: {value}|N/A
363
+ claude_mem_has_openrouter_key: true|false|N/A
364
+ omc_installed: true|false
365
+ omc_enabled: true|false
366
+ findings: {comma-separated list of H1,H2,M1,M2,M3 or "none"}
367
+ ```
@@ -0,0 +1,125 @@
1
+ # Plugin Tuning Steps
2
+
3
+ Procedural reference for Phase 7 — `node -e` scripts for each audit rule.
4
+ Each script is idempotent: re-reads the target file before writing and
5
+ checks current value before modifying.
6
+
7
+ ---
8
+
9
+ ## H1/H2: Disable Hookify
10
+
11
+ Target: `~/.claude/settings.json`
12
+
13
+ ```bash
14
+ node -e "
15
+ const fs = require('fs');
16
+ const p = require('os').homedir() + '/.claude/settings.json';
17
+ const s = JSON.parse(fs.readFileSync(p, 'utf8'));
18
+ const key = Object.keys(s.enabledPlugins || {}).find(k => k.includes('hookify'));
19
+ if (key && s.enabledPlugins[key] !== false) {
20
+ s.enabledPlugins[key] = false;
21
+ fs.writeFileSync(p, JSON.stringify(s, null, 2) + '\n');
22
+ console.log('Disabled hookify');
23
+ } else {
24
+ console.log('Hookify already disabled — skipped');
25
+ }
26
+ "
27
+ ```
28
+
29
+ ---
30
+
31
+ ## M1: Add Read-Only Tools to SKIP_TOOLS
32
+
33
+ Target: `~/.claude-mem/settings.json`
34
+
35
+ ```bash
36
+ node -e "
37
+ const fs = require('fs');
38
+ const p = require('os').homedir() + '/.claude-mem/settings.json';
39
+ const s = JSON.parse(fs.readFileSync(p, 'utf8'));
40
+ const required = ['Read','Glob','Grep','ToolSearch','Agent','WebSearch','WebFetch'];
41
+ const current = (s.CLAUDE_MEM_SKIP_TOOLS || '').split(',').map(t => t.trim()).filter(Boolean);
42
+ const missing = required.filter(t => !current.includes(t));
43
+ if (missing.length > 0) {
44
+ s.CLAUDE_MEM_SKIP_TOOLS = [...new Set([...current, ...missing])].join(',');
45
+ fs.writeFileSync(p, JSON.stringify(s, null, 2) + '\n');
46
+ console.log('Added to SKIP_TOOLS: ' + missing.join(', '));
47
+ } else {
48
+ console.log('All read-only tools already in SKIP_TOOLS — skipped');
49
+ }
50
+ "
51
+ ```
52
+
53
+ ---
54
+
55
+ ## M2: Reduce Context Injection
56
+
57
+ Target: `~/.claude-mem/settings.json`
58
+
59
+ ```bash
60
+ node -e "
61
+ const fs = require('fs');
62
+ const p = require('os').homedir() + '/.claude-mem/settings.json';
63
+ const s = JSON.parse(fs.readFileSync(p, 'utf8'));
64
+ let changed = false;
65
+ if (parseInt(s.CLAUDE_MEM_CONTEXT_OBSERVATIONS || '50') > 10) {
66
+ s.CLAUDE_MEM_CONTEXT_OBSERVATIONS = '10';
67
+ changed = true;
68
+ }
69
+ if (parseInt(s.CLAUDE_MEM_CONTEXT_SESSION_COUNT || '10') > 3) {
70
+ s.CLAUDE_MEM_CONTEXT_SESSION_COUNT = '3';
71
+ changed = true;
72
+ }
73
+ if (changed) {
74
+ fs.writeFileSync(p, JSON.stringify(s, null, 2) + '\n');
75
+ console.log('Reduced context injection: observations=10, sessions=3');
76
+ } else {
77
+ console.log('Context injection already optimal — skipped');
78
+ }
79
+ "
80
+ ```
81
+
82
+ ---
83
+
84
+ ## M3: Switch Provider to OpenRouter
85
+
86
+ Target: `~/.claude-mem/settings.json`
87
+
88
+ Pre-check: verify OpenRouter API key exists. If missing, warn and skip.
89
+
90
+ ```bash
91
+ node -e "
92
+ const fs = require('fs');
93
+ const p = require('os').homedir() + '/.claude-mem/settings.json';
94
+ const s = JSON.parse(fs.readFileSync(p, 'utf8'));
95
+ if (s.CLAUDE_MEM_PROVIDER === 'claude') {
96
+ if (!s.CLAUDE_MEM_OPENROUTER_API_KEY && !process.env.OPENROUTER_API_KEY) {
97
+ console.log('SKIP: No OpenRouter API key configured');
98
+ process.exit(0);
99
+ }
100
+ s.CLAUDE_MEM_PROVIDER = 'openrouter';
101
+ fs.writeFileSync(p, JSON.stringify(s, null, 2) + '\n');
102
+ console.log('Switched provider to openrouter');
103
+ } else {
104
+ console.log('Provider already ' + (s.CLAUDE_MEM_PROVIDER || 'unset') + ' — skipped');
105
+ }
106
+ "
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Plugin Role Separation Content
112
+
113
+ Inject after `## Memory` in the project CLAUDE.md when both claude-mem AND
114
+ oh-my-claudecode are enabled. Skip if `### Plugin Role Separation` already
115
+ exists (idempotent).
116
+
117
+ ```markdown
118
+ ### Plugin Role Separation (claude-mem + OMC)
119
+
120
+ - **claude-mem** = passive memory layer (auto-capture, recall, search). Let it run silently.
121
+ - **OMC project memory** = active, curated checkpoints and project status.
122
+ - **OMC wiki** = architectural decisions, bug solutions, patterns.
123
+ - Searching past work → OMC project memory first, then claude-mem for older/cross-project.
124
+ - Code navigation → OMC LSP tools. Planning → OMC /plan or /autopilot.
125
+ ```
@@ -26,6 +26,14 @@ Present this summary after verification completes.
26
26
  │ {✅} tools/memory/ Legacy memory scripts
27
27
  │ {✅} memory/ Legacy memory directory
28
28
 
29
+ │ 🔌 Plugin Tuning (only if Phase 7 ran)
30
+ │ {✅|⏭️} Hookify: {disabled / already disabled / not installed / skipped / N/A}
31
+ │ {✅|⏭️} claude-mem SKIP_TOOLS: {optimised / already optimal / not installed / skipped / N/A}
32
+ │ {✅|⏭️} claude-mem context: {reduced / already optimal / not installed / skipped / N/A}
33
+ │ {✅|⏭️} claude-mem provider: {switched / already optimal / not installed / skipped / N/A}
34
+ │ {⏭️} oh-my-claudecode: {installed / not installed}
35
+ │ {✅|⏭️} Plugin role separation: {injected / already present / not applicable / skipped}
36
+
29
37
  │ ⚠️ Notes
30
38
  │ {any warnings or skipped items}
31
39
 
@@ -33,6 +41,7 @@ Present this summary after verification completes.
33
41
  │ 1. Review CLAUDE.md and customise for your project
34
42
  │ 2. Add domain knowledge to context/
35
43
  │ 3. Run /speccy to design your first feature
44
+ │ 4. Restart Claude Code to activate plugin changes (if any applied)
36
45
 
37
46
  └─────────────────────────────────────────────────
38
47
  ```
@@ -102,12 +102,17 @@ For each row, in order:
102
102
  4. After all checks: summarize what's available and what's degraded
103
103
 
104
104
  1. Capture **PLAN** (the user's argument) and **FLAGS**
105
- 2. **Load project context** — invoke `/prime` to load domain-specific context
105
+ 2. **Clear pending-build marker** — if a marker was left by `/speccy`, clear it:
106
+ ```bash
107
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/slamb2k}"
108
+ node -e "require('$PLUGIN_ROOT/hooks/lib/state.cjs').clearPendingBuild(process.cwd())"
109
+ ```
110
+ 3. **Load project context** — invoke `/prime` to load domain-specific context
106
111
  (CLAUDE.md, specs, memory). If /prime is unavailable, fall back to
107
112
  manually scanning CLAUDE.md and specs/ directory.
108
- 3. Detect project type using `references/project-detection.md` to populate
113
+ 4. Detect project type using `references/project-detection.md` to populate
109
114
  **PROJECT_CONFIG** (language, test_runner, test_setup)
110
- 4. **Create task list** — ALWAYS create tasks upfront for all stages using
115
+ 5. **Create task list** — ALWAYS create tasks upfront for all stages using
111
116
  `TaskCreate`. This provides visible progress tracking throughout the build:
112
117
  - Task: "Stage 1: Explore codebase"
113
118
  - Task: "Stage 2: Clarifying questions" (if not `--skip-questions`)
@@ -117,11 +122,11 @@ For each row, in order:
117
122
  - Task: "Stage 7: Verify"
118
123
  - Task: "Stage 9: Ship" (if not `--no-ship`)
119
124
  Mark each task `in_progress` when starting and `completed` when done.
120
- 5. Check for outstanding items from previous work:
125
+ 6. Check for outstanding items from previous work:
121
126
  - Query persistent tasks via `TaskList` for incomplete items
122
127
  - Search CLAUDE.md for a "Known Issues" or "Open Questions" section
123
128
  - Search memory (if available) for recent unresolved items
124
- 4. If outstanding items found, present via AskUserQuestion:
129
+ 7. If outstanding items found, present via AskUserQuestion:
125
130
  ```
126
131
  "Found {count} outstanding items from previous work:"
127
132
  {numbered list with summary of each}
@@ -1,12 +1,12 @@
1
1
  {
2
- "generated": "2026-03-24T15:07:20.484Z",
2
+ "generated": "2026-04-17T04:43:53.359Z",
3
3
  "count": 10,
4
4
  "skills": [
5
5
  {
6
6
  "name": "brace",
7
7
  "directory": "brace",
8
8
  "description": "'Initialize any project directory with a standard scaffold for AI-assisted development. Creates specs/ and context/ directories, a project CLAUDE.md with development workflow and guardrails, .gitignore, and branch protection. Recommends claude-mem for persistent memory. Idempotent — safe to run on existing projects. Triggers: \"init project\", \"setup brace\", \"brace\", \"initialize\", \"bootstrap\", \"scaffold\".'",
9
- "lines": 460,
9
+ "lines": 427,
10
10
  "hasScripts": false,
11
11
  "hasReferences": true,
12
12
  "hasAssets": true,
@@ -16,7 +16,7 @@
16
16
  "name": "build",
17
17
  "directory": "build",
18
18
  "description": "Context-isolated feature development pipeline. Takes a detailed design/plan as argument and executes the full feature-dev lifecycle (explore, question, architect, implement, review, ship) inside subagents so the primary conversation stays compact. Use when you have a well-defined plan and want autonomous execution with minimal context window consumption.",
19
- "lines": 451,
19
+ "lines": 456,
20
20
  "hasScripts": false,
21
21
  "hasReferences": true,
22
22
  "hasAssets": false,
@@ -86,7 +86,7 @@
86
86
  "name": "speccy",
87
87
  "directory": "speccy",
88
88
  "description": "Deep-dive interview skill for creating comprehensive specifications. Reviews existing code and docs, then interviews the user through multiple rounds of targeted questions covering technical implementation, UI/UX, concerns, and tradeoffs. Produces a structured spec in specs/. Use when starting a new feature, system, or major change that needs a spec.",
89
- "lines": 249,
89
+ "lines": 260,
90
90
  "hasScripts": false,
91
91
  "hasReferences": true,
92
92
  "hasAssets": false,
@@ -232,10 +232,21 @@ After the spec is created, report to the user:
232
232
  └─────────────────────────────────────────────────
233
233
  ```
234
234
 
235
+ Then write a **pending-build marker** so the next session knows about this spec:
236
+
237
+ ```bash
238
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/slamb2k}"
239
+ node -e "require('$PLUGIN_ROOT/hooks/lib/state.cjs').savePendingBuild(process.cwd(), '{spec file path}')"
240
+ ```
241
+
242
+ This marker is picked up by the session-guard hook on the next session start
243
+ (including after `/clear`), which surfaces the build command automatically.
244
+
235
245
  Then display the build command:
236
246
 
237
247
  ```
238
248
  ⚡ To implement, run: /build {spec file path}
249
+ (You can /clear first — the spec is saved and the next session will remind you)
239
250
  ```
240
251
 
241
252
  The spec file persists on disk, so the user can `/clear` the conversation