@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.
- package/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +1 -1
- package/hooks/lib/config.cjs +13 -0
- package/hooks/lib/git-checks.cjs +12 -6
- package/hooks/lib/plugin-health.cjs +107 -0
- package/hooks/lib/state.cjs +92 -4
- package/hooks/lib/utils.cjs +1 -0
- package/hooks/session-guard.cjs +76 -8
- package/package.json +1 -1
- package/skills/brace/SKILL.md +92 -125
- package/skills/brace/assets/gitignore-template +4 -0
- package/skills/brace/references/branch-protection-steps.md +135 -0
- package/skills/brace/references/claude-md-template.md +2 -0
- package/skills/brace/references/phase-prompts.md +111 -0
- package/skills/brace/references/plugin-tuning-steps.md +125 -0
- package/skills/brace/references/report-template.md +9 -0
- package/skills/build/SKILL.md +10 -5
- package/skills/manifest.json +4 -4
- package/skills/speccy/SKILL.md +11 -0
|
@@ -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
|
}
|
package/hooks/lib/config.cjs
CHANGED
|
@@ -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
|
};
|
package/hooks/lib/git-checks.cjs
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
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 };
|
package/hooks/lib/state.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 };
|
package/hooks/lib/utils.cjs
CHANGED
package/hooks/session-guard.cjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
package/skills/brace/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
+
---
|
|
355
325
|
|
|
356
|
-
|
|
357
|
-
otherwise fall back to REST API:
|
|
326
|
+
## Phase 7: Plugin Performance Tuning
|
|
358
327
|
|
|
359
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
334
|
+
If `--force` is set, apply all recommendations without prompting.
|
|
381
335
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
###
|
|
364
|
+
### Present Findings
|
|
428
365
|
|
|
429
|
-
If
|
|
366
|
+
If PLUGIN_REPORT contains zero findings, output:
|
|
430
367
|
```
|
|
431
|
-
|
|
368
|
+
━━ 7 · Plugin Performance ━━━━━━━━━━━━━━━━━━━━
|
|
369
|
+
⏭️ No plugin optimisations needed
|
|
432
370
|
```
|
|
371
|
+
Skip to the report.
|
|
433
372
|
|
|
434
|
-
|
|
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
|
|
|
@@ -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
|
```
|
package/skills/build/SKILL.md
CHANGED
|
@@ -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. **
|
|
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
|
-
|
|
113
|
+
4. Detect project type using `references/project-detection.md` to populate
|
|
109
114
|
**PROJECT_CONFIG** (language, test_runner, test_setup)
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
package/skills/manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-
|
|
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":
|
|
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":
|
|
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":
|
|
89
|
+
"lines": 260,
|
|
90
90
|
"hasScripts": false,
|
|
91
91
|
"hasReferences": true,
|
|
92
92
|
"hasAssets": false,
|
package/skills/speccy/SKILL.md
CHANGED
|
@@ -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
|