@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,94 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { detectWorkspace } = require('../workspace/detect');
6
+ const { enumerateMembers } = require('../workspace/enumerate');
7
+ const { loadGovernanceHierarchy } = require('../workspace/governance');
8
+
9
+ /**
10
+ * crag workspace — inspect the detected workspace.
11
+ * Prints workspace type, root, all members with their stacks and governance status.
12
+ */
13
+ function workspace(args) {
14
+ const json = args.includes('--json');
15
+ const cwd = process.cwd();
16
+
17
+ const ws = detectWorkspace(cwd);
18
+
19
+ if (ws.type === 'none') {
20
+ if (json) {
21
+ console.log(JSON.stringify({ type: 'none', root: ws.root, members: [] }, null, 2));
22
+ return;
23
+ }
24
+ console.log(`\n No workspace detected.`);
25
+ console.log(` Root: ${ws.root}`);
26
+ console.log(` crag works fine without a workspace — it just won't enumerate members.\n`);
27
+ return;
28
+ }
29
+
30
+ const members = enumerateMembers(ws);
31
+ const hierarchy = loadGovernanceHierarchy(ws, members);
32
+
33
+ if (json) {
34
+ const output = {
35
+ type: ws.type,
36
+ root: ws.root,
37
+ configFile: ws.configFile,
38
+ memberCount: members.length,
39
+ warnings: ws.warnings || [],
40
+ members: members.map(m => ({
41
+ name: m.name,
42
+ path: m.relativePath,
43
+ stack: m.stack,
44
+ hasGovernance: m.hasGovernance,
45
+ hasGit: m.hasGit,
46
+ inheritsFromRoot: hierarchy.members[m.name]?.inherit === 'root',
47
+ })),
48
+ rootGovernance: hierarchy.root ? {
49
+ name: hierarchy.root.name,
50
+ gatesCount: Object.keys(hierarchy.root.gates).length,
51
+ runtimes: hierarchy.root.runtimes,
52
+ } : null,
53
+ };
54
+ console.log(JSON.stringify(output, null, 2));
55
+ return;
56
+ }
57
+
58
+ console.log(`\n Workspace: \x1b[36m${ws.type}\x1b[0m`);
59
+ console.log(` Root: ${ws.root}`);
60
+ if (ws.configFile) console.log(` Config: ${ws.configFile}`);
61
+ console.log(` Members: ${members.length}`);
62
+
63
+ if (ws.warnings && ws.warnings.length > 0) {
64
+ console.log(` \x1b[33mWarnings:\x1b[0m`);
65
+ for (const w of ws.warnings) console.log(` ! ${w}`);
66
+ }
67
+
68
+ if (hierarchy.root) {
69
+ const gates = Object.keys(hierarchy.root.gates).length;
70
+ console.log(` Root governance: ${gates} gate section(s), runtimes: ${hierarchy.root.runtimes.join(', ') || 'none'}`);
71
+ } else {
72
+ console.log(` Root governance: \x1b[90m(none)\x1b[0m`);
73
+ }
74
+
75
+ if (members.length === 0) {
76
+ console.log(`\n (No members found — check workspace config.)\n`);
77
+ return;
78
+ }
79
+
80
+ console.log(`\n Members:`);
81
+ for (const m of members) {
82
+ const govIcon = m.hasGovernance ? '\x1b[32m✓\x1b[0m' : '\x1b[90m○\x1b[0m';
83
+ const gitIcon = m.hasGit ? ' \x1b[33m[git]\x1b[0m' : '';
84
+ const stack = m.stack.length > 0 ? `[${m.stack.join(', ')}]` : '[empty]';
85
+ const inherit = hierarchy.members[m.name]?.inherit === 'root' ? ' \x1b[36m(inherits)\x1b[0m' : '';
86
+ console.log(` ${govIcon} ${m.name.padEnd(30)} ${stack}${gitIcon}${inherit}`);
87
+ console.log(` ${m.relativePath}`);
88
+ }
89
+
90
+ console.log('');
91
+ console.log(` Legend: \x1b[32m✓\x1b[0m has governance \x1b[90m○\x1b[0m no governance \x1b[33m[git]\x1b[0m independent repo \x1b[36m(inherits)\x1b[0m merges with root\n`);
92
+ }
93
+
94
+ module.exports = { workspace };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { flattenGates } = require('../governance/parse');
6
+ const { atomicWrite } = require('./atomic-write');
7
+
8
+ function generateAgentsMd(cwd, parsed) {
9
+ const flat = flattenGates(parsed.gates);
10
+
11
+ let gatesSection = '';
12
+ for (const [section, cmds] of Object.entries(flat)) {
13
+ gatesSection += `### ${section.charAt(0).toUpperCase() + section.slice(1)}\n`;
14
+ cmds.forEach((cmd, i) => {
15
+ gatesSection += `${i + 1}. \`${cmd}\`\n`;
16
+ });
17
+ gatesSection += '\n';
18
+ }
19
+
20
+ // Extract security section from governance content if available
21
+ const securitySection = parsed.security || 'No hardcoded secrets or API keys in source.';
22
+
23
+ const content = [
24
+ '# AGENTS.md',
25
+ '',
26
+ `> Generated from governance.md by crag. Regenerate: \`crag compile --target agents-md\``,
27
+ '',
28
+ `## Project: ${parsed.name || 'Unnamed'}`,
29
+ '',
30
+ parsed.description ? `${parsed.description}\n` : '',
31
+ '## Quality Gates',
32
+ '',
33
+ 'All changes must pass these checks before commit:',
34
+ '',
35
+ gatesSection.trim(),
36
+ '',
37
+ '## Coding Standards',
38
+ '',
39
+ `- Runtimes: ${parsed.runtimes.join(', ') || 'auto-detected'}`,
40
+ '- Follow conventional commits (feat:, fix:, docs:, etc.)',
41
+ '- No hardcoded secrets — grep for sk_live, AKIA, password= before commit',
42
+ '',
43
+ '## Architecture',
44
+ '',
45
+ 'Run `/pre-start-context` at the start of every session to discover the project stack, load governance rules, and prepare for work.',
46
+ '',
47
+ '## Validation',
48
+ '',
49
+ 'Run `/post-start-validation` after completing any task to validate changes, run gates, capture knowledge, and commit.',
50
+ '',
51
+ ].join('\n');
52
+
53
+ const outPath = path.join(cwd, 'AGENTS.md');
54
+ atomicWrite(outPath, content);
55
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
56
+ }
57
+
58
+ module.exports = { generateAgentsMd };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Write `content` to `filePath` atomically:
8
+ * 1. Ensure parent directory exists
9
+ * 2. Write to a sibling tempfile
10
+ * 3. Rename tempfile over destination
11
+ *
12
+ * If any step fails, the tempfile is cleaned up and the original destination
13
+ * remains untouched. Prevents partial-write state if the process is killed
14
+ * mid-write or the filesystem runs out of space.
15
+ */
16
+ function atomicWrite(filePath, content) {
17
+ const dir = path.dirname(filePath);
18
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
19
+
20
+ const tmp = filePath + '.tmp.' + process.pid + '.' + Date.now();
21
+
22
+ try {
23
+ fs.writeFileSync(tmp, content);
24
+ fs.renameSync(tmp, filePath);
25
+ } catch (err) {
26
+ // Best-effort cleanup
27
+ try { if (fs.existsSync(tmp)) fs.unlinkSync(tmp); } catch {}
28
+ throw err;
29
+ }
30
+ }
31
+
32
+ module.exports = { atomicWrite };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { flattenGatesRich } = require('../governance/parse');
5
+ const { atomicWrite } = require('./atomic-write');
6
+
7
+ /**
8
+ * Compile governance.md to Cline rules.
9
+ * Output: .clinerules
10
+ *
11
+ * Cline is a VS Code extension that runs agentic coding loops locally.
12
+ * It reads `.clinerules` at the workspace root automatically.
13
+ *
14
+ * Reference:
15
+ * https://docs.cline.bot/features/cline-rules
16
+ */
17
+ function generateCline(cwd, parsed) {
18
+ const gates = flattenGatesRich(parsed.gates);
19
+
20
+ const gatesList = gates.length === 0
21
+ ? '- (none defined)'
22
+ : gates
23
+ .map((g) => {
24
+ const tags = [];
25
+ if (g.classification !== 'MANDATORY') tags.push(g.classification);
26
+ if (g.path) tags.push(`path=${g.path}`);
27
+ if (g.condition) tags.push(`if=${g.condition}`);
28
+ const tagStr = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
29
+ return `- ${g.cmd}${tagStr}`;
30
+ })
31
+ .join('\n');
32
+
33
+ const content = `# Cline Rules — ${parsed.name || 'project'}
34
+
35
+ Generated from governance.md by crag. Regenerate with: \`crag compile --target cline\`
36
+
37
+ ## About this project
38
+
39
+ ${parsed.description || '(No description)'}
40
+
41
+ Runtimes: ${parsed.runtimes.join(', ') || 'auto-detected'}
42
+
43
+ ## Mandatory behavior
44
+
45
+ 1. Read this file at the start of every session. Read \`governance.md\` for full context.
46
+ 2. Run all mandatory quality gates before proposing a commit.
47
+ 3. If a gate fails, attempt an automatic fix (lint/format) with bounded retry (max 2 attempts). If it still fails, escalate to the user.
48
+ 4. Never modify files outside this repository.
49
+ 5. Never run destructive system commands (rm -rf /, DROP TABLE, force-push to main, curl|bash).
50
+ 6. Use conventional commits.
51
+
52
+ ## Quality gates
53
+
54
+ Run these in order, stop on first MANDATORY failure:
55
+
56
+ ${gatesList}
57
+
58
+ ## Security
59
+
60
+ - Never commit hardcoded secrets (grep for sk_live, sk_test, AKIA, password=)
61
+ - Validate all user input at system boundaries
62
+ - Use parameterized queries for database access
63
+
64
+ ## Workflow
65
+
66
+ For every task:
67
+ 1. Read the governance.md file first
68
+ 2. Understand which files need to change
69
+ 3. Make minimal, focused changes
70
+ 4. Run all mandatory gates
71
+ 5. Commit with a conventional commit message
72
+
73
+ ## Tool context
74
+
75
+ This project uses **crag** — the bedrock layer for AI coding agents. https://www.npmjs.com/package/@whitehatd/crag
76
+ `;
77
+
78
+ const outPath = path.join(cwd, '.clinerules');
79
+ atomicWrite(outPath, content);
80
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
81
+ }
82
+
83
+ module.exports = { generateCline };
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { flattenGatesRich } = require('../governance/parse');
5
+ const { atomicWrite } = require('./atomic-write');
6
+
7
+ /**
8
+ * Compile governance.md to Sourcegraph Cody instructions.
9
+ * Output: .sourcegraph/cody-instructions.md
10
+ *
11
+ * Cody is Sourcegraph's AI coding assistant. It reads project-level
12
+ * instructions from `.sourcegraph/cody-instructions.md` to provide
13
+ * repo-wide context to its chat, edit, and autocomplete features.
14
+ *
15
+ * Reference:
16
+ * https://sourcegraph.com/docs/cody/capabilities/custom-commands
17
+ */
18
+ function generateCody(cwd, parsed) {
19
+ const gates = flattenGatesRich(parsed.gates);
20
+
21
+ const gatesList = gates.length === 0
22
+ ? '- _(none defined — add gates to governance.md)_'
23
+ : gates
24
+ .map((g) => {
25
+ const annotations = [];
26
+ if (g.classification !== 'MANDATORY') annotations.push(g.classification);
27
+ if (g.path) annotations.push(`scope: ${g.path}`);
28
+ if (g.condition) annotations.push(`if: ${g.condition}`);
29
+ const tagStr = annotations.length > 0 ? ` — ${annotations.join(' · ')}` : '';
30
+ return `- \`${g.cmd}\`${tagStr}`;
31
+ })
32
+ .join('\n');
33
+
34
+ const content = `# Cody Instructions — ${parsed.name || 'project'}
35
+
36
+ > Generated from governance.md by crag. Regenerate: \`crag compile --target cody\`
37
+
38
+ ## About
39
+
40
+ ${parsed.description || '(No description)'}
41
+
42
+ **Runtimes detected:** ${parsed.runtimes.join(', ') || 'polyglot'}
43
+
44
+ ## How Cody Should Behave on This Project
45
+
46
+ ### Code Generation
47
+
48
+ 1. **Run governance gates before suggesting commits.** The gates below define the quality bar.
49
+ 2. **Respect classifications:** MANDATORY (default) blocks on failure; OPTIONAL warns; ADVISORY is informational only.
50
+ 3. **Respect scopes:** Path-scoped gates run from that directory. Conditional gates skip when their file does not exist.
51
+ 4. **No secrets.** Never generate code containing \`sk_live\`, \`sk_test\`, \`AKIA\`, or plaintext credentials.
52
+ 5. **Minimal diffs.** Prefer editing existing code over creating new files. Do not refactor unrelated areas.
53
+
54
+ ### Quality Gates
55
+
56
+ ${gatesList}
57
+
58
+ ### Commit Style
59
+
60
+ Use conventional commits: \`feat(scope): description\`, \`fix(scope): description\`, \`docs: description\`, etc.
61
+
62
+ ### Boundaries
63
+
64
+ - All file operations must stay within this repository.
65
+ - No destructive shell commands (rm -rf above repo root, DROP TABLE without confirmation, force-push to main).
66
+ - No new dependencies without an explicit reason.
67
+
68
+ ## Authoritative Source
69
+
70
+ When these instructions seem to conflict with something in the repo, **\`.claude/governance.md\` is the source of truth**. This file is a compiled view.
71
+
72
+ ---
73
+
74
+ **Tool:** crag — https://www.npmjs.com/package/@whitehatd/crag
75
+ `;
76
+
77
+ const outPath = path.join(cwd, '.sourcegraph', 'cody-instructions.md');
78
+ atomicWrite(outPath, content);
79
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
80
+ }
81
+
82
+ module.exports = { generateCody };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { flattenGatesRich } = require('../governance/parse');
5
+ const { atomicWrite } = require('./atomic-write');
6
+
7
+ /**
8
+ * Compile governance.md to Continue.dev rules.
9
+ * Output: .continuerules (a markdown file) + companion block for config.yaml
10
+ *
11
+ * Continue is an open-source AI code assistant that supports custom rules
12
+ * at the project level. It reads `.continuerules` and project-level
13
+ * `config.yaml` fragments.
14
+ *
15
+ * Reference:
16
+ * https://docs.continue.dev/customize/deep-dives/rules
17
+ */
18
+ function generateContinue(cwd, parsed) {
19
+ const gates = flattenGatesRich(parsed.gates);
20
+
21
+ const gatesList = gates.length === 0
22
+ ? ' (none defined)'
23
+ : gates
24
+ .map((g) => {
25
+ const tags = [];
26
+ if (g.classification !== 'MANDATORY') tags.push(g.classification);
27
+ if (g.path) tags.push(`path=${g.path}`);
28
+ return ` - ${g.cmd}${tags.length > 0 ? ` (${tags.join(', ')})` : ''}`;
29
+ })
30
+ .join('\n');
31
+
32
+ const content = `# Continue Rules — ${parsed.name || 'project'}
33
+
34
+ > Generated from governance.md by crag. Regenerate: \`crag compile --target continue\`
35
+
36
+ ${parsed.description || ''}
37
+
38
+ ## Project Context
39
+
40
+ - **Runtimes:** ${parsed.runtimes.join(', ') || 'polyglot'}
41
+ - **Governance source:** \`.claude/governance.md\` (single source of truth)
42
+
43
+ ## Coding Rules
44
+
45
+ Always follow these when generating or modifying code:
46
+
47
+ 1. **Run gates before committing.** Every change must pass the mandatory gates below.
48
+ 2. **Classifications matter:**
49
+ - \`MANDATORY\` — must pass (default)
50
+ - \`OPTIONAL\` — should pass, warn on failure
51
+ - \`ADVISORY\` — informational only
52
+ 3. **Path-scoped gates** run from their declared directory.
53
+ 4. **Conditional gates** only run when their referenced file exists.
54
+ 5. **No secrets.** Reject any code containing \`sk_live\`, \`sk_test\`, \`AKIA\`, or plaintext passwords.
55
+ 6. **Conventional commits.** Format: \`<type>(<scope>): <description>\`
56
+
57
+ ## Quality Gates
58
+
59
+ ${gatesList}
60
+
61
+ ## Boundaries
62
+
63
+ - All file operations stay within this repository
64
+ - No destructive shell commands
65
+ - No new dependencies without justification
66
+ - Prefer editing existing files over creating new ones
67
+
68
+ ## Powered by crag
69
+
70
+ This rule file is auto-generated from a single \`governance.md\` via **crag** (https://www.npmjs.com/package/@whitehatd/crag) — the bedrock layer for AI coding agents. To update these rules, edit governance.md and re-run \`crag compile --target continue\`.
71
+ `;
72
+
73
+ const outPath = path.join(cwd, '.continuerules');
74
+ atomicWrite(outPath, content);
75
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
76
+ }
77
+
78
+ module.exports = { generateContinue };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { flattenGatesRich } = require('../governance/parse');
5
+ const { atomicWrite } = require('./atomic-write');
6
+
7
+ /**
8
+ * Compile governance.md to GitHub Copilot Workspace instructions.
9
+ * Output: .github/copilot-instructions.md
10
+ *
11
+ * Copilot Workspace reads this file automatically. It's supported by
12
+ * Copilot in VS Code, JetBrains IDEs, Visual Studio, and the Copilot
13
+ * Workspace web experience.
14
+ *
15
+ * Reference:
16
+ * https://docs.github.com/en/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot
17
+ */
18
+ function generateCopilot(cwd, parsed) {
19
+ const gates = flattenGatesRich(parsed.gates);
20
+
21
+ const gatesBlock = gates.length === 0
22
+ ? '_No quality gates defined — add them to governance.md._'
23
+ : gates
24
+ .map((g) => {
25
+ const prefix = g.classification !== 'MANDATORY' ? ` (${g.classification.toLowerCase()})` : '';
26
+ const scope = g.path ? ` in \`${g.path}\`` : '';
27
+ return `- **${g.section}**${scope}${prefix}: \`${g.cmd}\``;
28
+ })
29
+ .join('\n');
30
+
31
+ const runtimesBlock = parsed.runtimes.length > 0
32
+ ? parsed.runtimes.join(', ')
33
+ : 'polyglot (detected from files at runtime)';
34
+
35
+ const content = `# Copilot Instructions — ${parsed.name || 'project'}
36
+
37
+ > Generated from governance.md by crag. Regenerate: \`crag compile --target copilot\`
38
+
39
+ ${parsed.description || ''}
40
+
41
+ ## Runtimes
42
+
43
+ ${runtimesBlock}
44
+
45
+ ## Quality Gates
46
+
47
+ When you propose changes, the following checks must pass before commit:
48
+
49
+ ${gatesBlock}
50
+
51
+ ## Expectations for AI-Assisted Code
52
+
53
+ 1. **Run gates before suggesting a commit.** If you cannot run them (no shell access), explicitly remind the human to run them.
54
+ 2. **Respect classifications.** \`MANDATORY\` gates must pass. \`OPTIONAL\` gates should pass but may be overridden with a note. \`ADVISORY\` gates are informational only.
55
+ 3. **Respect workspace paths.** When a gate is scoped to a subdirectory, run it from that directory.
56
+ 4. **No hardcoded secrets.** Never commit values matching \`sk_live\`, \`sk_test\`, \`AKIA\`, or \`password = "…"\`.
57
+ 5. **Conventional commits** for all changes.
58
+ 6. **Conservative changes.** Do not rewrite unrelated files. Do not add new dependencies without explaining why.
59
+
60
+ ## Tool Context
61
+
62
+ This project uses **crag** (https://www.npmjs.com/package/@whitehatd/crag) as its AI-agent governance layer. The \`governance.md\` file is the authoritative source. If you have shell access, run \`crag check\` to verify the infrastructure and \`crag diff\` to detect drift.
63
+ `;
64
+
65
+ const outPath = path.join(cwd, '.github', 'copilot-instructions.md');
66
+ atomicWrite(outPath, content);
67
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
68
+ }
69
+
70
+ module.exports = { generateCopilot };
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { flattenGates } = require('../governance/parse');
6
+ const { atomicWrite } = require('./atomic-write');
7
+
8
+ function generateCursorRules(cwd, parsed) {
9
+ const dir = path.join(cwd, '.cursor', 'rules');
10
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
11
+
12
+ const flat = flattenGates(parsed.gates);
13
+
14
+ let gatesList = '';
15
+ for (const [section, cmds] of Object.entries(flat)) {
16
+ gatesList += `\n### ${section.charAt(0).toUpperCase() + section.slice(1)}\n`;
17
+ for (const cmd of cmds) {
18
+ gatesList += `- \`${cmd}\`\n`;
19
+ }
20
+ }
21
+
22
+ // Determine globs from runtimes
23
+ const globs = [];
24
+ if (parsed.runtimes.includes('node')) globs.push('"**/*.ts"', '"**/*.tsx"', '"**/*.js"', '"**/*.jsx"');
25
+ if (parsed.runtimes.includes('rust')) globs.push('"**/*.rs"');
26
+ if (parsed.runtimes.includes('python')) globs.push('"**/*.py"');
27
+ if (parsed.runtimes.includes('java')) globs.push('"**/*.java"', '"**/*.kt"');
28
+ if (parsed.runtimes.includes('go')) globs.push('"**/*.go"');
29
+ if (globs.length === 0) globs.push('"**/*"');
30
+
31
+ const content = [
32
+ '---',
33
+ `description: Governance rules for ${parsed.name || 'this project'} — quality gates, security, conventions`,
34
+ 'globs:',
35
+ ...globs.map(g => ` - ${g}`),
36
+ 'alwaysApply: true',
37
+ '---',
38
+ '',
39
+ `# Governance — ${parsed.name || 'Project'}`,
40
+ '',
41
+ `> Generated from governance.md by crag. Regenerate: \`crag compile --target cursor\``,
42
+ '',
43
+ '## Quality Gates',
44
+ '',
45
+ 'Run these checks in order before committing:',
46
+ gatesList.trim(),
47
+ '',
48
+ '## Security',
49
+ '',
50
+ '- No hardcoded secrets — grep for sk_live, AKIA, password= before commit',
51
+ '- Validate all user input at system boundaries',
52
+ '- Use parameterized queries for database access',
53
+ '',
54
+ '## Conventions',
55
+ '',
56
+ '- Follow conventional commits (feat:, fix:, docs:, etc.)',
57
+ `- Runtimes: ${parsed.runtimes.join(', ') || 'auto-detected'}`,
58
+ '',
59
+ ].join('\n');
60
+
61
+ const outPath = path.join(dir, 'governance.mdc');
62
+ atomicWrite(outPath, content);
63
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
64
+ }
65
+
66
+ module.exports = { generateCursorRules };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { flattenGates } = require('../governance/parse');
6
+ const { atomicWrite } = require('./atomic-write');
7
+
8
+ function generateGeminiMd(cwd, parsed) {
9
+ const flat = flattenGates(parsed.gates);
10
+
11
+ let gatesList = '';
12
+ let i = 1;
13
+ for (const [section, cmds] of Object.entries(flat)) {
14
+ for (const cmd of cmds) {
15
+ gatesList += `${i}. [${section}] \`${cmd}\`\n`;
16
+ i++;
17
+ }
18
+ }
19
+
20
+ const content = [
21
+ '# GEMINI.md',
22
+ '',
23
+ `> Generated from governance.md by crag. Regenerate: \`crag compile --target gemini\``,
24
+ '',
25
+ '## Project Context',
26
+ '',
27
+ `- **Name:** ${parsed.name || 'Unnamed'}`,
28
+ parsed.description ? `- **Description:** ${parsed.description}` : '',
29
+ `- **Runtimes:** ${parsed.runtimes.join(', ') || 'auto-detected'}`,
30
+ '',
31
+ '## Rules',
32
+ '',
33
+ '### Quality Gates',
34
+ '',
35
+ 'Run these checks in order before committing any changes:',
36
+ '',
37
+ gatesList.trim(),
38
+ '',
39
+ '### Security',
40
+ '',
41
+ '- Never hardcode secrets, API keys, or credentials in source code',
42
+ '- Grep for sk_live, AKIA, password= before every commit',
43
+ '- Validate all user input at system boundaries',
44
+ '',
45
+ '### Workflow',
46
+ '',
47
+ '- Use conventional commits (feat:, fix:, docs:, chore:, etc.)',
48
+ '- Run quality gates before committing',
49
+ '- Review security implications of all changes',
50
+ '',
51
+ ].join('\n');
52
+
53
+ const outPath = path.join(cwd, 'GEMINI.md');
54
+ atomicWrite(outPath, content);
55
+ console.log(` \x1b[32m✓\x1b[0m ${path.relative(cwd, outPath)}`);
56
+ }
57
+
58
+ module.exports = { generateGeminiMd };