@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.
- package/README.md +838 -15
- package/bin/crag.js +7 -0
- package/package.json +18 -4
- package/src/cli.js +102 -0
- package/src/commands/analyze.js +513 -0
- package/src/commands/check.js +55 -0
- package/src/commands/compile.js +104 -0
- package/src/commands/diff.js +289 -0
- package/src/commands/init.js +112 -0
- package/src/commands/upgrade.js +64 -0
- package/src/commands/workspace.js +94 -0
- package/src/compile/agents-md.js +58 -0
- package/src/compile/atomic-write.js +32 -0
- package/src/compile/cline.js +83 -0
- package/src/compile/cody.js +82 -0
- package/src/compile/continue.js +78 -0
- package/src/compile/copilot.js +70 -0
- package/src/compile/cursor-rules.js +66 -0
- package/src/compile/gemini-md.js +58 -0
- package/src/compile/github-actions.js +165 -0
- package/src/compile/husky.js +66 -0
- package/src/compile/pre-commit.js +50 -0
- package/src/compile/windsurf.js +76 -0
- package/src/compile/zed.js +86 -0
- package/src/crag-agent.md +254 -0
- package/src/governance/gate-to-shell.js +28 -0
- package/src/governance/parse.js +182 -0
- package/src/skills/post-start-validation.md +297 -0
- package/src/skills/pre-start-context.md +506 -0
- package/src/update/integrity.js +131 -0
- package/src/update/skill-sync.js +116 -0
- package/src/update/version-check.js +156 -0
- package/src/workspace/detect.js +190 -0
- package/src/workspace/enumerate.js +270 -0
- package/src/workspace/governance.js +119 -0
- package/cli.js +0 -15
|
@@ -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 };
|