brainclaw 0.19.14 → 0.21.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/README.md +27 -11
- package/dist/cli.js +11 -0
- package/dist/commands/context.js +3 -1
- package/dist/commands/export.js +44 -0
- package/dist/commands/init.js +7 -6
- package/dist/commands/mcp.js +86 -5
- package/dist/commands/setup.js +1 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/core/agent-capability.js +196 -0
- package/dist/core/agent-context.js +24 -6
- package/dist/core/agent-integrations.js +3 -0
- package/dist/core/ai-agent-detection.js +34 -19
- package/dist/core/bootstrap.js +177 -0
- package/dist/core/context.js +47 -24
- package/dist/core/instruction-templates.js +308 -0
- package/dist/core/schema.js +10 -0
- package/dist/core/setup-flow.js +191 -0
- package/dist/core/setup-state.js +30 -1
- package/dist/core/store-resolution.js +58 -0
- package/docs/architecture/project-refs.md +305 -0
- package/docs/cli.md +2 -1
- package/docs/integrations/agents.md +102 -150
- package/docs/integrations/openclaw.md +98 -0
- package/docs/integrations/overview.md +73 -45
- package/docs/quickstart.md +43 -147
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent capability profiles — describes what integration surfaces each
|
|
3
|
+
* agent supports so brainclaw can adapt its instruction file content,
|
|
4
|
+
* integration depth, and pressure level accordingly.
|
|
5
|
+
*
|
|
6
|
+
* Three profile tiers drive instruction file templates:
|
|
7
|
+
* A (full) — MCP + hooks + auto-approve → lightweight instructions
|
|
8
|
+
* B (standard) — MCP, no hooks → directive instructions with top traps
|
|
9
|
+
* C (limited) — no MCP → rich static content (plans, traps, decisions)
|
|
10
|
+
*/
|
|
11
|
+
const PROFILES = {
|
|
12
|
+
'claude-code': {
|
|
13
|
+
name: 'claude-code',
|
|
14
|
+
hasMcp: true,
|
|
15
|
+
hasHooks: true,
|
|
16
|
+
hasAutoApprove: true,
|
|
17
|
+
hasSkills: true,
|
|
18
|
+
hasRules: true,
|
|
19
|
+
instructionFile: 'CLAUDE.md',
|
|
20
|
+
sharedInstructionFile: true,
|
|
21
|
+
mcpConfigScope: 'both',
|
|
22
|
+
templateTier: 'A',
|
|
23
|
+
},
|
|
24
|
+
cursor: {
|
|
25
|
+
name: 'cursor',
|
|
26
|
+
hasMcp: true,
|
|
27
|
+
hasHooks: false,
|
|
28
|
+
hasAutoApprove: false,
|
|
29
|
+
hasSkills: false,
|
|
30
|
+
hasRules: true,
|
|
31
|
+
instructionFile: '.cursor/rules/brainclaw.md',
|
|
32
|
+
sharedInstructionFile: false,
|
|
33
|
+
mcpConfigScope: 'machine',
|
|
34
|
+
templateTier: 'B',
|
|
35
|
+
},
|
|
36
|
+
windsurf: {
|
|
37
|
+
name: 'windsurf',
|
|
38
|
+
hasMcp: true,
|
|
39
|
+
hasHooks: false,
|
|
40
|
+
hasAutoApprove: false,
|
|
41
|
+
hasSkills: false,
|
|
42
|
+
hasRules: true,
|
|
43
|
+
instructionFile: '.windsurfrules',
|
|
44
|
+
sharedInstructionFile: true,
|
|
45
|
+
mcpConfigScope: 'machine',
|
|
46
|
+
templateTier: 'B',
|
|
47
|
+
},
|
|
48
|
+
cline: {
|
|
49
|
+
name: 'cline',
|
|
50
|
+
hasMcp: true,
|
|
51
|
+
hasHooks: false,
|
|
52
|
+
hasAutoApprove: true,
|
|
53
|
+
hasSkills: false,
|
|
54
|
+
hasRules: true,
|
|
55
|
+
instructionFile: '.clinerules/brainclaw.md',
|
|
56
|
+
sharedInstructionFile: false,
|
|
57
|
+
mcpConfigScope: 'project',
|
|
58
|
+
templateTier: 'B',
|
|
59
|
+
},
|
|
60
|
+
roo: {
|
|
61
|
+
name: 'roo',
|
|
62
|
+
hasMcp: true,
|
|
63
|
+
hasHooks: false,
|
|
64
|
+
hasAutoApprove: true,
|
|
65
|
+
hasSkills: false,
|
|
66
|
+
hasRules: true,
|
|
67
|
+
instructionFile: '.roo/rules/brainclaw.md',
|
|
68
|
+
sharedInstructionFile: false,
|
|
69
|
+
mcpConfigScope: 'project',
|
|
70
|
+
templateTier: 'B',
|
|
71
|
+
},
|
|
72
|
+
continue: {
|
|
73
|
+
name: 'continue',
|
|
74
|
+
hasMcp: true,
|
|
75
|
+
hasHooks: false,
|
|
76
|
+
hasAutoApprove: false,
|
|
77
|
+
hasSkills: false,
|
|
78
|
+
hasRules: true,
|
|
79
|
+
instructionFile: '.continue/rules/brainclaw.md',
|
|
80
|
+
sharedInstructionFile: false,
|
|
81
|
+
mcpConfigScope: 'both',
|
|
82
|
+
templateTier: 'B',
|
|
83
|
+
},
|
|
84
|
+
opencode: {
|
|
85
|
+
name: 'opencode',
|
|
86
|
+
hasMcp: true,
|
|
87
|
+
hasHooks: false,
|
|
88
|
+
hasAutoApprove: false,
|
|
89
|
+
hasSkills: false,
|
|
90
|
+
hasRules: true,
|
|
91
|
+
instructionFile: 'AGENTS.md',
|
|
92
|
+
sharedInstructionFile: true,
|
|
93
|
+
mcpConfigScope: 'project',
|
|
94
|
+
templateTier: 'B',
|
|
95
|
+
},
|
|
96
|
+
codex: {
|
|
97
|
+
name: 'codex',
|
|
98
|
+
hasMcp: true,
|
|
99
|
+
hasHooks: false,
|
|
100
|
+
hasAutoApprove: false,
|
|
101
|
+
hasSkills: false,
|
|
102
|
+
hasRules: true,
|
|
103
|
+
instructionFile: 'AGENTS.md',
|
|
104
|
+
sharedInstructionFile: true,
|
|
105
|
+
mcpConfigScope: 'machine',
|
|
106
|
+
templateTier: 'B',
|
|
107
|
+
},
|
|
108
|
+
antigravity: {
|
|
109
|
+
name: 'antigravity',
|
|
110
|
+
hasMcp: true,
|
|
111
|
+
hasHooks: false,
|
|
112
|
+
hasAutoApprove: false,
|
|
113
|
+
hasSkills: false,
|
|
114
|
+
hasRules: true,
|
|
115
|
+
instructionFile: 'GEMINI.md',
|
|
116
|
+
sharedInstructionFile: true,
|
|
117
|
+
mcpConfigScope: 'machine',
|
|
118
|
+
templateTier: 'B',
|
|
119
|
+
},
|
|
120
|
+
'github-copilot': {
|
|
121
|
+
name: 'github-copilot',
|
|
122
|
+
hasMcp: false,
|
|
123
|
+
hasHooks: false,
|
|
124
|
+
hasAutoApprove: false,
|
|
125
|
+
hasSkills: true,
|
|
126
|
+
hasRules: true,
|
|
127
|
+
instructionFile: '.github/copilot-instructions.md',
|
|
128
|
+
sharedInstructionFile: true,
|
|
129
|
+
mcpConfigScope: 'none',
|
|
130
|
+
templateTier: 'C',
|
|
131
|
+
},
|
|
132
|
+
openclaw: {
|
|
133
|
+
name: 'openclaw',
|
|
134
|
+
hasMcp: false,
|
|
135
|
+
hasHooks: false,
|
|
136
|
+
hasAutoApprove: false,
|
|
137
|
+
hasSkills: true,
|
|
138
|
+
hasRules: false,
|
|
139
|
+
instructionFile: 'skills/openclaw/SKILL.md',
|
|
140
|
+
sharedInstructionFile: false,
|
|
141
|
+
mcpConfigScope: 'none',
|
|
142
|
+
templateTier: 'C',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Get the capability profile for a known agent.
|
|
147
|
+
* Returns undefined for unknown agent names.
|
|
148
|
+
*/
|
|
149
|
+
export function getAgentCapabilityProfile(name) {
|
|
150
|
+
return PROFILES[name];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get all known agent capability profiles.
|
|
154
|
+
*/
|
|
155
|
+
export function getAllAgentCapabilityProfiles() {
|
|
156
|
+
return Object.values(PROFILES);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get all agent names that match a given template tier.
|
|
160
|
+
*/
|
|
161
|
+
export function getAgentsByTier(tier) {
|
|
162
|
+
return Object.values(PROFILES).filter((p) => p.templateTier === tier);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if an agent name is a known brainclaw-supported agent.
|
|
166
|
+
*/
|
|
167
|
+
export function isKnownAgent(name) {
|
|
168
|
+
return name in PROFILES;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Summarize which integration surfaces are available for a given agent.
|
|
172
|
+
* Useful for setup UI to explain what brainclaw will configure.
|
|
173
|
+
*/
|
|
174
|
+
export function describeAgentSurfaces(name) {
|
|
175
|
+
const profile = getAgentCapabilityProfile(name);
|
|
176
|
+
if (!profile)
|
|
177
|
+
return [];
|
|
178
|
+
const surfaces = [];
|
|
179
|
+
if (profile.hasMcp) {
|
|
180
|
+
surfaces.push(`MCP server (${profile.mcpConfigScope})`);
|
|
181
|
+
}
|
|
182
|
+
if (profile.hasRules) {
|
|
183
|
+
surfaces.push(`Instruction file (${profile.instructionFile})`);
|
|
184
|
+
}
|
|
185
|
+
if (profile.hasAutoApprove) {
|
|
186
|
+
surfaces.push('Auto-approve MCP tools');
|
|
187
|
+
}
|
|
188
|
+
if (profile.hasHooks) {
|
|
189
|
+
surfaces.push('Session hooks (pre-prompt + stop)');
|
|
190
|
+
}
|
|
191
|
+
if (profile.hasSkills) {
|
|
192
|
+
surfaces.push('Slash command / skill');
|
|
193
|
+
}
|
|
194
|
+
return surfaces;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=agent-capability.js.map
|
|
@@ -60,12 +60,30 @@ function readAgentsMarkdown(cwd) {
|
|
|
60
60
|
const raw = fs.readFileSync(filepath, 'utf-8');
|
|
61
61
|
const lines = raw.split(/\r?\n/);
|
|
62
62
|
const title = lines.find((line) => line.trim().startsWith('#'))?.replace(/^#+\s*/, '').trim();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// Only extract rules from actionable sections, not from descriptive sections
|
|
64
|
+
// like "why this matters" which contain explanatory bullets, not instructions.
|
|
65
|
+
const SKIP_SECTIONS = /why this matters|what it provides|what brainclaw/i;
|
|
66
|
+
let currentSection = '';
|
|
67
|
+
let skipSection = false;
|
|
68
|
+
const rules = [];
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (trimmed.startsWith('#')) {
|
|
72
|
+
currentSection = trimmed.replace(/^#+\s*/, '');
|
|
73
|
+
skipSection = SKIP_SECTIONS.test(currentSection);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (skipSection)
|
|
77
|
+
continue;
|
|
78
|
+
if (/^([-*]|\d+\.)\s+/.test(trimmed)) {
|
|
79
|
+
const text = trimmed.replace(/^([-*]|\d+\.)\s+/, '').trim();
|
|
80
|
+
if (text) {
|
|
81
|
+
rules.push(text);
|
|
82
|
+
if (rules.length >= MAX_AGENT_RULES)
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
69
87
|
return {
|
|
70
88
|
present: true,
|
|
71
89
|
title,
|
|
@@ -57,6 +57,9 @@ const DEFAULT_SURFACES = {
|
|
|
57
57
|
{ kind: 'instructions', location: 'workspace', path: '.roo/rules/brainclaw.md' },
|
|
58
58
|
{ kind: 'mcp', location: 'workspace', path: '.roo/mcp.json' },
|
|
59
59
|
],
|
|
60
|
+
'openclaw': [
|
|
61
|
+
{ kind: 'skill', location: 'machine', path: '.openclaw/workspace/skills/brainclaw/SKILL.md' },
|
|
62
|
+
],
|
|
60
63
|
};
|
|
61
64
|
function mergeSurfaces(current, next) {
|
|
62
65
|
const merged = [...current];
|
|
@@ -6,18 +6,20 @@ import os from 'node:os';
|
|
|
6
6
|
* environment variables and well-known config paths. Returns the first confident
|
|
7
7
|
* match, or undefined if no agent is detected.
|
|
8
8
|
*
|
|
9
|
-
* Detection order (highest confidence first
|
|
9
|
+
* Detection order (highest confidence first — agents with dedicated env vars
|
|
10
|
+
* are tested before agents detected via passive/ambient env vars):
|
|
10
11
|
* 1. BRAINCLAW_AGENT env var (explicit override)
|
|
11
|
-
* 2.
|
|
12
|
-
* 3.
|
|
13
|
-
* 4.
|
|
14
|
-
* 5.
|
|
15
|
-
* 6.
|
|
12
|
+
* 2. Claude Code (CLAUDE_CODE_VERSION — set by Claude Code itself)
|
|
13
|
+
* 3. Cursor (CURSOR_TRACE_ID — set by Cursor itself)
|
|
14
|
+
* 4. Windsurf (WINDSURF_SESSION_ID — set by Windsurf itself)
|
|
15
|
+
* 5. Cline (CLINE_AGENT — set by Cline itself)
|
|
16
|
+
* 6. GitHub Copilot (GITHUB_COPILOT_PRODUCT — passive VS Code env, tested after active agents)
|
|
16
17
|
* 7. Codex CLI (~/.codex/ directory exists)
|
|
17
18
|
* 8. OpenCode (OPENCODE_* env or ~/.config/opencode/)
|
|
18
19
|
* 9. Antigravity / Gemini CLI (ANTIGRAVITY_* env or ~/.gemini/antigravity/)
|
|
19
20
|
* 10. Continue (CONTINUE_*)
|
|
20
21
|
* 11. Roo Code (ROO_*)
|
|
22
|
+
* 12. OpenClaw (~/.openclaw/ or OPENCLAW_*)
|
|
21
23
|
*/
|
|
22
24
|
export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
23
25
|
// Explicit override
|
|
@@ -29,24 +31,14 @@ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
|
29
31
|
detection_source: 'BRAINCLAW_AGENT env var',
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
env.GITHUB_COPILOT_PRODUCT ||
|
|
35
|
-
(env.VSCODE_GIT_IPC_HANDLE && (env.AGENT_NAME?.toLowerCase().includes('copilot') || env.GH_COPILOT_AGENT))) {
|
|
36
|
-
return {
|
|
37
|
-
name: 'github-copilot',
|
|
38
|
-
kind: 'agent',
|
|
39
|
-
trust_level: 'trusted',
|
|
40
|
-
detection_source: 'GITHUB_COPILOT_* env var',
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// Claude Code (Anthropic's CLI coding agent)
|
|
34
|
+
// Claude Code — tested BEFORE Copilot because both can be present in VS Code.
|
|
35
|
+
// CLAUDE_CODE_VERSION is set by Claude Code itself, not by VS Code passively.
|
|
44
36
|
if (env.CLAUDE_CODE_VERSION || env.ANTHROPIC_AI_PRODUCT === 'claude-code') {
|
|
45
37
|
return {
|
|
46
38
|
name: 'claude-code',
|
|
47
39
|
kind: 'agent',
|
|
48
40
|
trust_level: 'trusted',
|
|
49
|
-
detection_source: 'CLAUDE_CODE_VERSION env var',
|
|
41
|
+
detection_source: env.CLAUDE_CODE_VERSION ? 'CLAUDE_CODE_VERSION env var' : 'ANTHROPIC_AI_PRODUCT env var',
|
|
50
42
|
};
|
|
51
43
|
}
|
|
52
44
|
// Cursor IDE
|
|
@@ -76,6 +68,20 @@ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
|
76
68
|
detection_source: 'CLINE_* env var',
|
|
77
69
|
};
|
|
78
70
|
}
|
|
71
|
+
// GitHub Copilot — tested AFTER agents with dedicated env vars.
|
|
72
|
+
// GITHUB_COPILOT_TOKEN and GITHUB_COPILOT_PRODUCT are set passively by VS Code
|
|
73
|
+
// whenever the Copilot extension is installed, even when another agent is active.
|
|
74
|
+
// We only match if no higher-priority agent was detected above.
|
|
75
|
+
if (env.GITHUB_COPILOT_PRODUCT ||
|
|
76
|
+
(env.GITHUB_COPILOT_TOKEN && !env.CLAUDE_CODE_VERSION && !env.CURSOR_TRACE_ID && !env.WINDSURF_SESSION_ID) ||
|
|
77
|
+
(env.VSCODE_GIT_IPC_HANDLE && (env.AGENT_NAME?.toLowerCase().includes('copilot') || env.GH_COPILOT_AGENT))) {
|
|
78
|
+
return {
|
|
79
|
+
name: 'github-copilot',
|
|
80
|
+
kind: 'agent',
|
|
81
|
+
trust_level: 'trusted',
|
|
82
|
+
detection_source: env.GITHUB_COPILOT_PRODUCT ? 'GITHUB_COPILOT_PRODUCT env var' : 'GITHUB_COPILOT_TOKEN env var',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
79
85
|
// OpenAI Codex CLI (~/.codex/ presence)
|
|
80
86
|
if (fs.existsSync(path.join(homeDir, '.codex'))) {
|
|
81
87
|
return {
|
|
@@ -121,6 +127,15 @@ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
|
|
|
121
127
|
detection_source: 'ROO_* env var',
|
|
122
128
|
};
|
|
123
129
|
}
|
|
130
|
+
// OpenClaw (~/.openclaw/ presence or OPENCLAW_* env)
|
|
131
|
+
if (env.OPENCLAW_SESSION_ID || env.OPENCLAW_AGENT || fs.existsSync(path.join(homeDir, '.openclaw'))) {
|
|
132
|
+
return {
|
|
133
|
+
name: 'openclaw',
|
|
134
|
+
kind: 'agent',
|
|
135
|
+
trust_level: 'trusted',
|
|
136
|
+
detection_source: env.OPENCLAW_SESSION_ID || env.OPENCLAW_AGENT ? 'OPENCLAW_* env var' : '~/.openclaw directory',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
124
139
|
return undefined;
|
|
125
140
|
}
|
|
126
141
|
/**
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -267,6 +267,12 @@ function buildBootstrapArtifacts(input) {
|
|
|
267
267
|
const repoAnalysis = analyzeRepository(input.cwd);
|
|
268
268
|
sourcesScanned.push('repo-analysis');
|
|
269
269
|
seeds.push(...extractRepoAnalysisSeeds(repoAnalysis, input.target));
|
|
270
|
+
// Additional brownfield sources (step 12)
|
|
271
|
+
const additionalSeeds = extractAdditionalBrownfieldSeeds(input.cwd, input.target);
|
|
272
|
+
if (additionalSeeds.seeds.length > 0) {
|
|
273
|
+
sourcesScanned.push(...additionalSeeds.sources);
|
|
274
|
+
seeds.push(...additionalSeeds.seeds);
|
|
275
|
+
}
|
|
270
276
|
const gitProbe = probeGit(input.cwd, input.target);
|
|
271
277
|
if (gitProbe.available) {
|
|
272
278
|
sourcesScanned.push('git');
|
|
@@ -703,6 +709,44 @@ function probeGit(cwd, target) {
|
|
|
703
709
|
}));
|
|
704
710
|
}
|
|
705
711
|
}
|
|
712
|
+
// Step 13: Active branches
|
|
713
|
+
const branchResult = spawnSync('git', ['branch', '--no-merged', 'HEAD', '--format=%(refname:short)'], {
|
|
714
|
+
cwd,
|
|
715
|
+
encoding: 'utf-8',
|
|
716
|
+
timeout: 5000,
|
|
717
|
+
});
|
|
718
|
+
if (branchResult.status === 0) {
|
|
719
|
+
const branches = branchResult.stdout.split(/\r?\n/).map((b) => b.trim()).filter(Boolean).slice(0, 5);
|
|
720
|
+
for (const branch of branches) {
|
|
721
|
+
hotspotSeeds.push(createSeed({
|
|
722
|
+
text: `Active branch: ${branch}`,
|
|
723
|
+
seedKind: 'hotspot',
|
|
724
|
+
sourceKind: 'git',
|
|
725
|
+
sourceRef: `branch:${branch}`,
|
|
726
|
+
confidence: 'low',
|
|
727
|
+
tags: ['bootstrap', 'git', 'branch'],
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Step 13: Recent tags
|
|
732
|
+
const tagResult = spawnSync('git', ['tag', '--sort=-creatordate', '-l'], {
|
|
733
|
+
cwd,
|
|
734
|
+
encoding: 'utf-8',
|
|
735
|
+
timeout: 5000,
|
|
736
|
+
});
|
|
737
|
+
if (tagResult.status === 0) {
|
|
738
|
+
const tags = tagResult.stdout.split(/\r?\n/).map((t) => t.trim()).filter(Boolean).slice(0, 3);
|
|
739
|
+
if (tags.length > 0) {
|
|
740
|
+
hotspotSeeds.push(createSeed({
|
|
741
|
+
text: `Version tags: ${tags.join(', ')} (${tags.length} most recent)`,
|
|
742
|
+
seedKind: 'convention',
|
|
743
|
+
sourceKind: 'git',
|
|
744
|
+
sourceRef: 'tags',
|
|
745
|
+
confidence: 'medium',
|
|
746
|
+
tags: ['bootstrap', 'git', 'versioning'],
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
706
750
|
return {
|
|
707
751
|
available: true,
|
|
708
752
|
repoFingerprint,
|
|
@@ -1594,4 +1638,137 @@ function normalizeTarget(target) {
|
|
|
1594
1638
|
const trimmed = target?.trim();
|
|
1595
1639
|
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
1596
1640
|
}
|
|
1641
|
+
// ─── Step 12: Additional brownfield sources ──────────────────────────────────
|
|
1642
|
+
const CI_WORKFLOW_DIRS = ['.github/workflows', '.gitlab'];
|
|
1643
|
+
const CI_FILES = ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml'];
|
|
1644
|
+
const CONTRIBUTING_FILES = ['CONTRIBUTING.md', 'CONTRIBUTING'];
|
|
1645
|
+
const CHANGELOG_FILES = ['CHANGELOG.md', 'CHANGELOG', 'HISTORY.md'];
|
|
1646
|
+
const DOCKER_FILES = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'];
|
|
1647
|
+
const ENV_EXAMPLE_FILES = ['.env.example', '.env.sample', '.env.template'];
|
|
1648
|
+
const ADR_DIRS = ['doc/adr', 'docs/adr', 'doc/decisions', 'docs/decisions', 'adr'];
|
|
1649
|
+
function extractAdditionalBrownfieldSeeds(cwd, target) {
|
|
1650
|
+
const seeds = [];
|
|
1651
|
+
const sources = [];
|
|
1652
|
+
// CI/CD workflows
|
|
1653
|
+
for (const dir of CI_WORKFLOW_DIRS) {
|
|
1654
|
+
const fullPath = path.join(cwd, dir);
|
|
1655
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
1656
|
+
sources.push('ci_workflows');
|
|
1657
|
+
try {
|
|
1658
|
+
const files = fs.readdirSync(fullPath).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
|
|
1659
|
+
if (files.length > 0) {
|
|
1660
|
+
seeds.push(createSeed({
|
|
1661
|
+
text: `CI/CD: ${files.length} workflow(s) in ${dir}/`,
|
|
1662
|
+
seedKind: 'convention',
|
|
1663
|
+
sourceKind: 'ci_config',
|
|
1664
|
+
sourceRef: dir,
|
|
1665
|
+
confidence: 'medium',
|
|
1666
|
+
tags: ['bootstrap', 'ci'],
|
|
1667
|
+
relatedPaths: target ? [target] : undefined,
|
|
1668
|
+
}));
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
catch { /* skip unreadable */ }
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
for (const file of CI_FILES) {
|
|
1676
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
1677
|
+
if (!sources.includes('ci_workflows'))
|
|
1678
|
+
sources.push('ci_config');
|
|
1679
|
+
seeds.push(createSeed({
|
|
1680
|
+
text: `CI/CD config: ${file}`,
|
|
1681
|
+
seedKind: 'convention',
|
|
1682
|
+
sourceKind: 'ci_config',
|
|
1683
|
+
sourceRef: file,
|
|
1684
|
+
confidence: 'medium',
|
|
1685
|
+
tags: ['bootstrap', 'ci'],
|
|
1686
|
+
}));
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
// CONTRIBUTING.md
|
|
1691
|
+
const contributingPath = findFirstExisting(cwd, CONTRIBUTING_FILES);
|
|
1692
|
+
if (contributingPath) {
|
|
1693
|
+
sources.push('contributing');
|
|
1694
|
+
seeds.push(createSeed({
|
|
1695
|
+
text: `Contributing guide found: ${path.basename(contributingPath)}`,
|
|
1696
|
+
seedKind: 'convention',
|
|
1697
|
+
sourceKind: 'contributing',
|
|
1698
|
+
sourceRef: path.basename(contributingPath),
|
|
1699
|
+
confidence: 'medium',
|
|
1700
|
+
tags: ['bootstrap', 'contributing'],
|
|
1701
|
+
}));
|
|
1702
|
+
}
|
|
1703
|
+
// CHANGELOG
|
|
1704
|
+
const changelogPath = findFirstExisting(cwd, CHANGELOG_FILES);
|
|
1705
|
+
if (changelogPath) {
|
|
1706
|
+
sources.push('changelog');
|
|
1707
|
+
seeds.push(createSeed({
|
|
1708
|
+
text: `Changelog found: ${path.basename(changelogPath)}`,
|
|
1709
|
+
seedKind: 'convention',
|
|
1710
|
+
sourceKind: 'changelog',
|
|
1711
|
+
sourceRef: path.basename(changelogPath),
|
|
1712
|
+
confidence: 'low',
|
|
1713
|
+
tags: ['bootstrap', 'changelog'],
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
// Docker
|
|
1717
|
+
for (const file of DOCKER_FILES) {
|
|
1718
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
1719
|
+
sources.push('docker');
|
|
1720
|
+
seeds.push(createSeed({
|
|
1721
|
+
text: `Docker config: ${file}`,
|
|
1722
|
+
seedKind: 'convention',
|
|
1723
|
+
sourceKind: 'docker',
|
|
1724
|
+
sourceRef: file,
|
|
1725
|
+
confidence: 'medium',
|
|
1726
|
+
tags: ['bootstrap', 'docker', 'infrastructure'],
|
|
1727
|
+
}));
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
// .env.example
|
|
1732
|
+
const envPath = findFirstExisting(cwd, ENV_EXAMPLE_FILES);
|
|
1733
|
+
if (envPath) {
|
|
1734
|
+
sources.push('env_example');
|
|
1735
|
+
try {
|
|
1736
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
1737
|
+
const varCount = content.split(/\r?\n/).filter((l) => l.includes('=') && !l.startsWith('#')).length;
|
|
1738
|
+
seeds.push(createSeed({
|
|
1739
|
+
text: `Environment template: ${path.basename(envPath)} (${varCount} variables)`,
|
|
1740
|
+
seedKind: 'convention',
|
|
1741
|
+
sourceKind: 'env_example',
|
|
1742
|
+
sourceRef: path.basename(envPath),
|
|
1743
|
+
confidence: 'medium',
|
|
1744
|
+
tags: ['bootstrap', 'env', 'configuration'],
|
|
1745
|
+
}));
|
|
1746
|
+
}
|
|
1747
|
+
catch { /* skip unreadable */ }
|
|
1748
|
+
}
|
|
1749
|
+
// ADR (Architecture Decision Records)
|
|
1750
|
+
for (const dir of ADR_DIRS) {
|
|
1751
|
+
const fullPath = path.join(cwd, dir);
|
|
1752
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
1753
|
+
sources.push('adr');
|
|
1754
|
+
try {
|
|
1755
|
+
const files = fs.readdirSync(fullPath).filter((f) => f.endsWith('.md'));
|
|
1756
|
+
if (files.length > 0) {
|
|
1757
|
+
seeds.push(createSeed({
|
|
1758
|
+
text: `Architecture Decision Records: ${files.length} ADR(s) in ${dir}/`,
|
|
1759
|
+
seedKind: 'convention',
|
|
1760
|
+
sourceKind: 'adr',
|
|
1761
|
+
sourceRef: dir,
|
|
1762
|
+
confidence: 'high',
|
|
1763
|
+
tags: ['bootstrap', 'adr', 'architecture'],
|
|
1764
|
+
relatedPaths: target ? [target] : undefined,
|
|
1765
|
+
}));
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
catch { /* skip unreadable */ }
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return { seeds, sources: [...new Set(sources)] };
|
|
1773
|
+
}
|
|
1597
1774
|
//# sourceMappingURL=bootstrap.js.map
|