start-vibing-stacks 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +16 -2
- package/dist/migrate.d.ts +27 -0
- package/dist/migrate.js +217 -0
- package/dist/setup.js +10 -0
- package/package.json +1 -1
- package/stacks/_shared/agents/claude-md-compactor.md +1 -0
- package/stacks/_shared/agents/commit-manager.md +1 -0
- package/stacks/_shared/agents/documenter.md +1 -0
- package/stacks/_shared/agents/domain-updater.md +1 -0
- package/stacks/_shared/agents/research-web.md +1 -0
- package/stacks/_shared/agents/security-auditor.md +168 -0
- package/stacks/_shared/agents/tester.md +1 -0
- package/stacks/_shared/hooks/final-check.ts +205 -0
- package/stacks/_shared/hooks/stop-validator.ts +77 -1
- package/stacks/_shared/skills/accessibility-wcag22/SKILL.md +284 -0
- package/stacks/_shared/skills/ci-pipelines/SKILL.md +166 -0
- package/stacks/_shared/skills/codebase-knowledge/SKILL.md +5 -0
- package/stacks/_shared/skills/database-migrations/SKILL.md +256 -0
- package/stacks/_shared/skills/debugging-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/docker-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/docs-tracker/SKILL.md +5 -0
- package/stacks/_shared/skills/error-handling/SKILL.md +335 -0
- package/stacks/_shared/skills/final-check/SKILL.md +74 -37
- package/stacks/_shared/skills/git-workflow/SKILL.md +5 -0
- package/stacks/_shared/skills/hook-development/SKILL.md +5 -0
- package/stacks/_shared/skills/observability/SKILL.md +351 -0
- package/stacks/_shared/skills/performance-patterns/SKILL.md +5 -0
- package/stacks/_shared/skills/playwright-automation/SKILL.md +5 -0
- package/stacks/_shared/skills/quality-gate/SKILL.md +5 -0
- package/stacks/_shared/skills/research-cache/SKILL.md +5 -0
- package/stacks/_shared/skills/secrets-management/SKILL.md +245 -0
- package/stacks/_shared/skills/security-baseline/SKILL.md +202 -0
- package/stacks/_shared/skills/test-coverage/SKILL.md +5 -0
- package/stacks/_shared/skills/ui-ux-audit/SKILL.md +5 -0
- package/stacks/frontend/react/skills/preline-ui/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-standards/SKILL.md +5 -0
- package/stacks/frontend/react/skills/react-ui-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/shadcn-ui/SKILL.md +5 -0
- package/stacks/frontend/react/skills/tailwind-patterns/SKILL.md +5 -0
- package/stacks/frontend/react/skills/zod-validation/SKILL.md +5 -0
- package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +5 -0
- package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +5 -0
- package/stacks/nodejs/skills/api-security-node/SKILL.md +275 -0
- package/stacks/nodejs/skills/bun-runtime/SKILL.md +5 -0
- package/stacks/nodejs/skills/mongoose-patterns/SKILL.md +5 -0
- package/stacks/nodejs/skills/nextjs-app-router/SKILL.md +5 -0
- package/stacks/nodejs/skills/trpc-api/SKILL.md +5 -0
- package/stacks/nodejs/skills/typescript-strict/SKILL.md +5 -0
- package/stacks/nodejs/stack.json +2 -1
- package/stacks/nodejs/workflows/ci.yml +90 -0
- package/stacks/nodejs/workflows/security.yml +45 -0
- package/stacks/php/skills/api-design/SKILL.md +5 -0
- package/stacks/php/skills/api-security/SKILL.md +5 -0
- package/stacks/php/skills/composer-workflow/SKILL.md +5 -0
- package/stacks/php/skills/external-api-patterns/SKILL.md +5 -0
- package/stacks/php/skills/inertia-react/SKILL.md +5 -0
- package/stacks/php/skills/laravel-inertia-i18n/SKILL.md +5 -0
- package/stacks/php/skills/laravel-octane/SKILL.md +5 -0
- package/stacks/php/skills/laravel-patterns/SKILL.md +5 -0
- package/stacks/php/skills/mariadb-octane/SKILL.md +5 -0
- package/stacks/php/skills/php-patterns/SKILL.md +5 -0
- package/stacks/php/skills/phpstan-analysis/SKILL.md +5 -0
- package/stacks/php/skills/phpunit-testing/SKILL.md +5 -0
- package/stacks/php/skills/security-scan-php/SKILL.md +5 -0
- package/stacks/php/workflows/ci.yml +106 -0
- package/stacks/php/workflows/security.yml +36 -0
- package/stacks/python/skills/api-security-python/SKILL.md +312 -0
- package/stacks/python/skills/async-patterns/SKILL.md +5 -0
- package/stacks/python/skills/django-patterns/SKILL.md +5 -0
- package/stacks/python/skills/fastapi-patterns/SKILL.md +5 -0
- package/stacks/python/skills/pydantic-validation/SKILL.md +5 -0
- package/stacks/python/skills/pytest-testing/SKILL.md +5 -0
- package/stacks/python/skills/python-patterns/SKILL.md +5 -0
- package/stacks/python/skills/python-performance/SKILL.md +5 -0
- package/stacks/python/skills/scripting-automation/SKILL.md +5 -0
- package/stacks/python/stack.json +2 -1
- package/stacks/python/workflows/ci.yml +76 -0
- package/stacks/python/workflows/security.yml +56 -0
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const PKG_VERSION = JSON.parse(readFileSync(join(CLI_ROOT, 'package.json'), 'utf
|
|
|
24
24
|
// CLI Arguments
|
|
25
25
|
// =============================================================================
|
|
26
26
|
const args = process.argv.slice(2);
|
|
27
|
+
const SUBCOMMAND = args[0] && !args[0].startsWith('-') ? args[0] : null;
|
|
27
28
|
const FLAGS = {
|
|
28
29
|
force: args.includes('--force'),
|
|
29
30
|
noClaude: args.includes('--no-claude'),
|
|
@@ -31,6 +32,7 @@ const FLAGS = {
|
|
|
31
32
|
noInstall: args.includes('--no-install'),
|
|
32
33
|
help: args.includes('--help') || args.includes('-h'),
|
|
33
34
|
version: args.includes('--version') || args.includes('-v'),
|
|
35
|
+
apply: args.includes('--apply'),
|
|
34
36
|
};
|
|
35
37
|
if (FLAGS.version) {
|
|
36
38
|
console.log(PKG_VERSION);
|
|
@@ -40,10 +42,16 @@ if (FLAGS.help) {
|
|
|
40
42
|
console.log(ui.LOGO);
|
|
41
43
|
console.log(`
|
|
42
44
|
${chalk.bold('Usage:')}
|
|
43
|
-
npx start-vibing-stacks [options]
|
|
45
|
+
npx start-vibing-stacks [command] [options]
|
|
46
|
+
|
|
47
|
+
${chalk.bold('Commands:')}
|
|
48
|
+
(default) Setup or resume current project
|
|
49
|
+
migrate Compare installed vs bundled skill/agent versions
|
|
50
|
+
Add --apply to update outdated/missing items
|
|
44
51
|
|
|
45
52
|
${chalk.bold('Options:')}
|
|
46
|
-
--force Overwrite existing configuration
|
|
53
|
+
--force Overwrite existing configuration (default command)
|
|
54
|
+
--apply Apply updates (migrate command)
|
|
47
55
|
--no-claude Skip Claude Code installation
|
|
48
56
|
--no-mcp Skip MCP server selection
|
|
49
57
|
--no-install Skip dependency installation
|
|
@@ -57,6 +65,12 @@ if (FLAGS.help) {
|
|
|
57
65
|
`);
|
|
58
66
|
process.exit(0);
|
|
59
67
|
}
|
|
68
|
+
// Subcommand: migrate
|
|
69
|
+
if (SUBCOMMAND === 'migrate') {
|
|
70
|
+
const { runMigrate } = await import('./migrate.js');
|
|
71
|
+
await runMigrate(process.cwd(), { apply: FLAGS.apply });
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
60
74
|
const AVAILABLE_STACKS = [
|
|
61
75
|
{ id: 'php', name: 'PHP 8.3+', icon: '🐘', available: true },
|
|
62
76
|
{ id: 'nodejs', name: 'Node.js / TypeScript', icon: '📦', available: true },
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start Vibing Stacks — Migrate
|
|
3
|
+
*
|
|
4
|
+
* Compares the SKILL.md / agent / hook versions installed in .claude/
|
|
5
|
+
* against the bundled stacks/<stack>/ and stacks/_shared/ versions.
|
|
6
|
+
*
|
|
7
|
+
* Reports outdated, missing, and modified items. Optionally upgrades.
|
|
8
|
+
*
|
|
9
|
+
* Skill version contract:
|
|
10
|
+
* YAML frontmatter at top of SKILL.md must include `version: X.Y.Z`.
|
|
11
|
+
* No frontmatter = treated as "v0" / pre-versioning.
|
|
12
|
+
*/
|
|
13
|
+
export interface MigrateItem {
|
|
14
|
+
kind: 'skill' | 'agent' | 'hook';
|
|
15
|
+
name: string;
|
|
16
|
+
source: string;
|
|
17
|
+
target: string;
|
|
18
|
+
bundledVersion: string;
|
|
19
|
+
installedVersion: string | null;
|
|
20
|
+
status: 'missing' | 'outdated' | 'current' | 'ahead' | 'modified-no-version';
|
|
21
|
+
}
|
|
22
|
+
export interface MigrateOptions {
|
|
23
|
+
apply: boolean;
|
|
24
|
+
scope?: 'skills' | 'agents' | 'hooks' | 'all';
|
|
25
|
+
}
|
|
26
|
+
export declare function planMigration(projectDir: string, opts: MigrateOptions): MigrateItem[];
|
|
27
|
+
export declare function runMigrate(projectDir: string, opts: MigrateOptions): Promise<void>;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start Vibing Stacks — Migrate
|
|
3
|
+
*
|
|
4
|
+
* Compares the SKILL.md / agent / hook versions installed in .claude/
|
|
5
|
+
* against the bundled stacks/<stack>/ and stacks/_shared/ versions.
|
|
6
|
+
*
|
|
7
|
+
* Reports outdated, missing, and modified items. Optionally upgrades.
|
|
8
|
+
*
|
|
9
|
+
* Skill version contract:
|
|
10
|
+
* YAML frontmatter at top of SKILL.md must include `version: X.Y.Z`.
|
|
11
|
+
* No frontmatter = treated as "v0" / pre-versioning.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, copyFileSync, mkdirSync, statSync, readdirSync } from 'fs';
|
|
14
|
+
import { join, relative, dirname, resolve } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import * as semver from 'semver';
|
|
17
|
+
import * as ui from './ui.js';
|
|
18
|
+
const __m_filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __m_dirname = dirname(__m_filename);
|
|
20
|
+
const CLI_ROOT = resolve(__m_dirname, '..');
|
|
21
|
+
const FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---/;
|
|
22
|
+
const VERSION_RE = /^version:\s*["']?([0-9]+\.[0-9]+\.[0-9]+(?:-[A-Za-z0-9.-]+)?)["']?\s*$/m;
|
|
23
|
+
function parseVersion(file) {
|
|
24
|
+
if (!existsSync(file))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
const content = readFileSync(file, 'utf8');
|
|
28
|
+
const fm = FRONTMATTER_RE.exec(content);
|
|
29
|
+
if (!fm)
|
|
30
|
+
return null;
|
|
31
|
+
const v = VERSION_RE.exec(fm[1] ?? '');
|
|
32
|
+
return v?.[1] ?? null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function statusOf(bundled, installed) {
|
|
39
|
+
if (installed === null)
|
|
40
|
+
return 'modified-no-version';
|
|
41
|
+
const cmp = semver.compare(installed, bundled);
|
|
42
|
+
if (cmp < 0)
|
|
43
|
+
return 'outdated';
|
|
44
|
+
if (cmp > 0)
|
|
45
|
+
return 'ahead';
|
|
46
|
+
return 'current';
|
|
47
|
+
}
|
|
48
|
+
function listSubdirs(dir) {
|
|
49
|
+
if (!existsSync(dir))
|
|
50
|
+
return [];
|
|
51
|
+
try {
|
|
52
|
+
return readdirSync(dir).filter(n => {
|
|
53
|
+
try {
|
|
54
|
+
return statSync(join(dir, n)).isDirectory();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function listFiles(dir, suffix) {
|
|
66
|
+
if (!existsSync(dir))
|
|
67
|
+
return [];
|
|
68
|
+
try {
|
|
69
|
+
return readdirSync(dir).filter(n => n.endsWith(suffix));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function listSkillSources(stack, frontendSkillsDir) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const sharedDir = join(CLI_ROOT, 'stacks', '_shared', 'skills');
|
|
78
|
+
const stackDir = join(CLI_ROOT, 'stacks', stack, 'skills');
|
|
79
|
+
const frontendDir = frontendSkillsDir
|
|
80
|
+
? join(CLI_ROOT, 'stacks', 'frontend', frontendSkillsDir, 'skills')
|
|
81
|
+
: null;
|
|
82
|
+
for (const dir of [sharedDir, stackDir, frontendDir].filter(Boolean)) {
|
|
83
|
+
for (const entry of listSubdirs(dir)) {
|
|
84
|
+
const skillPath = join(dir, entry, 'SKILL.md');
|
|
85
|
+
if (existsSync(skillPath))
|
|
86
|
+
out.push({ source: skillPath, name: entry });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function listAgentSources() {
|
|
92
|
+
const dir = join(CLI_ROOT, 'stacks', '_shared', 'agents');
|
|
93
|
+
return listFiles(dir, '.md').map(n => ({ source: join(dir, n), name: n }));
|
|
94
|
+
}
|
|
95
|
+
function listHookSources() {
|
|
96
|
+
const dir = join(CLI_ROOT, 'stacks', '_shared', 'hooks');
|
|
97
|
+
return listFiles(dir, '.ts').map(n => ({ source: join(dir, n), name: n }));
|
|
98
|
+
}
|
|
99
|
+
function loadProjectConfig(projectDir) {
|
|
100
|
+
const path = join(projectDir, '.claude', 'config', 'active-project.json');
|
|
101
|
+
if (!existsSync(path))
|
|
102
|
+
return null;
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function planMigration(projectDir, opts) {
|
|
111
|
+
const config = loadProjectConfig(projectDir);
|
|
112
|
+
if (!config) {
|
|
113
|
+
ui.error('No .claude/config/active-project.json found. Run `npx start-vibing-stacks` first.');
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const items = [];
|
|
117
|
+
const scope = opts.scope ?? 'all';
|
|
118
|
+
if (scope === 'all' || scope === 'skills') {
|
|
119
|
+
for (const { source, name } of listSkillSources(config.stack, config.frontendSkillsDir)) {
|
|
120
|
+
const target = join(projectDir, '.claude', 'skills', name, 'SKILL.md');
|
|
121
|
+
const bundledVersion = parseVersion(source);
|
|
122
|
+
if (!bundledVersion)
|
|
123
|
+
continue;
|
|
124
|
+
const installedVersion = parseVersion(target);
|
|
125
|
+
const status = !existsSync(target)
|
|
126
|
+
? 'missing'
|
|
127
|
+
: statusOf(bundledVersion, installedVersion);
|
|
128
|
+
items.push({ kind: 'skill', name, source, target, bundledVersion, installedVersion, status });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (scope === 'all' || scope === 'agents') {
|
|
132
|
+
for (const { source, name } of listAgentSources()) {
|
|
133
|
+
const target = join(projectDir, '.claude', 'agents', name);
|
|
134
|
+
const bundledVersion = parseVersion(source);
|
|
135
|
+
if (!bundledVersion)
|
|
136
|
+
continue;
|
|
137
|
+
const installedVersion = parseVersion(target);
|
|
138
|
+
const status = !existsSync(target)
|
|
139
|
+
? 'missing'
|
|
140
|
+
: statusOf(bundledVersion, installedVersion);
|
|
141
|
+
items.push({ kind: 'agent', name, source, target, bundledVersion, installedVersion, status });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (scope === 'all' || scope === 'hooks') {
|
|
145
|
+
for (const { source, name } of listHookSources()) {
|
|
146
|
+
const target = join(projectDir, '.claude', 'hooks', name);
|
|
147
|
+
const bundledVersion = '0.0.0';
|
|
148
|
+
const installedVersion = existsSync(target) ? '0.0.0' : null;
|
|
149
|
+
const status = !existsSync(target) ? 'missing' : 'current';
|
|
150
|
+
items.push({ kind: 'hook', name, source, target, bundledVersion, installedVersion, status });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return items;
|
|
154
|
+
}
|
|
155
|
+
function applyOne(item) {
|
|
156
|
+
mkdirSync(dirname(item.target), { recursive: true });
|
|
157
|
+
copyFileSync(item.source, item.target);
|
|
158
|
+
}
|
|
159
|
+
export async function runMigrate(projectDir, opts) {
|
|
160
|
+
ui.header('🔄 Start Vibing — Migrate');
|
|
161
|
+
const items = planMigration(projectDir, opts);
|
|
162
|
+
if (items.length === 0) {
|
|
163
|
+
ui.info('Nothing to migrate.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const grouped = {
|
|
167
|
+
missing: [], outdated: [], current: [], ahead: [], 'modified-no-version': [],
|
|
168
|
+
};
|
|
169
|
+
for (const it of items)
|
|
170
|
+
grouped[it.status].push(it);
|
|
171
|
+
const summary = (label, list) => {
|
|
172
|
+
if (list.length === 0)
|
|
173
|
+
return;
|
|
174
|
+
console.log(`\n ${label} (${list.length}):`);
|
|
175
|
+
for (const it of list.slice(0, 30)) {
|
|
176
|
+
const v = it.installedVersion ?? '–';
|
|
177
|
+
console.log(` ${it.kind.padEnd(5)} ${it.name.padEnd(32)} installed=${v.padEnd(8)} bundled=${it.bundledVersion}`);
|
|
178
|
+
}
|
|
179
|
+
if (list.length > 30)
|
|
180
|
+
console.log(` ... and ${list.length - 30} more`);
|
|
181
|
+
};
|
|
182
|
+
summary('MISSING', grouped.missing);
|
|
183
|
+
summary('OUTDATED', grouped.outdated);
|
|
184
|
+
summary('AHEAD (installed newer than bundled — kept)', grouped.ahead);
|
|
185
|
+
summary('UNVERSIONED (manual review)', grouped['modified-no-version']);
|
|
186
|
+
summary('CURRENT', grouped.current);
|
|
187
|
+
const upgradable = [...grouped.missing, ...grouped.outdated];
|
|
188
|
+
if (upgradable.length === 0) {
|
|
189
|
+
console.log('');
|
|
190
|
+
ui.success('Everything up to date.');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!opts.apply) {
|
|
194
|
+
console.log('');
|
|
195
|
+
ui.info(`Run with --apply to install/update ${upgradable.length} item(s).`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
console.log('');
|
|
199
|
+
for (const it of upgradable) {
|
|
200
|
+
try {
|
|
201
|
+
applyOne(it);
|
|
202
|
+
ui.success(`updated ${it.kind} ${it.name} (${it.installedVersion ?? '–'} → ${it.bundledVersion})`);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
ui.warn(`failed ${it.kind} ${it.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
console.log('');
|
|
209
|
+
ui.success(`Migration complete: ${upgradable.length} item(s) updated.`);
|
|
210
|
+
if (grouped['modified-no-version'].length > 0) {
|
|
211
|
+
console.log('');
|
|
212
|
+
ui.warn(`${grouped['modified-no-version'].length} unversioned local item(s) skipped — review manually.`);
|
|
213
|
+
for (const it of grouped['modified-no-version'].slice(0, 10)) {
|
|
214
|
+
console.log(` ${relative(projectDir, it.target)}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -169,6 +169,16 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
169
169
|
spinner.text = `Imported ${config.standardsReview.patterns.length} project standards`;
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
// 11d. Copy CI workflow templates (only when target dir is empty or --force)
|
|
173
|
+
const stackWorkflowsDir = join(PACKAGE_ROOT, 'stacks', config.stack, 'workflows');
|
|
174
|
+
if (existsSync(stackWorkflowsDir)) {
|
|
175
|
+
const ghWorkflowsDir = join(projectDir, '.github', 'workflows');
|
|
176
|
+
const targetIsEmpty = !existsSync(ghWorkflowsDir);
|
|
177
|
+
if (targetIsEmpty || options.force) {
|
|
178
|
+
copyDirRecursive(stackWorkflowsDir, ghWorkflowsDir, options.force);
|
|
179
|
+
spinner.text = 'Installed CI workflow templates';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
172
182
|
// 12. Copy commands
|
|
173
183
|
const sharedCommandsDir = join(PACKAGE_ROOT, 'stacks', '_shared', 'commands');
|
|
174
184
|
if (existsSync(sharedCommandsDir)) {
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claude-md-compactor
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: "AUTOMATICALLY invoke when CLAUDE.md exceeds 40k chars OR auto memory MEMORY.md exceeds 200 lines. Compacts while preserving critical knowledge by offloading to topic files."
|
|
4
5
|
model: sonnet
|
|
5
6
|
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: research-web
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: "AUTOMATICALLY invoke BEFORE implementing any new feature or technology. Triggers: new feature, new technology, 'search', 'find info'. Web research specialist."
|
|
4
5
|
model: sonnet
|
|
5
6
|
tools: WebSearch, WebFetch, Read, Write
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-auditor
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "AUTOMATICALLY invoke when code touches auth, sessions, user data, passwords, tokens, API routes, database queries, cookies, or env vars. VETO POWER — blocks insecure code. Runs AFTER tester, BEFORE quality-gate."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: Read, Grep, Glob, Bash
|
|
7
|
+
skills: security-baseline, secrets-management
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Security Auditor Agent
|
|
11
|
+
|
|
12
|
+
You audit code for security flaws. **You have VETO power** — when violations are found you block the workflow and require fixes.
|
|
13
|
+
|
|
14
|
+
## When You Run
|
|
15
|
+
|
|
16
|
+
After implementation and tester, **before** quality-gate and commit-manager. Always run when modified files include:
|
|
17
|
+
|
|
18
|
+
- Auth, session, login, register, password, token, JWT
|
|
19
|
+
- Route Handlers, Server Actions, controllers, API endpoints
|
|
20
|
+
- Database queries, ORM models
|
|
21
|
+
- Cookie / header / CORS / CSP configuration
|
|
22
|
+
- File uploads
|
|
23
|
+
- Anything reading `process.env` / `os.environ` / `$_ENV` / `env()`
|
|
24
|
+
|
|
25
|
+
## Step 1 — Read Stack Context
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cat .claude/config/active-project.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Branch logic on `stack`:
|
|
32
|
+
- `nodejs` → load `api-security-node` skill
|
|
33
|
+
- `python` → load `api-security-python` skill
|
|
34
|
+
- `php` → load `api-security` skill
|
|
35
|
+
- always → `security-baseline`, `secrets-management`
|
|
36
|
+
|
|
37
|
+
## Step 2 — Identify Modified Files
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git diff --name-only --diff-filter=AM HEAD
|
|
41
|
+
git diff --name-only --cached --diff-filter=AM
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Read each modified source file.
|
|
45
|
+
|
|
46
|
+
## Step 3 — Run the Audit Matrix
|
|
47
|
+
|
|
48
|
+
Apply each check below. **One violation = block.**
|
|
49
|
+
|
|
50
|
+
### A. Authn / Session
|
|
51
|
+
|
|
52
|
+
| Check | Pattern that fails |
|
|
53
|
+
|---|---|
|
|
54
|
+
| User ID from session, not body | `req.body.userId`, `request.json()["user_id"]`, `$request->input('user_id')` used for ownership |
|
|
55
|
+
| Auth gate before logic | Route handler with no `auth()` / `Depends(current_user)` / `auth:sanctum` middleware |
|
|
56
|
+
| Algorithm pinned on JWT | `jwt.verify(token)` without `algorithms` array |
|
|
57
|
+
| Token in HttpOnly cookie | `localStorage.setItem('token', ...)` or token rendered into HTML |
|
|
58
|
+
|
|
59
|
+
### B. Authz
|
|
60
|
+
|
|
61
|
+
| Check | Pattern that fails |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Object-level scope | `Model.findById(id)` with no `where userId = session.user.id` |
|
|
64
|
+
| Role check on server | Role check only in client/UI |
|
|
65
|
+
| Mass assignment guard | `User.create(req.body)` without allowlist / Zod `.strict()` / Pydantic `extra="forbid"` / `$fillable` |
|
|
66
|
+
|
|
67
|
+
### C. Input Validation
|
|
68
|
+
|
|
69
|
+
| Check | Pattern that fails |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Schema at boundary | Route handler reads `req.body` / `request.json()` without Zod / Pydantic / FormRequest |
|
|
72
|
+
| Strict mode | Schema present but allows extra keys |
|
|
73
|
+
| Mongo operator injection | `User.findOne({ email: req.body.email })` without coercing email to string |
|
|
74
|
+
| SQL bindings | f-string / template-literal SQL: `f"... {x} ..."`, `\`SELECT ... ${x}\``, `"... $x ..."` |
|
|
75
|
+
|
|
76
|
+
### D. Secrets / Env
|
|
77
|
+
|
|
78
|
+
Run secrets scan:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Quick high-signal grep
|
|
82
|
+
git diff --cached -U0 \
|
|
83
|
+
| grep -E '(api[_-]?key|secret|token|password|bearer|aws_|private_key)' \
|
|
84
|
+
| grep -vE '\.env\.example|TEMPLATE|placeholder|example|<your|YOUR_|XXXX'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Also check:
|
|
88
|
+
- No `NEXT_PUBLIC_` containing `SECRET|TOKEN|PRIVATE|PASSWORD|CREDENTIAL`
|
|
89
|
+
- No hardcoded connection string with embedded password
|
|
90
|
+
- `.env` is in `.gitignore`
|
|
91
|
+
- `.env.example` exists if any env var is read
|
|
92
|
+
|
|
93
|
+
### E. Cookies
|
|
94
|
+
|
|
95
|
+
| Check | Required |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `httpOnly: true` | yes |
|
|
98
|
+
| `secure: true` in prod | yes |
|
|
99
|
+
| `sameSite` set | `lax` or `strict` |
|
|
100
|
+
|
|
101
|
+
### F. CORS / Headers
|
|
102
|
+
|
|
103
|
+
- No `origin: '*'` or `allow_origins=["*"]` combined with `credentials: true` / `allow_credentials=True`
|
|
104
|
+
- Security headers configured (Helmet / middleware): HSTS, CSP, X-Content-Type-Options, Referrer-Policy
|
|
105
|
+
|
|
106
|
+
### G. Rate Limiting
|
|
107
|
+
|
|
108
|
+
- Auth endpoints (`/login`, `/register`, `/password/reset`) have a rate limiter
|
|
109
|
+
- Webhook endpoints reject requests without verified signature
|
|
110
|
+
|
|
111
|
+
### H. Logging
|
|
112
|
+
|
|
113
|
+
- No `console.log(req.body)` / `print(request.json())` / `Log::info($request->all())`
|
|
114
|
+
- No `console.log(token)` / logging of cookies, headers, or `Authorization`
|
|
115
|
+
- No PII (email, phone, full name) in logs without redaction
|
|
116
|
+
|
|
117
|
+
### I. SSRF
|
|
118
|
+
|
|
119
|
+
- User-supplied URLs are validated against an allowlist OR private IP ranges are blocked
|
|
120
|
+
|
|
121
|
+
### J. Webhook Verification
|
|
122
|
+
|
|
123
|
+
- Stripe / GitHub / GitHub App webhooks call `constructEvent` / signature verifier **before** parsing body
|
|
124
|
+
|
|
125
|
+
## Step 4 — Report
|
|
126
|
+
|
|
127
|
+
If everything passes, output:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
✅ Security audit passed.
|
|
131
|
+
Files audited: <n>
|
|
132
|
+
Checks: A-J all green.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If violations are found, output:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
🛑 SECURITY AUDIT BLOCKED
|
|
139
|
+
|
|
140
|
+
<n> violation(s) found:
|
|
141
|
+
|
|
142
|
+
1. [HIGH] <file>:<line>
|
|
143
|
+
Issue: <one line>
|
|
144
|
+
Fix: <one line>
|
|
145
|
+
Reference: security-baseline §A01 (or whichever)
|
|
146
|
+
|
|
147
|
+
2. [MEDIUM] ...
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Severity:
|
|
151
|
+
- **CRITICAL** — RCE, SQLi, missing auth, secret in commit. Block immediately.
|
|
152
|
+
- **HIGH** — Authz bypass, missing CSRF, unsafe cookie. Block.
|
|
153
|
+
- **MEDIUM** — Missing rate limit, weak headers. Block.
|
|
154
|
+
- **LOW** — Minor logging concern. Warn, allow.
|
|
155
|
+
|
|
156
|
+
## Rules
|
|
157
|
+
|
|
158
|
+
1. **VETO POWER** — `domain-updater` and `commit-manager` MUST NOT run while you have unresolved CRITICAL/HIGH/MEDIUM findings.
|
|
159
|
+
2. **READ THE CODE** — never approve based on file names alone.
|
|
160
|
+
3. **NO FALSE NEGATIVES > FALSE POSITIVES** — when in doubt, flag and explain.
|
|
161
|
+
4. **CITE THE FIX** — every finding has a one-line fix and a skill reference.
|
|
162
|
+
5. **RE-RUN AFTER FIXES** — never trust "I fixed it" without re-reading the file.
|
|
163
|
+
|
|
164
|
+
## See Also
|
|
165
|
+
|
|
166
|
+
- Skill `security-baseline` — universal OWASP rules
|
|
167
|
+
- Skill `secrets-management` — env hygiene
|
|
168
|
+
- Stack-specific skill: `api-security-node` / `api-security-python` / PHP `api-security`
|