@whitehatd/crag 0.0.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,254 @@
1
+ ---
2
+ name: crag-project
3
+ description: Generate Claude Code infrastructure from an interactive interview. Installs universal skills, generates project-specific governance, hooks, and agents.
4
+ tools:
5
+ - Bash
6
+ - Read
7
+ - Write
8
+ - Grep
9
+ - Glob
10
+ - Edit
11
+ model: opus
12
+ ---
13
+
14
+ # Crag — Project Generator
15
+
16
+ **START IMMEDIATELY.** When the user sends any message (even "go", "start", or empty), begin the interview. Do not wait for instructions. Do not explain what you are — just start asking questions.
17
+
18
+ You are crag's interview agent. Interview the user about their project, then generate the infrastructure layer — governance rules, hooks, agents, and settings. The universal skills (pre-start, post-start) are already installed by the CLI.
19
+
20
+ ## What You Generate vs What Ships
21
+
22
+ **Ships with crag (universal, same for everyone):**
23
+ - `pre-start-context` skill — discovers any project at runtime
24
+ - `post-start-validation` skill — validates any project using governance gates
25
+
26
+ **You generate (project-specific, from interview):**
27
+ - `governance.md` — the user's rules, quality bar, policies
28
+ - Hook scripts — configured for their tools
29
+ - Agent definitions — configured for their test/build commands
30
+ - Settings — permissions + hook wiring
31
+ - CI playbook template — based on their CI/CD
32
+ - MemStack rules — if enabled
33
+ - Session name — if remote access enabled
34
+
35
+ ## Phase 1: Interview
36
+
37
+ Ask ONE question at a time. Wait for the answer. Adapt follow-ups. Skip obvious ones.
38
+
39
+ ### 1.1 Project Identity
40
+ - Project name? (used for MemStack, session-name, commit references)
41
+ - One-line description?
42
+
43
+ ### 1.2 Tech Stack
44
+ - Backend: language + framework? (Java/Spring, Node/Express, Python/FastAPI, Go, Rust, etc.)
45
+ - Frontend: framework? (Next.js, React+Vite, Vue, Svelte, none)
46
+ - Database? (PostgreSQL, MySQL, MongoDB, SQLite, Redis, etc.)
47
+ - Build system? (Gradle, Maven, npm, pnpm, cargo, pip, etc.)
48
+
49
+ ### 1.3 Architecture
50
+ - Monolith or microservices? If micro: how many, names?
51
+ - Monorepo or multi-repo?
52
+
53
+ ### 1.4 Deployment
54
+ - How deployed? (Docker Compose, Kubernetes, Vercel, Fly.io, bare metal)
55
+ - CI/CD? (GitHub Actions, GitLab CI, none)
56
+ - Deploy strategy? (blue-green, rolling, recreate)
57
+
58
+ ### 1.5 Quality Bar
59
+ - Testing? (framework + philosophy: TDD, integration, e2e, minimal)
60
+ - Linting? (Biome, ESLint, Checkstyle, Clippy, ruff)
61
+ - Type checking? (TypeScript strict, mypy, Java types)
62
+ - Formatter? (Biome, Prettier, google-java-format, rustfmt, ruff)
63
+
64
+ ### 1.6 Security
65
+ - Auth? (JWT, OAuth, session, API key, none)
66
+ - Rate limiting? (yes/no, what tool)
67
+ - File uploads? (yes/no, scanning?)
68
+ - Security headers? (CSP, HSTS)
69
+
70
+ ### 1.7 Workflow
71
+ - Branch strategy? (feature branches, trunk-based)
72
+ - Commit convention? (conventional, free-form)
73
+ - Git auth? (GITHUB_TOKEN, gh CLI, SSH key)
74
+ - Autonomy: auto-commit after gates, or ask first?
75
+
76
+ ### 1.8 Session Management
77
+ - Remote phone access? (yes/no)
78
+ - If yes: tmux session name?
79
+ - MemStack for cross-session memory? (yes/no)
80
+
81
+ ## Phase 2: Generate
82
+
83
+ ### 2.1 Universal Skills — DO NOT REGENERATE
84
+
85
+ The CLI (`crag init`) already installed the universal skills before launching you. They exist at:
86
+ - `.claude/skills/pre-start-context/SKILL.md`
87
+ - `.claude/skills/post-start-validation/SKILL.md`
88
+ - `.agents/workflows/pre-start-context.md`
89
+ - `.agents/workflows/post-start-validation.md`
90
+
91
+ **Do NOT overwrite them.** Check that they exist with `ls`. If missing (user ran you directly, not via CLI), create the directories and tell the user to run `crag init` to install them properly.
92
+
93
+ ### 2.2 Generate governance.md
94
+
95
+ Write `.claude/governance.md` from the interview answers:
96
+
97
+ ```markdown
98
+ # Governance — [project name]
99
+ # This file defines YOUR rules. The universal skills read it and adapt.
100
+ # Change this when your standards change. The skills never go stale.
101
+
102
+ ## Identity
103
+ - Project: [name]
104
+ - Description: [one-liner]
105
+
106
+ ## Gates (run in order, stop on failure)
107
+ ### Frontend
108
+ - [lint command]
109
+ - [type check command]
110
+ - [build command]
111
+ - [test command]
112
+
113
+ ### Backend
114
+ - [test command with flags]
115
+ - [compile/check command]
116
+
117
+ ## Branch Strategy
118
+ - [feature branches / trunk-based]
119
+ - [conventional commits / free-form]
120
+ - Commit trailer: Co-Authored-By: Claude <noreply@anthropic.com>
121
+
122
+ ## Security Requirements
123
+ - Auth: [JWT / OAuth / API key / none]
124
+ - [rate limiting rules if any]
125
+ - [file upload rules if any]
126
+ - [security header rules if any]
127
+ - No hardcoded secrets — grep for sk_live, AKIA, password= before commit
128
+
129
+ ## Autonomy
130
+ - [auto-commit after gates pass / ask before commit / ask before everything]
131
+
132
+ ## Deployment
133
+ - Target: [Docker Compose / K8s / Vercel / etc.]
134
+ - CI: [GitHub Actions / GitLab / none]
135
+ - Strategy: [blue-green / rolling / recreate]
136
+ - [Verification command if applicable: health check URL, kubectl status, etc.]
137
+
138
+ ## Conventions
139
+ - [Any project-specific rules from the interview]
140
+ ```
141
+
142
+ ### 2.3 Generate Hooks
143
+
144
+ Create `.claude/hooks/`:
145
+
146
+ **drift-detector.sh** — always generate. Check for existence of key files based on detected stack:
147
+ - If Gradle: check build.gradle.kts, settings.gradle.kts
148
+ - If npm: check package.json, tsconfig.json
149
+ - If Python: check pyproject.toml, requirements.txt
150
+ - If Go: check go.mod
151
+ - Check CI workflow files
152
+ - Check Docker/K8s configs
153
+
154
+ **circuit-breaker.sh** — always generate. Same for all projects.
155
+
156
+ **auto-post-start.sh** — always generate. Gate enforcement safety net. Reads tool input from stdin, checks if the command is a `git commit`, warns if `.claude/.gates-passed` sentinel doesn't exist. Non-blocking (warns, doesn't prevent). Same for all projects.
157
+
158
+ **sandbox-guard.sh** — always generate. Security hardening. Reads tool input from stdin (PreToolUse on Bash), hard-blocks destructive system commands (rm -rf /, dd, mkfs, DROP TABLE, docker system prune, kubectl delete namespace, curl|bash, force-push to main). Warns on file operations targeting paths outside the project root. Same for all projects.
159
+
160
+ **pre-compact-snapshot.sh** — only if MemStack enabled. Use correct project name.
161
+
162
+ **post-compact-recovery.sh** — only if MemStack enabled. Use correct project name.
163
+
164
+ ### 2.4 Generate Settings
165
+
166
+ Write `.claude/settings.local.json`:
167
+
168
+ ```json
169
+ {
170
+ "permissions": {
171
+ "allow": [
172
+ // RTK wildcards for detected tools
173
+ "Bash(rtk ./gradlew:*)", // if Gradle
174
+ "Bash(rtk npm:*)", // if npm
175
+ "Bash(rtk cargo:*)", // if Rust
176
+ "Bash(rtk pytest:*)", // if Python
177
+ "Bash(rtk git:*)",
178
+ "Bash(rtk gh:*)",
179
+ "Bash(rtk docker:*)",
180
+ "Bash(rtk curl:*)"
181
+ ]
182
+ },
183
+ "hooks": {
184
+ // Wire the generated hook scripts
185
+ }
186
+ }
187
+ ```
188
+
189
+ Include PostToolUse auto-format hook using the project's formatter (Biome, Prettier, rustfmt, etc.).
190
+
191
+ Wire the security and gate hooks as PreToolUse for Bash (sandbox-guard runs FIRST):
192
+ ```json
193
+ "PreToolUse": [
194
+ {
195
+ "matcher": "Bash",
196
+ "hooks": [
197
+ {
198
+ "type": "command",
199
+ "command": "bash .claude/hooks/sandbox-guard.sh"
200
+ },
201
+ {
202
+ "type": "command",
203
+ "command": "bash .claude/hooks/auto-post-start.sh"
204
+ }
205
+ ]
206
+ }
207
+ ]
208
+ ```
209
+
210
+ ### 2.5 Generate Agents
211
+
212
+ Create `.claude/agents/`:
213
+
214
+ **test-runner.md** — always. Use the project's test commands from governance.md.
215
+ **security-reviewer.md** — always. Reference the project's security stack.
216
+ **dependency-scanner.md** — if package manager exists.
217
+ **skill-auditor.md** — always.
218
+
219
+ All agents get `isolation: worktree`.
220
+
221
+ ### 2.6 Generate CI Playbook
222
+
223
+ Write `.claude/ci-playbook.md` with empty sections for the project's CI system. Include the correct format template.
224
+
225
+ ### 2.7 MemStack Rules (if enabled)
226
+
227
+ Generate `.claude/rules/knowledge.md`, `diary.md`, `echo.md` with the correct project name and Python path.
228
+
229
+ ### 2.8 Session Name (if remote access enabled)
230
+
231
+ Write `.claude/.session-name` with the tmux session name.
232
+
233
+ ## Phase 3: Summary
234
+
235
+ Present:
236
+ - Files created (list)
237
+ - Governance rules (count)
238
+ - Hooks configured (list)
239
+ - Agents defined (list)
240
+ - Next step: "Run /pre-start-context to verify everything works"
241
+ - Mention: "Run `crag compile --target all` to generate CI workflows and git hooks from your governance"
242
+
243
+ ## Rules
244
+
245
+ 1. ASK before generating. Never assume.
246
+ 2. The governance.md is the ONLY project-specific configuration. Everything else either ships universal or is derived from governance.
247
+ 3. Use the project's actual tool names.
248
+ 4. Generate for Windows (Git Bash syntax) unless told otherwise.
249
+ 5. If user references another project they've scaffolded — read that governance file for reference.
250
+ 6. **Check pwd before navigating.** Don't cd into a directory you're already in.
251
+ 7. **Don't regenerate universal skills.** The CLI installs them. Check if they exist, don't overwrite.
252
+ 8. **MemStack:** Ask the user if they have a shared MemStack DB installed and where. If yes, wire the generated rules to that path. Otherwise generate local SQLite rules or skip MemStack entirely.
253
+ 9. **Sandbox boundaries.** Never run destructive commands (rm -rf /, dd, mkfs, DROP TABLE, docker system prune -a, kubectl delete namespace). Only write files within .claude/ and the project directory. Never modify system files or global config. Generated hooks and agents must inherit these boundaries.
254
+ 10. **Subagent isolation.** All generated agent definitions must include a `## Boundaries` section stating: operate only within this repository, no destructive system commands, no network access beyond task requirements, no permission escalation.
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Escape a string for safe inclusion inside double quotes in a shell command.
5
+ * Escapes: backslash, backtick, dollar sign, double quote.
6
+ */
7
+ function shellEscapeDoubleQuoted(s) {
8
+ return String(s).replace(/[\\`"$]/g, '\\$&');
9
+ }
10
+
11
+ /**
12
+ * Convert human-readable gate descriptions to shell commands.
13
+ * e.g. Verify src/skills/pre-start-context.md contains "discovers any project"
14
+ * → grep -qi "discovers any project" "src/skills/pre-start-context.md"
15
+ *
16
+ * All interpolated values are shell-escaped to prevent command injection.
17
+ */
18
+ function gateToShell(cmd) {
19
+ const verify = cmd.match(/^Verify\s+(\S+)\s+contains\s+["']([^"']+)["']$/i);
20
+ if (verify) {
21
+ const needle = shellEscapeDoubleQuoted(verify[2]);
22
+ const file = shellEscapeDoubleQuoted(verify[1]);
23
+ return `grep -qi "${needle}" "${file}"`;
24
+ }
25
+ return cmd;
26
+ }
27
+
28
+ module.exports = { gateToShell, shellEscapeDoubleQuoted };
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Governance parser v2 — backward-compatible with v1.
5
+ *
6
+ * New optional annotations:
7
+ * ### Section (path: dir/) — path-scoped gates
8
+ * ### Section (if: file) — conditional section
9
+ * - command # [OPTIONAL]
10
+ * ## Gates (inherit: root) — inheritance marker
11
+ */
12
+
13
+ // Defensive cap: governance files should be well under this size.
14
+ // Protects against ReDoS on catastrophic-backtracking-prone regex.
15
+ const MAX_CONTENT_SIZE = 256 * 1024; // 256 KB
16
+
17
+ /**
18
+ * Extract a markdown section body by heading name.
19
+ * Starts after the first line matching `## <name>` (with optional trailing text),
20
+ * ends at the next `## ` heading or EOF. Returns the body string, or null if not found.
21
+ *
22
+ * Implemented via line-by-line scan to avoid regex backtracking on large inputs.
23
+ */
24
+ function extractSection(content, name) {
25
+ const lines = content.split('\n');
26
+ const headingPrefix = `## ${name}`;
27
+ let start = -1;
28
+
29
+ for (let i = 0; i < lines.length; i++) {
30
+ const line = lines[i];
31
+ if (line === headingPrefix || line.startsWith(headingPrefix + ' ') || line.startsWith(headingPrefix + '\t')) {
32
+ start = i + 1;
33
+ break;
34
+ }
35
+ }
36
+
37
+ if (start === -1) return null;
38
+
39
+ let end = lines.length;
40
+ for (let i = start; i < lines.length; i++) {
41
+ // Next top-level section (## but not ###)
42
+ if (/^## [^#]/.test(lines[i])) {
43
+ end = i;
44
+ break;
45
+ }
46
+ }
47
+
48
+ return lines.slice(start, end).join('\n');
49
+ }
50
+
51
+ function parseGovernance(content) {
52
+ const result = {
53
+ name: '',
54
+ description: '',
55
+ gates: {},
56
+ runtimes: [],
57
+ inherit: null,
58
+ warnings: [],
59
+ };
60
+
61
+ if (typeof content !== 'string') {
62
+ result.warnings.push('Invalid content type (expected string)');
63
+ return result;
64
+ }
65
+ if (content.length > MAX_CONTENT_SIZE) {
66
+ result.warnings.push(`governance.md exceeds ${MAX_CONTENT_SIZE} bytes — truncating`);
67
+ content = content.slice(0, MAX_CONTENT_SIZE);
68
+ }
69
+
70
+ const nameMatch = content.match(/- Project:\s*(.+)/);
71
+ if (nameMatch) result.name = nameMatch[1].trim();
72
+
73
+ const descMatch = content.match(/- Description:\s*(.+)/);
74
+ if (descMatch) result.description = descMatch[1].trim();
75
+
76
+ // Check for inheritance marker: ## Gates (inherit: root)
77
+ const inheritMatch = content.match(/## Gates[^\n]*\(inherit:\s*(\w+)\)/);
78
+ if (inheritMatch) result.inherit = inheritMatch[1].trim();
79
+
80
+ // Extract the Gates section (ends at next ## heading or EOF).
81
+ // Splitting manually avoids potential catastrophic backtracking on large inputs.
82
+ const gatesBody = extractSection(content, 'Gates');
83
+ if (gatesBody) {
84
+ let section = 'default';
85
+ let sectionMeta = { path: null, condition: null };
86
+
87
+ for (const line of gatesBody.split('\n')) {
88
+ // Match ### Section or ### Section (path: dir/) or ### Section (if: file)
89
+ const sub = line.match(/^### (.+?)(?:\s*\((?:(path|if):\s*(.+?))\))?\s*$/);
90
+ if (sub) {
91
+ section = sub[1].trim().toLowerCase();
92
+ sectionMeta = { path: null, condition: null };
93
+ if (sub[2] === 'path') sectionMeta.path = sub[3].trim();
94
+ if (sub[2] === 'if') sectionMeta.condition = sub[3].trim();
95
+ result.gates[section] = {
96
+ commands: [],
97
+ path: sectionMeta.path,
98
+ condition: sectionMeta.condition,
99
+ };
100
+ } else if (line.match(/^\s*- [^[\s]/) && line.trim() !== '-') {
101
+ let cmd = line.replace(/^\s*- /, '').trim();
102
+ let classification = 'MANDATORY';
103
+
104
+ // Check for # [OPTIONAL] or # [MANDATORY] suffix
105
+ const classMatch = cmd.match(/\s*#\s*\[(MANDATORY|OPTIONAL|ADVISORY)\]\s*$/);
106
+ if (classMatch) {
107
+ classification = classMatch[1];
108
+ cmd = cmd.replace(/\s*#\s*\[(?:MANDATORY|OPTIONAL|ADVISORY)\]\s*$/, '').trim();
109
+ }
110
+
111
+ if (cmd) {
112
+ if (!result.gates[section]) {
113
+ result.gates[section] = { commands: [], path: null, condition: null };
114
+ }
115
+ result.gates[section].commands.push({ cmd, classification });
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ // Warn if no gates were found (helps users catch structural mistakes)
122
+ if (Object.keys(result.gates).length === 0) {
123
+ result.warnings.push('No gates found in governance.md. Expected: "## Gates" section with "- command" entries.');
124
+ }
125
+
126
+ // Detect runtimes from gate commands
127
+ const allCmds = Object.values(result.gates)
128
+ .flatMap(g => (g.commands || []).map(c => c.cmd))
129
+ .join(' ');
130
+ if (/\b(node|npm|npx|eslint|prettier|biome|vitest|jest|next)\b/.test(allCmds)) result.runtimes.push('node');
131
+ if (/\b(cargo|rustc|clippy|rustfmt)\b/.test(allCmds)) result.runtimes.push('rust');
132
+ if (/\b(python|pip|pytest|ruff|mypy|django)\b/.test(allCmds)) result.runtimes.push('python');
133
+ if (/\b(java|gradle|gradlew|maven|mvn)\b/.test(allCmds)) result.runtimes.push('java');
134
+ if (/\bgo (build|test|vet|lint)\b/.test(allCmds)) result.runtimes.push('go');
135
+ if (/\bdocker\b/.test(allCmds)) result.runtimes.push('docker');
136
+
137
+ return result;
138
+ }
139
+
140
+ /**
141
+ * Flatten v2 gates to v1 format for backward compat with compile targets.
142
+ * Returns { sectionName: ['cmd1', 'cmd2'] } — classification and metadata are lost.
143
+ * Use flattenGatesRich() when you need annotations.
144
+ */
145
+ function flattenGates(gates) {
146
+ const flat = {};
147
+ if (!gates || typeof gates !== 'object') return flat;
148
+ for (const [section, data] of Object.entries(gates)) {
149
+ if (!data || typeof data !== 'object') continue;
150
+ const cmds = Array.isArray(data.commands) ? data.commands : [];
151
+ flat[section] = cmds
152
+ .filter(c => c && typeof c.cmd === 'string' && c.cmd.trim())
153
+ .map(c => c.cmd);
154
+ }
155
+ return flat;
156
+ }
157
+
158
+ /**
159
+ * Flatten v2 gates preserving metadata.
160
+ * Returns an array of { section, cmd, classification, path, condition } in order.
161
+ */
162
+ function flattenGatesRich(gates) {
163
+ const out = [];
164
+ if (!gates || typeof gates !== 'object') return out;
165
+ for (const [section, data] of Object.entries(gates)) {
166
+ if (!data || typeof data !== 'object') continue;
167
+ const cmds = Array.isArray(data.commands) ? data.commands : [];
168
+ for (const c of cmds) {
169
+ if (!c || typeof c.cmd !== 'string' || !c.cmd.trim()) continue;
170
+ out.push({
171
+ section,
172
+ cmd: c.cmd,
173
+ classification: c.classification || 'MANDATORY',
174
+ path: data.path || null,
175
+ condition: data.condition || null,
176
+ });
177
+ }
178
+ }
179
+ return out;
180
+ }
181
+
182
+ module.exports = { parseGovernance, flattenGates, flattenGatesRich, extractSection };