@vreko/cli 3.0.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/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/CeremonyView-LQS7FTMK.js +134 -0
- package/dist/CeremonyView-LQS7FTMK.js.map +1 -0
- package/dist/InitApp-7K5DTYSW.js +1479 -0
- package/dist/InitApp-7K5DTYSW.js.map +1 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js +7 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js.map +1 -0
- package/dist/TuiApp-FX23XQBK.js +8 -0
- package/dist/TuiApp-FX23XQBK.js.map +1 -0
- package/dist/analysis-ABEO6RTN.js +8 -0
- package/dist/analysis-ABEO6RTN.js.map +1 -0
- package/dist/auth-XNBEBNPY.js +7669 -0
- package/dist/auth-XNBEBNPY.js.map +1 -0
- package/dist/ceremony-M7CXVBVA.js +45 -0
- package/dist/ceremony-M7CXVBVA.js.map +1 -0
- package/dist/chunk-A3QSZJPD.js +3147 -0
- package/dist/chunk-A3QSZJPD.js.map +1 -0
- package/dist/chunk-ASGZ5B6C.js +3969 -0
- package/dist/chunk-ASGZ5B6C.js.map +1 -0
- package/dist/chunk-DMXC2JTC.js +58 -0
- package/dist/chunk-DMXC2JTC.js.map +1 -0
- package/dist/chunk-EEBSK2IH.js +161 -0
- package/dist/chunk-EEBSK2IH.js.map +1 -0
- package/dist/chunk-EWOJGXRX.js +22 -0
- package/dist/chunk-EWOJGXRX.js.map +1 -0
- package/dist/chunk-F7GEJLP7.js +2389 -0
- package/dist/chunk-F7GEJLP7.js.map +1 -0
- package/dist/chunk-GOYL3F4T.js +605 -0
- package/dist/chunk-GOYL3F4T.js.map +1 -0
- package/dist/chunk-GRMRYWYS.js +17 -0
- package/dist/chunk-GRMRYWYS.js.map +1 -0
- package/dist/chunk-GSUGROXB.js +1951 -0
- package/dist/chunk-GSUGROXB.js.map +1 -0
- package/dist/chunk-H7773ONB.js +50 -0
- package/dist/chunk-H7773ONB.js.map +1 -0
- package/dist/chunk-HFQHU5LC.js +445 -0
- package/dist/chunk-HFQHU5LC.js.map +1 -0
- package/dist/chunk-IVHUBLJD.js +318 -0
- package/dist/chunk-IVHUBLJD.js.map +1 -0
- package/dist/chunk-KJWKY4L4.js +14 -0
- package/dist/chunk-KJWKY4L4.js.map +1 -0
- package/dist/chunk-MJVY2XUN.js +1793 -0
- package/dist/chunk-MJVY2XUN.js.map +1 -0
- package/dist/chunk-QWZVCJII.js +1797 -0
- package/dist/chunk-QWZVCJII.js.map +1 -0
- package/dist/chunk-VTSNRV3V.js +3237 -0
- package/dist/chunk-VTSNRV3V.js.map +1 -0
- package/dist/chunk-W5B4GTXR.js +1466 -0
- package/dist/chunk-W5B4GTXR.js.map +1 -0
- package/dist/chunk-WZEZLVOW.js +4995 -0
- package/dist/chunk-WZEZLVOW.js.map +1 -0
- package/dist/chunk-YPTTIXKC.js +199 -0
- package/dist/chunk-YPTTIXKC.js.map +1 -0
- package/dist/chunk-Z55UGM6X.js +6360 -0
- package/dist/chunk-Z55UGM6X.js.map +1 -0
- package/dist/chunk-ZIIRQODJ.js +110 -0
- package/dist/chunk-ZIIRQODJ.js.map +1 -0
- package/dist/chunk-ZSUQ4FMB.js +77 -0
- package/dist/chunk-ZSUQ4FMB.js.map +1 -0
- package/dist/client-JMTSZS3V.js +10 -0
- package/dist/client-JMTSZS3V.js.map +1 -0
- package/dist/deprecated-snap.js +19 -0
- package/dist/deprecated-snap.js.map +1 -0
- package/dist/dist-2KWBZFLA.js +14 -0
- package/dist/dist-2KWBZFLA.js.map +1 -0
- package/dist/dist-5ZYKNNU3.js +7 -0
- package/dist/dist-5ZYKNNU3.js.map +1 -0
- package/dist/dist-CP3RFHPI.js +11 -0
- package/dist/dist-CP3RFHPI.js.map +1 -0
- package/dist/gecko-53ITAGG6.js +56 -0
- package/dist/gecko-53ITAGG6.js.map +1 -0
- package/dist/guards-QAFC64NO.js +7 -0
- package/dist/guards-QAFC64NO.js.map +1 -0
- package/dist/index.js +57785 -0
- package/dist/index.js.map +1 -0
- package/dist/init-command-246JIVXM.js +7 -0
- package/dist/init-command-246JIVXM.js.map +1 -0
- package/dist/init-core-KAI7LCXZ.js +12 -0
- package/dist/init-core-KAI7LCXZ.js.map +1 -0
- package/dist/init-scan-RZNYDTUV.js +1919 -0
- package/dist/init-scan-RZNYDTUV.js.map +1 -0
- package/dist/local-service-adapter-6KNN6WQL.js +8 -0
- package/dist/local-service-adapter-6KNN6WQL.js.map +1 -0
- package/dist/secure-credentials-JXWAQLS2.js +306 -0
- package/dist/secure-credentials-JXWAQLS2.js.map +1 -0
- package/dist/tui-TPJPUS2R.js +111 -0
- package/dist/tui-TPJPUS2R.js.map +1 -0
- package/dist/vreko-dir-O3RLG7PI.js +8 -0
- package/dist/vreko-dir-O3RLG7PI.js.map +1 -0
- package/package.json +132 -0
- package/scripts/check-banned-words.ts +152 -0
- package/scripts/hooks/posttooluse-file-notify.sh +108 -0
- package/scripts/hooks/pretooluse-fragile-guard.sh +82 -0
- package/scripts/post-install-notice.js +24 -0
- package/scripts/postinstall.mjs +84 -0
- package/scripts/preuninstall.mjs +34 -0
- package/scripts/verify-jsx-transform.mjs +55 -0
|
@@ -0,0 +1,1797 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cliState } from './chunk-GRMRYWYS.js';
|
|
3
|
+
import { getDaemonClient, connectToDaemon } from './chunk-EEBSK2IH.js';
|
|
4
|
+
import { getServicePidPath } from './chunk-GSUGROXB.js';
|
|
5
|
+
import { resolveVrekoBinaryPath, detectAIClients, getVrekoMCPConfig, writeClientConfig } from './chunk-MJVY2XUN.js';
|
|
6
|
+
import { __name, __require } from './chunk-EWOJGXRX.js';
|
|
7
|
+
import { execFileSync } from 'child_process';
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, lstatSync, copyFileSync, chmodSync, rmSync, statSync, appendFileSync } from 'fs';
|
|
9
|
+
import { readdir } from 'fs/promises';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join, dirname, normalize, resolve, basename, extname } from 'path';
|
|
12
|
+
import * as clack from '@clack/prompts';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
process.env.VREKO_CLI='true';process.env.NODE_NO_WARNINGS='1';
|
|
20
|
+
function generateClaudeIntegration(config, baseline) {
|
|
21
|
+
const { workspacePath, overwrite = false } = config;
|
|
22
|
+
const result = {
|
|
23
|
+
filesWritten: [],
|
|
24
|
+
filesSkipped: [],
|
|
25
|
+
intelligenceAvailable: false,
|
|
26
|
+
fragileFilesIncluded: 0,
|
|
27
|
+
coChangePatternsIncluded: 0,
|
|
28
|
+
globalSettingsUpdated: false
|
|
29
|
+
};
|
|
30
|
+
const fragileFiles = (baseline?.fragileFiles ?? []).filter((f) => f.compositeScore >= 30).sort((a, b) => b.compositeScore - a.compositeScore).slice(0, 15).map((f) => ({
|
|
31
|
+
path: f.path,
|
|
32
|
+
score: f.compositeScore / 100,
|
|
33
|
+
level: f.compositeScore >= 80 ? "critical" : f.compositeScore >= 60 ? "fragile" : "moderate"
|
|
34
|
+
}));
|
|
35
|
+
const coChangePairs = (baseline?.coChangeClusters ?? []).filter((c) => c.files.length >= 2 && c.coOccurrenceRate >= 0.5).sort((a, b) => b.coOccurrenceRate - a.coOccurrenceRate).slice(0, 10).map((c) => ({
|
|
36
|
+
fileA: c.files[0],
|
|
37
|
+
fileB: c.files[1],
|
|
38
|
+
frequency: c.coOccurrenceRate
|
|
39
|
+
}));
|
|
40
|
+
result.intelligenceAvailable = fragileFiles.length > 0 || coChangePairs.length > 0;
|
|
41
|
+
result.fragileFilesIncluded = fragileFiles.length;
|
|
42
|
+
result.coChangePatternsIncluded = coChangePairs.length;
|
|
43
|
+
const files = [
|
|
44
|
+
{
|
|
45
|
+
relativePath: ".mcp.json",
|
|
46
|
+
content: buildMcpJson(config)
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
relativePath: ".claude/agents/vreko-preflight.md",
|
|
50
|
+
content: buildPreflightAgent(fragileFiles, coChangePairs)
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
relativePath: ".claude/agents/vreko-session.md",
|
|
54
|
+
content: VREKO_SESSION_AGENT
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
relativePath: ".claude/commands/snap-check.md",
|
|
58
|
+
content: SNAP_CHECK_COMMAND
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const fullPath = join(workspacePath, file.relativePath);
|
|
63
|
+
const dir = join(fullPath, "..");
|
|
64
|
+
if (existsSync(fullPath) && !overwrite) {
|
|
65
|
+
const existing = readFileSync(fullPath, "utf-8");
|
|
66
|
+
const existingHash = createHash("sha256").update(existing).digest("hex");
|
|
67
|
+
const newHash = createHash("sha256").update(file.content).digest("hex");
|
|
68
|
+
const suffix = existingHash !== newHash ? " (modified, use --force to update)" : "";
|
|
69
|
+
result.filesSkipped.push(`${file.relativePath}${suffix}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
mkdirSync(dir, {
|
|
73
|
+
recursive: true
|
|
74
|
+
});
|
|
75
|
+
writeFileSync(fullPath, file.content, "utf-8");
|
|
76
|
+
result.filesWritten.push(file.relativePath);
|
|
77
|
+
}
|
|
78
|
+
result.globalSettingsUpdated = writeVrekoToClaudeCodeGlobalSettings();
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
__name(generateClaudeIntegration, "generateClaudeIntegration");
|
|
82
|
+
function buildMcpJson(config) {
|
|
83
|
+
const { command, args: cmdArgs } = resolveMcpCommand(config.workspacePath);
|
|
84
|
+
const servers = {
|
|
85
|
+
vreko: {
|
|
86
|
+
type: "stdio",
|
|
87
|
+
command,
|
|
88
|
+
args: cmdArgs,
|
|
89
|
+
instructions: "Vreko provides codebase intelligence. Use vreko_pulse to check risk before modifying files. Use vreko_learn to record patterns. Use vreko_end to close sessions."
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
if (config.includeChannel === true) {
|
|
93
|
+
const channelArgs = [
|
|
94
|
+
...cmdArgs
|
|
95
|
+
];
|
|
96
|
+
channelArgs.push("--channel");
|
|
97
|
+
servers["vreko-channel"] = {
|
|
98
|
+
type: "stdio",
|
|
99
|
+
command,
|
|
100
|
+
args: channelArgs,
|
|
101
|
+
instructions: "Vreko intelligence channel. Pushes real-time warnings about fragile files and risk spikes. Requires: claude --channels vreko-channel"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return `${JSON.stringify({
|
|
105
|
+
mcpServers: servers
|
|
106
|
+
}, null, 2)}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
__name(buildMcpJson, "buildMcpJson");
|
|
110
|
+
function resolveMcpCommand(workspacePath) {
|
|
111
|
+
return {
|
|
112
|
+
command: resolveVrekoBinaryPath(),
|
|
113
|
+
args: [
|
|
114
|
+
"mcp",
|
|
115
|
+
"--stdio",
|
|
116
|
+
"--workspace",
|
|
117
|
+
workspacePath
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
__name(resolveMcpCommand, "resolveMcpCommand");
|
|
122
|
+
function writeVrekoToClaudeCodeGlobalSettings() {
|
|
123
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
124
|
+
if (!existsSync(settingsPath)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
let settings = {};
|
|
128
|
+
try {
|
|
129
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const mcpServers = settings.mcpServers ?? {};
|
|
134
|
+
const command = resolveVrekoBinaryPath();
|
|
135
|
+
const existing = mcpServers.vreko;
|
|
136
|
+
if (existing?.command === command) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
mcpServers.vreko = {
|
|
140
|
+
command,
|
|
141
|
+
args: [
|
|
142
|
+
"mcp",
|
|
143
|
+
"--stdio"
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
settings.mcpServers = mcpServers;
|
|
147
|
+
try {
|
|
148
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
149
|
+
`, "utf-8");
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
__name(writeVrekoToClaudeCodeGlobalSettings, "writeVrekoToClaudeCodeGlobalSettings");
|
|
156
|
+
function buildPreflightAgent(fragileFiles, coChangePairs) {
|
|
157
|
+
let fragileSection;
|
|
158
|
+
if (fragileFiles.length > 0) {
|
|
159
|
+
const lines = fragileFiles.map((f) => `- \`${f.path}\` - fragility: ${f.level} (score: ${f.score.toFixed(2)})`).join("\n");
|
|
160
|
+
fragileSection = `## Known Fragile Files in This Codebase
|
|
161
|
+
|
|
162
|
+
The following files have high rollback rates or frequent issues:
|
|
163
|
+
|
|
164
|
+
${lines}
|
|
165
|
+
|
|
166
|
+
If the task involves any of these files, always recommend creating
|
|
167
|
+
a snapshot before modification.`;
|
|
168
|
+
} else {
|
|
169
|
+
fragileSection = `## Codebase Intelligence
|
|
170
|
+
|
|
171
|
+
Vreko is still learning about this codebase. Use vreko_pulse
|
|
172
|
+
to get real-time risk assessment for any files being modified.
|
|
173
|
+
Intelligence will improve as more sessions are tracked.`;
|
|
174
|
+
}
|
|
175
|
+
let coChangeSection = "";
|
|
176
|
+
if (coChangePairs.length > 0) {
|
|
177
|
+
const lines = coChangePairs.map((p) => `- \`${p.fileA}\` \u2194 \`${p.fileB}\` (${(p.frequency * 100).toFixed(0)}% co-change rate)`).join("\n");
|
|
178
|
+
coChangeSection = `
|
|
179
|
+
|
|
180
|
+
## Co-Change Patterns
|
|
181
|
+
|
|
182
|
+
These files historically change together. If modifying one,
|
|
183
|
+
check whether the others also need updates:
|
|
184
|
+
|
|
185
|
+
${lines}`;
|
|
186
|
+
}
|
|
187
|
+
return `---
|
|
188
|
+
name: vreko-preflight
|
|
189
|
+
description: >
|
|
190
|
+
Run before implementing changes that touch multiple files
|
|
191
|
+
or modify config/infrastructure. Queries Vreko for risk
|
|
192
|
+
context and known fragile patterns in this codebase.
|
|
193
|
+
tools:
|
|
194
|
+
- Read
|
|
195
|
+
- Grep
|
|
196
|
+
- Glob
|
|
197
|
+
- mcp__vreko__vreko_pulse
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
You are a preflight check agent for this codebase. Vreko has
|
|
201
|
+
accumulated intelligence about which files are risky and which
|
|
202
|
+
files always change together.
|
|
203
|
+
|
|
204
|
+
Before the parent agent modifies files, you:
|
|
205
|
+
|
|
206
|
+
1. Call vreko_pulse with the workspace path and list of files about to change
|
|
207
|
+
2. Check the risk assessment in the response
|
|
208
|
+
3. Flag any files with HIGH or CRITICAL fragility
|
|
209
|
+
4. Surface co-change patterns (files that must change together)
|
|
210
|
+
5. Return a structured summary to the parent agent:
|
|
211
|
+
- Files safe to modify in parallel
|
|
212
|
+
- Files requiring sequential, careful changes
|
|
213
|
+
- Known pitfalls from codebase history
|
|
214
|
+
- Whether a snapshot is recommended before proceeding
|
|
215
|
+
|
|
216
|
+
Do NOT modify any files. Read-only analysis only.
|
|
217
|
+
|
|
218
|
+
${fragileSection}${coChangeSection}
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
__name(buildPreflightAgent, "buildPreflightAgent");
|
|
222
|
+
var VREKO_SESSION_AGENT = `---
|
|
223
|
+
name: vreko-session
|
|
224
|
+
description: >
|
|
225
|
+
Manage Vreko session lifecycle. Use at the start and
|
|
226
|
+
end of significant implementation work to capture context
|
|
227
|
+
and trigger intelligence collection.
|
|
228
|
+
tools:
|
|
229
|
+
- mcp__vreko__vreko
|
|
230
|
+
- mcp__vreko__vreko_learn
|
|
231
|
+
- mcp__vreko__vreko_end
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
You manage Vreko sessions for this codebase.
|
|
235
|
+
|
|
236
|
+
When starting work:
|
|
237
|
+
- Call vreko to begin a session with a descriptive task name
|
|
238
|
+
- Note the session context for the parent agent
|
|
239
|
+
|
|
240
|
+
When completing work:
|
|
241
|
+
- Call vreko_learn with any patterns discovered during implementation:
|
|
242
|
+
- Files that needed to change together (co-change pattern)
|
|
243
|
+
- Config that was fragile or surprising (fragile pattern)
|
|
244
|
+
- Dependencies that weren't obvious (dependency pattern)
|
|
245
|
+
- Conventions the codebase follows (convention pattern)
|
|
246
|
+
- Call vreko_end to close the session
|
|
247
|
+
|
|
248
|
+
Capture learnings in this format:
|
|
249
|
+
- Pattern type: co-change | fragile | dependency | convention
|
|
250
|
+
- Affected files: list of file paths
|
|
251
|
+
- Description: what the implementing agent discovered
|
|
252
|
+
|
|
253
|
+
The more patterns captured, the better Vreko's intelligence
|
|
254
|
+
becomes for future sessions.
|
|
255
|
+
`;
|
|
256
|
+
var SNAP_CHECK_COMMAND = `---
|
|
257
|
+
description: Check Vreko risk context for files you're about to change
|
|
258
|
+
argument-hint: <file paths or description of planned changes>
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
Query Vreko intelligence for the specified files or task.
|
|
262
|
+
|
|
263
|
+
1. Call the vreko_pulse MCP tool with the provided context
|
|
264
|
+
2. Display risk scores, fragile file warnings, and relevant history
|
|
265
|
+
3. If any file has fragility level "fragile" or "critical", warn explicitly
|
|
266
|
+
4. List co-change groups - files that should be modified together
|
|
267
|
+
5. Recommend whether to create a manual snapshot before proceeding
|
|
268
|
+
6. If fragile files are involved, suggest the vreko-preflight agent
|
|
269
|
+
for more detailed analysis
|
|
270
|
+
`;
|
|
271
|
+
function validateWorkspacePath(workspacePath) {
|
|
272
|
+
try {
|
|
273
|
+
const normalizedPath = normalize(workspacePath);
|
|
274
|
+
const absolutePath = resolve(normalizedPath);
|
|
275
|
+
if (!absolutePath.startsWith(process.cwd()) && !absolutePath.startsWith("/")) {
|
|
276
|
+
return {
|
|
277
|
+
valid: false,
|
|
278
|
+
root: "",
|
|
279
|
+
error: "Invalid workspace path"
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const hasGit = existsSync(resolve(absolutePath, ".git"));
|
|
283
|
+
const hasPackageJson = existsSync(resolve(absolutePath, "package.json"));
|
|
284
|
+
const hasVreko = existsSync(resolve(absolutePath, ".vreko"));
|
|
285
|
+
if (!hasGit && !hasPackageJson && !hasVreko) {
|
|
286
|
+
return {
|
|
287
|
+
valid: false,
|
|
288
|
+
root: absolutePath,
|
|
289
|
+
error: "Workspace must contain at least one marker: .git, package.json, or .vreko"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const stat = lstatSync(absolutePath);
|
|
294
|
+
if (stat.isSymbolicLink()) {
|
|
295
|
+
return {
|
|
296
|
+
valid: false,
|
|
297
|
+
root: absolutePath,
|
|
298
|
+
error: "Workspace path cannot be a symbolic link"
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
return {
|
|
303
|
+
valid: false,
|
|
304
|
+
root: absolutePath,
|
|
305
|
+
error: "Cannot access workspace path"
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
valid: true,
|
|
310
|
+
root: absolutePath
|
|
311
|
+
};
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return {
|
|
314
|
+
valid: false,
|
|
315
|
+
root: "",
|
|
316
|
+
error: error instanceof Error ? error.message : "Unknown error validating workspace"
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
__name(validateWorkspacePath, "validateWorkspacePath");
|
|
321
|
+
function resolveWorkspaceRoot(explicitPath) {
|
|
322
|
+
if (explicitPath) {
|
|
323
|
+
const validation = validateWorkspacePath(explicitPath);
|
|
324
|
+
if (validation.valid) {
|
|
325
|
+
return validation;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
let currentPath = resolve(process.cwd());
|
|
329
|
+
const maxIterations = 50;
|
|
330
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
331
|
+
const hasMarker = existsSync(resolve(currentPath, ".git")) || existsSync(resolve(currentPath, "package.json")) || existsSync(resolve(currentPath, ".vreko"));
|
|
332
|
+
if (hasMarker) {
|
|
333
|
+
return validateWorkspacePath(currentPath);
|
|
334
|
+
}
|
|
335
|
+
const parent = resolve(currentPath, "..");
|
|
336
|
+
if (parent === currentPath) {
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
currentPath = parent;
|
|
340
|
+
}
|
|
341
|
+
return validateWorkspacePath(process.cwd());
|
|
342
|
+
}
|
|
343
|
+
__name(resolveWorkspaceRoot, "resolveWorkspaceRoot");
|
|
344
|
+
function findWorkspaceRoot(cwd) {
|
|
345
|
+
let dir = cwd;
|
|
346
|
+
while (dir !== "/") {
|
|
347
|
+
if (existsSync(join(dir, ".vreko"))) {
|
|
348
|
+
return dir;
|
|
349
|
+
}
|
|
350
|
+
const parent = join(dir, "..");
|
|
351
|
+
if (parent === dir) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
dir = parent;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
__name(findWorkspaceRoot, "findWorkspaceRoot");
|
|
359
|
+
function findGitRoot(cwd) {
|
|
360
|
+
try {
|
|
361
|
+
const { execSync } = __require("child_process");
|
|
362
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
363
|
+
cwd,
|
|
364
|
+
encoding: "utf-8",
|
|
365
|
+
stdio: [
|
|
366
|
+
"pipe",
|
|
367
|
+
"pipe",
|
|
368
|
+
"pipe"
|
|
369
|
+
]
|
|
370
|
+
}).trim();
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
__name(findGitRoot, "findGitRoot");
|
|
376
|
+
var HOOK_SCRIPT_SRC = join(dirname(fileURLToPath(import.meta.url)), "../scripts/hooks/pretooluse-fragile-guard.sh");
|
|
377
|
+
var VREKO_HOOK_COMMAND = ".claude/hooks/vreko-fragile-guard.sh";
|
|
378
|
+
var VREKO_HOOK_MARKER = "vreko-fragile-guard.sh";
|
|
379
|
+
var HOOK_ENTRY = {
|
|
380
|
+
matcher: "Edit|Write|MultiEdit",
|
|
381
|
+
hooks: [
|
|
382
|
+
{
|
|
383
|
+
type: "command",
|
|
384
|
+
command: VREKO_HOOK_COMMAND
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
};
|
|
388
|
+
var POST_HOOK_SCRIPT_SRC = join(dirname(fileURLToPath(import.meta.url)), "../scripts/hooks/posttooluse-file-notify.sh");
|
|
389
|
+
var POST_HOOK_COMMAND = ".claude/hooks/vreko-file-notify.sh";
|
|
390
|
+
var POST_HOOK_MARKER = "vreko-file-notify.sh";
|
|
391
|
+
var POST_HOOK_ENTRY = {
|
|
392
|
+
matcher: "Edit|Write|MultiEdit",
|
|
393
|
+
hooks: [
|
|
394
|
+
{
|
|
395
|
+
type: "command",
|
|
396
|
+
command: POST_HOOK_COMMAND
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
function readSettings(settingsPath) {
|
|
401
|
+
if (!existsSync(settingsPath)) {
|
|
402
|
+
return {};
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
return JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.error(`[vreko hooks] Failed to parse ${settingsPath}:`, err);
|
|
408
|
+
return {};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
__name(readSettings, "readSettings");
|
|
412
|
+
function writeSettings(settingsPath, settings) {
|
|
413
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
414
|
+
`, "utf-8");
|
|
415
|
+
}
|
|
416
|
+
__name(writeSettings, "writeSettings");
|
|
417
|
+
function readVrekoConfig(workspace) {
|
|
418
|
+
const configPath = join(workspace, ".vreko", "config.json");
|
|
419
|
+
if (!existsSync(configPath)) {
|
|
420
|
+
return {};
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error("[vreko hooks] Failed to parse .vreko/config.json:", err);
|
|
426
|
+
return {};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
__name(readVrekoConfig, "readVrekoConfig");
|
|
430
|
+
function writeVrekoConfig(workspace, config) {
|
|
431
|
+
const configDir = join(workspace, ".vreko");
|
|
432
|
+
if (!existsSync(configDir)) {
|
|
433
|
+
mkdirSync(configDir, {
|
|
434
|
+
recursive: true
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
writeFileSync(join(configDir, "config.json"), `${JSON.stringify(config, null, 2)}
|
|
438
|
+
`, "utf-8");
|
|
439
|
+
}
|
|
440
|
+
__name(writeVrekoConfig, "writeVrekoConfig");
|
|
441
|
+
function isVrekoHookPresent(preToolUseArray) {
|
|
442
|
+
return preToolUseArray.some((entry) => {
|
|
443
|
+
if (typeof entry !== "object" || entry === null) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
const e = entry;
|
|
447
|
+
const hooks = e.hooks;
|
|
448
|
+
if (!Array.isArray(hooks)) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
return hooks.some((h) => {
|
|
452
|
+
if (typeof h !== "object" || h === null) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
const hook = h;
|
|
456
|
+
return typeof hook.command === "string" && hook.command.includes(VREKO_HOOK_MARKER);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
__name(isVrekoHookPresent, "isVrekoHookPresent");
|
|
461
|
+
function isVrekoPostHookPresent(postToolUseArray) {
|
|
462
|
+
return postToolUseArray.some((entry) => {
|
|
463
|
+
if (typeof entry !== "object" || entry === null) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const e = entry;
|
|
467
|
+
const hooks = e.hooks;
|
|
468
|
+
if (!Array.isArray(hooks)) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
return hooks.some((h) => {
|
|
472
|
+
if (typeof h !== "object" || h === null) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
const hook = h;
|
|
476
|
+
return typeof hook.command === "string" && hook.command.includes(POST_HOOK_MARKER);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
__name(isVrekoPostHookPresent, "isVrekoPostHookPresent");
|
|
481
|
+
function copyHookScripts(cwd) {
|
|
482
|
+
const hooksDir = join(cwd, ".claude", "hooks");
|
|
483
|
+
const hookDest = join(cwd, VREKO_HOOK_COMMAND);
|
|
484
|
+
const postHookDest = join(cwd, POST_HOOK_COMMAND);
|
|
485
|
+
if (!existsSync(HOOK_SCRIPT_SRC)) {
|
|
486
|
+
throw new Error(`Hook script not found at ${HOOK_SCRIPT_SRC}`);
|
|
487
|
+
}
|
|
488
|
+
if (!existsSync(hooksDir)) {
|
|
489
|
+
mkdirSync(hooksDir, {
|
|
490
|
+
recursive: true
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
copyFileSync(HOOK_SCRIPT_SRC, hookDest);
|
|
494
|
+
chmodSync(hookDest, 493);
|
|
495
|
+
if (existsSync(POST_HOOK_SCRIPT_SRC)) {
|
|
496
|
+
copyFileSync(POST_HOOK_SCRIPT_SRC, postHookDest);
|
|
497
|
+
chmodSync(postHookDest, 493);
|
|
498
|
+
} else {
|
|
499
|
+
console.error(`[vreko hooks] PostToolUse hook script not found at ${POST_HOOK_SCRIPT_SRC}`);
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
hookDest,
|
|
503
|
+
postHookDest
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
__name(copyHookScripts, "copyHookScripts");
|
|
507
|
+
function mergeHookEntries(settingsPath) {
|
|
508
|
+
const settings = readSettings(settingsPath);
|
|
509
|
+
const hooks = settings.hooks ?? {};
|
|
510
|
+
const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
511
|
+
const postToolUse = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
|
|
512
|
+
if (!isVrekoHookPresent(preToolUse)) preToolUse.push(HOOK_ENTRY);
|
|
513
|
+
if (!isVrekoPostHookPresent(postToolUse)) postToolUse.push(POST_HOOK_ENTRY);
|
|
514
|
+
hooks.PreToolUse = preToolUse;
|
|
515
|
+
hooks.PostToolUse = postToolUse;
|
|
516
|
+
settings.hooks = hooks;
|
|
517
|
+
writeSettings(settingsPath, settings);
|
|
518
|
+
}
|
|
519
|
+
__name(mergeHookEntries, "mergeHookEntries");
|
|
520
|
+
function writeVrekoConfigHooks(cwd) {
|
|
521
|
+
const config = readVrekoConfig(cwd);
|
|
522
|
+
const cfgHooks = config.hooks ?? {};
|
|
523
|
+
cfgHooks["claude-code"] = {
|
|
524
|
+
installed: true,
|
|
525
|
+
mode: "advisory",
|
|
526
|
+
fragileThreshold: 2,
|
|
527
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
528
|
+
postToolUseInstalled: true
|
|
529
|
+
};
|
|
530
|
+
config.hooks = cfgHooks;
|
|
531
|
+
writeVrekoConfig(cwd, config);
|
|
532
|
+
}
|
|
533
|
+
__name(writeVrekoConfigHooks, "writeVrekoConfigHooks");
|
|
534
|
+
async function installHook(tool, workspace) {
|
|
535
|
+
if (tool !== "claude-code") {
|
|
536
|
+
throw new Error(`Hook install for '${tool}' is not yet supported. Only 'claude-code' is supported.`);
|
|
537
|
+
}
|
|
538
|
+
const cwd = workspace ?? process.cwd();
|
|
539
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
540
|
+
const { hookDest, postHookDest } = copyHookScripts(cwd);
|
|
541
|
+
mergeHookEntries(settingsPath);
|
|
542
|
+
writeVrekoConfigHooks(cwd);
|
|
543
|
+
console.log("Vreko hooks installed for Claude Code");
|
|
544
|
+
console.log(` PreToolUse hook: ${hookDest}`);
|
|
545
|
+
console.log(` Settings: ${settingsPath}`);
|
|
546
|
+
}
|
|
547
|
+
__name(installHook, "installHook");
|
|
548
|
+
async function uninstallHook(tool, workspace) {
|
|
549
|
+
if (tool !== "claude-code") {
|
|
550
|
+
console.error(`[vreko hooks] Hook uninstall for '${tool}' is not yet supported.`);
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
const cwd = workspace ?? process.cwd();
|
|
554
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
555
|
+
const hookDest = join(cwd, VREKO_HOOK_COMMAND);
|
|
556
|
+
if (existsSync(settingsPath)) {
|
|
557
|
+
try {
|
|
558
|
+
const settings = readSettings(settingsPath);
|
|
559
|
+
const hooks = settings.hooks ?? {};
|
|
560
|
+
if (Array.isArray(hooks.PreToolUse)) {
|
|
561
|
+
hooks.PreToolUse = hooks.PreToolUse.filter((entry) => {
|
|
562
|
+
if (typeof entry !== "object" || entry === null) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
const e = entry;
|
|
566
|
+
const entryHooks = e.hooks;
|
|
567
|
+
if (!Array.isArray(entryHooks)) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
return !entryHooks.some((h) => {
|
|
571
|
+
if (typeof h !== "object" || h === null) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
const hook = h;
|
|
575
|
+
return typeof hook.command === "string" && hook.command.includes(VREKO_HOOK_MARKER);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (Array.isArray(hooks.PostToolUse)) {
|
|
580
|
+
hooks.PostToolUse = hooks.PostToolUse.filter((entry) => {
|
|
581
|
+
if (typeof entry !== "object" || entry === null) {
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
const e = entry;
|
|
585
|
+
const entryHooks = e.hooks;
|
|
586
|
+
if (!Array.isArray(entryHooks)) {
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
return !entryHooks.some((h) => {
|
|
590
|
+
if (typeof h !== "object" || h === null) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
const hook = h;
|
|
594
|
+
return typeof hook.command === "string" && hook.command.includes(POST_HOOK_MARKER);
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
settings.hooks = hooks;
|
|
599
|
+
writeSettings(settingsPath, settings);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
console.error("[vreko hooks] Failed to update settings.json:", err);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (existsSync(hookDest)) {
|
|
605
|
+
try {
|
|
606
|
+
rmSync(hookDest);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
console.error("[vreko hooks] Failed to remove PreToolUse hook script:", err);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const postHookDest = join(cwd, POST_HOOK_COMMAND);
|
|
612
|
+
if (existsSync(postHookDest)) {
|
|
613
|
+
try {
|
|
614
|
+
rmSync(postHookDest);
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.error("[vreko hooks] Failed to remove PostToolUse hook script:", err);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const config = readVrekoConfig(cwd);
|
|
620
|
+
const cfgHooks = config.hooks ?? {};
|
|
621
|
+
const claudeCodeCfg = cfgHooks["claude-code"] ?? {};
|
|
622
|
+
claudeCodeCfg.installed = false;
|
|
623
|
+
cfgHooks["claude-code"] = claudeCodeCfg;
|
|
624
|
+
config.hooks = cfgHooks;
|
|
625
|
+
writeVrekoConfig(cwd, config);
|
|
626
|
+
console.log("Vreko hook removed");
|
|
627
|
+
}
|
|
628
|
+
__name(uninstallHook, "uninstallHook");
|
|
629
|
+
async function hookStatus(workspace) {
|
|
630
|
+
const cwd = workspace ?? process.cwd();
|
|
631
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
632
|
+
const hookDest = join(cwd, VREKO_HOOK_COMMAND);
|
|
633
|
+
const config = readVrekoConfig(cwd);
|
|
634
|
+
const cfgHooks = config.hooks ?? {};
|
|
635
|
+
const claudeCodeCfg = cfgHooks["claude-code"] ?? {};
|
|
636
|
+
const configInstalled = claudeCodeCfg.installed === true;
|
|
637
|
+
const scriptExists = existsSync(hookDest);
|
|
638
|
+
const settings = readSettings(settingsPath);
|
|
639
|
+
const hooks = settings.hooks ?? {};
|
|
640
|
+
const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
641
|
+
const postToolUse = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
|
|
642
|
+
const settingsHasEntry = isVrekoHookPresent(preToolUse);
|
|
643
|
+
const settingsHasPostEntry = isVrekoPostHookPresent(postToolUse);
|
|
644
|
+
const postHookDest = join(cwd, POST_HOOK_COMMAND);
|
|
645
|
+
const postScriptExists = existsSync(postHookDest);
|
|
646
|
+
console.log("Vreko hooks status:");
|
|
647
|
+
console.log(` Config installed flag: ${configInstalled ? "\u2705 true" : "\u274C false"}`);
|
|
648
|
+
console.log(` PreToolUse: script ${scriptExists ? "\u2705" : "\u274C"}, settings ${settingsHasEntry ? "\u2705" : "\u274C"}`);
|
|
649
|
+
console.log(` PostToolUse: script ${postScriptExists ? "\u2705" : "\u274C"}, settings ${settingsHasPostEntry ? "\u2705" : "\u274C"}`);
|
|
650
|
+
const fullyInstalled = configInstalled && scriptExists && settingsHasEntry && postScriptExists && settingsHasPostEntry;
|
|
651
|
+
const notInstalled = !configInstalled && !scriptExists && !settingsHasEntry && !postScriptExists && !settingsHasPostEntry;
|
|
652
|
+
if (fullyInstalled) {
|
|
653
|
+
console.log("\n\u2705 All hooks fully installed and active");
|
|
654
|
+
if (claudeCodeCfg.mode) {
|
|
655
|
+
console.log(` Mode: ${claudeCodeCfg.mode}`);
|
|
656
|
+
}
|
|
657
|
+
} else if (notInstalled) {
|
|
658
|
+
console.log("\n Not installed. Run: vreko hooks install --tool claude-code");
|
|
659
|
+
} else {
|
|
660
|
+
console.log("\n\u26A0\uFE0F Partial install detected. Run uninstall then reinstall to fix.");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
__name(hookStatus, "hookStatus");
|
|
664
|
+
function createHooksCommand() {
|
|
665
|
+
const hooks = new Command("hooks").description("Manage Vreko integration hooks for AI coding tools").addHelpText("after", "\nExamples:\n vreko hooks install --tool claude-code\n vreko hooks status\n");
|
|
666
|
+
hooks.command("install").description("Install the Vreko PreToolUse hook for the specified AI tool").requiredOption("--tool <tool>", "AI tool to install hook for (e.g. claude-code)").option("--workspace <path>", "Workspace directory (default: cwd)").action(async (opts) => {
|
|
667
|
+
try {
|
|
668
|
+
await installHook(opts.tool, opts.workspace);
|
|
669
|
+
} catch (err) {
|
|
670
|
+
console.error("[vreko hooks] Install failed:", err);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
hooks.command("uninstall").description("Remove the Vreko PreToolUse hook for the specified AI tool").requiredOption("--tool <tool>", "AI tool to uninstall hook for (e.g. claude-code)").option("--workspace <path>", "Workspace directory (default: cwd)").action(async (opts) => {
|
|
675
|
+
try {
|
|
676
|
+
await uninstallHook(opts.tool, opts.workspace);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
console.error("[vreko hooks] Uninstall failed:", err);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
hooks.command("status").description("Show current hook installation status").option("--workspace <path>", "Workspace directory (default: cwd)").action(async (opts) => {
|
|
683
|
+
try {
|
|
684
|
+
await hookStatus(opts.workspace);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
console.error("[vreko hooks] Status check failed:", err);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return hooks;
|
|
691
|
+
}
|
|
692
|
+
__name(createHooksCommand, "createHooksCommand");
|
|
693
|
+
|
|
694
|
+
// src/commands/init/init-core.ts
|
|
695
|
+
var GROUND_TRUTH_SKILL_CONTENT = `---
|
|
696
|
+
name: ground-truth
|
|
697
|
+
description: Disciplined methodology for AI-assisted coding that keeps Claude anchored to what the codebase actually says instead of what Claude assumes. Use this skill for any non-trivial coding task - refactors, multi-file changes, bug fixes, feature implementation, spec work, or anything touching more than a single function. Also use when the user mentions "refactor," "implement," "add a feature," "fix this bug," "migrate," "clean up," "audit," or anything that implies structured work on a real codebase. Especially use when working inside a git repository or when the user references files, modules, or prior work. Do not skip for tasks that seem simple on the surface; small changes in real codebases frequently have non-obvious ripple effects, and Ground Truth is built to catch those before they ship. The skill also responds to the user phrases "run ground truth," "ground truth audit," "brief me," "verify this," "close the session," and "pulse check."
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
# Ground Truth
|
|
701
|
+
|
|
702
|
+
A methodology for disciplined coding work in real codebases, powered by Vreko behavioral intelligence.
|
|
703
|
+
|
|
704
|
+
## The core principle
|
|
705
|
+
|
|
706
|
+
Ground truth is what the codebase actually says - the imports that actually exist, the functions that are actually called, the types that are actually exported, the tests that actually pass. Everything else is assumption. LLMs produce code that looks right more easily than they produce code that is right in the specific context of the codebase in front of them. The gap between those two things is where regressions live.
|
|
707
|
+
|
|
708
|
+
Ground Truth closes that gap with four disciplines and five workflows. The disciplines are the mindset; the workflows are the verbs the user invokes.
|
|
709
|
+
|
|
710
|
+
## Pre-flight (run once per session, first)
|
|
711
|
+
|
|
712
|
+
Before any workflow, check the capability surface:
|
|
713
|
+
|
|
714
|
+
\`\`\`bash
|
|
715
|
+
# Is Vreko installed and running?
|
|
716
|
+
vreko --version 2>/dev/null && vreko service status 2>/dev/null
|
|
717
|
+
\`\`\`
|
|
718
|
+
|
|
719
|
+
If the command fails with "command not found", explain to the user in one sentence what Vreko does and offer to install it:
|
|
720
|
+
|
|
721
|
+
> Vreko is a local service that tracks changes to this codebase, attributes them to AI tools, and surfaces codebase-specific intelligence as you work. Want me to install it? (\`npm install -g @vreko/cli && vreko init\`)
|
|
722
|
+
|
|
723
|
+
Do not install without approval. If the user approves, run the install, then \`vreko init\`, then continue with the requested workflow. If the user declines, Ground Truth degrades gracefully - the methodology still applies, the Vreko-specific MCP calls are skipped.
|
|
724
|
+
|
|
725
|
+
If the workspace has \`.vreko/docs/\` present, read \`INTELLIGENCE.md\` before any workflow. That file contains the capability declaration, current session context, and intelligence summary. Treat it as authoritative for this codebase.
|
|
726
|
+
|
|
727
|
+
## Workflow 1: \`ground-truth brief\`
|
|
728
|
+
|
|
729
|
+
**Trigger phrases:** "brief me," "what do I need to know," "give me context," "start a session"
|
|
730
|
+
|
|
731
|
+
**What it does:** Opens a Vreko session, reads the ambient docs, synthesizes a short briefing for the user.
|
|
732
|
+
|
|
733
|
+
**Steps:**
|
|
734
|
+
1. Call MCP tool \`vreko\` with a task description derived from the user's request.
|
|
735
|
+
2. Read \`.vreko/docs/INTELLIGENCE.md\` and \`.vreko/docs/current-session.md\` if they exist.
|
|
736
|
+
3. Synthesize a \u2264200-word briefing covering: what this codebase is, what's fragile, what the agent has learned about it, what the user should be aware of before working.
|
|
737
|
+
4. Present the briefing inline; do not create a file.
|
|
738
|
+
|
|
739
|
+
**Output format:**
|
|
740
|
+
\`\`\`
|
|
741
|
+
Session: <session-id>
|
|
742
|
+
Codebase: <brief description>
|
|
743
|
+
Fragile files in scope: <list or "none identified">
|
|
744
|
+
Recent patterns: <brief summary>
|
|
745
|
+
Watch for: <1-2 specific risks>
|
|
746
|
+
\`\`\`
|
|
747
|
+
|
|
748
|
+
**Anti-pattern:** Do not paraphrase the entire docs folder. Extract the two or three items relevant to what the user is about to do.
|
|
749
|
+
|
|
750
|
+
## Workflow 2: \`ground-truth audit\`
|
|
751
|
+
|
|
752
|
+
**Trigger phrases:** "run ground truth audit," "audit this," "verify the current state," "check the real state"
|
|
753
|
+
|
|
754
|
+
**What it does:** Runs a grep-based audit of the current codebase state to establish ground truth before proceeding. Pairs with \`vreko_pulse\` to fold in live intelligence.
|
|
755
|
+
|
|
756
|
+
**Steps:**
|
|
757
|
+
1. Identify the claim or scope to audit (ask if unclear).
|
|
758
|
+
2. Generate 4-8 \`rg\` / \`grep\` commands that would verify the claim.
|
|
759
|
+
3. Execute each command; capture output verbatim.
|
|
760
|
+
4. Call \`vreko_pulse\` to pull live warnings and fragile-file data.
|
|
761
|
+
5. Synthesize a report: what the audit found, what the pulse flagged, where they agree, where they disagree.
|
|
762
|
+
6. If the user had assumptions going in, call out explicitly which were confirmed and which were contradicted.
|
|
763
|
+
|
|
764
|
+
**Output format:**
|
|
765
|
+
\`\`\`
|
|
766
|
+
Claim: <what was being verified>
|
|
767
|
+
|
|
768
|
+
Audit results:
|
|
769
|
+
[A1] <command> \u2192 <summary of output>
|
|
770
|
+
[A2] <command> \u2192 <summary of output>
|
|
771
|
+
...
|
|
772
|
+
|
|
773
|
+
Pulse:
|
|
774
|
+
Active warnings: <list>
|
|
775
|
+
Fragile files in scope: <list>
|
|
776
|
+
Missing co-change partners: <list>
|
|
777
|
+
|
|
778
|
+
Synthesis:
|
|
779
|
+
Confirmed: <list>
|
|
780
|
+
Contradicted: <list>
|
|
781
|
+
Unknown: <list>
|
|
782
|
+
\`\`\`
|
|
783
|
+
|
|
784
|
+
**Anti-pattern:** Do not let the audit exceed 8 commands. If more depth is needed, split into two audits with distinct scopes.
|
|
785
|
+
|
|
786
|
+
## Workflow 3: \`ground-truth verify\`
|
|
787
|
+
|
|
788
|
+
**Trigger phrases:** "verify this," "run verification," "check the work," "gate this"
|
|
789
|
+
|
|
790
|
+
**What it does:** Runs the verification protocol for work that was just completed - tests, grep gates, lint. Records outcome as a learning.
|
|
791
|
+
|
|
792
|
+
**Steps:**
|
|
793
|
+
1. Identify the verification targets (which tests, which grep gates, which build commands).
|
|
794
|
+
2. Run each in sequence. Capture exit codes and summary output.
|
|
795
|
+
3. If all pass, call \`vreko_learn\` with a terse outcome record: \`{ insight: "<what was verified>: passed", severity: "info" }\`.
|
|
796
|
+
4. If any fail, call \`vreko_learn\` with the failure: \`{ insight: "<what failed and why>", severity: "warn" }\`, then present the failure to the user; do not mark the work complete.
|
|
797
|
+
5. Report pass/fail summary to the user.
|
|
798
|
+
|
|
799
|
+
**Output format:**
|
|
800
|
+
\`\`\`
|
|
801
|
+
Verification gates:
|
|
802
|
+
[V1] <command> \u2192 PASS / FAIL
|
|
803
|
+
[V2] <command> \u2192 PASS / FAIL
|
|
804
|
+
...
|
|
805
|
+
|
|
806
|
+
Overall: PASS / FAIL
|
|
807
|
+
Learning recorded: <learning-id>
|
|
808
|
+
\`\`\`
|
|
809
|
+
|
|
810
|
+
**Anti-pattern:** Do not call work "done" without running verification. Do not skip verification because tests are slow; offer to run a subset if full suite is impractical.
|
|
811
|
+
|
|
812
|
+
## Workflow 4: \`ground-truth check\`
|
|
813
|
+
|
|
814
|
+
**Trigger phrases:** "pulse check," "quick check," "how are we doing," "anything I'm missing"
|
|
815
|
+
|
|
816
|
+
**What it does:** Lightweight mid-session check. Pulls \`vreko_pulse\` and annotates for the user what to be aware of for the next several turns.
|
|
817
|
+
|
|
818
|
+
**Steps:**
|
|
819
|
+
1. Call \`vreko_pulse\`.
|
|
820
|
+
2. Read the \`LLM_HINT\` section of the response (see spec 03).
|
|
821
|
+
3. Summarize in 2-3 sentences what the user should be aware of right now.
|
|
822
|
+
4. If pulse returns warnings the user hasn't acknowledged, surface them.
|
|
823
|
+
|
|
824
|
+
**Output format:** Brief prose, no tables. Example: "Pulse check: you're in \`auth/session.ts\`, which was rolled back 4\xD7 in the last 90 days. Co-change partner \`auth/middleware.ts\` hasn't been touched yet - if you're refactoring session logic, middleware probably needs coordination. No other warnings."
|
|
825
|
+
|
|
826
|
+
**Anti-pattern:** Do not repeat the same warning on consecutive checks. Track what was surfaced and only re-surface if state has changed.
|
|
827
|
+
|
|
828
|
+
## Workflow 5: \`ground-truth close\`
|
|
829
|
+
|
|
830
|
+
**Trigger phrases:** "close the session," "we're done here," "wrap up," "ceremony"
|
|
831
|
+
|
|
832
|
+
**What it does:** Closes the Vreko session, reads the closing ceremony, presents the summary.
|
|
833
|
+
|
|
834
|
+
**Steps:**
|
|
835
|
+
1. Call \`vreko_end\` with a one-sentence outcome summary of what was accomplished.
|
|
836
|
+
2. Parse the ceremony response: learnings captured, pitfalls avoided, estimated token savings, session coherence score, carry-forward items.
|
|
837
|
+
3. Present the summary to the user in a compact format.
|
|
838
|
+
4. If there are carry-forward items, explicitly flag them for the next session.
|
|
839
|
+
|
|
840
|
+
**Output format:**
|
|
841
|
+
\`\`\`
|
|
842
|
+
Session closed: <session-id>
|
|
843
|
+
Outcome: <one sentence>
|
|
844
|
+
|
|
845
|
+
Ceremony summary:
|
|
846
|
+
Learnings captured: <n>
|
|
847
|
+
Pitfalls avoided: <n>
|
|
848
|
+
Token savings (est): ~<n>
|
|
849
|
+
Coherence score: <n>%
|
|
850
|
+
|
|
851
|
+
Carry forward to next session:
|
|
852
|
+
\u2022 <item>
|
|
853
|
+
\u2022 <item>
|
|
854
|
+
\`\`\`
|
|
855
|
+
|
|
856
|
+
**Anti-pattern:** Do not skip the closing ceremony just because the session was short. Short sessions often have the most valuable learnings.
|
|
857
|
+
|
|
858
|
+
## How workflows compose
|
|
859
|
+
|
|
860
|
+
A typical disciplined coding session looks like:
|
|
861
|
+
|
|
862
|
+
1. User states intent \u2192 Claude runs \`ground-truth brief\`.
|
|
863
|
+
2. Before significant changes \u2192 Claude runs \`ground-truth audit\` on the scope.
|
|
864
|
+
3. Mid-session, at file-change boundaries \u2192 Claude runs \`ground-truth check\`.
|
|
865
|
+
4. After implementation \u2192 Claude runs \`ground-truth verify\`.
|
|
866
|
+
5. At session end \u2192 Claude runs \`ground-truth close\`.
|
|
867
|
+
|
|
868
|
+
The user doesn't need to type these verbatim. The skill recognizes intent from natural phrasing.
|
|
869
|
+
|
|
870
|
+
## What this skill does NOT do
|
|
871
|
+
|
|
872
|
+
- Does not replace the user's judgment. If verification passes but something still feels wrong, stop and investigate.
|
|
873
|
+
- Does not auto-install Vreko. Always asks.
|
|
874
|
+
- Does not expose MCP tools directly to the user. The user asks for ground truth; the skill orchestrates the tools.
|
|
875
|
+
- Does not write to \`.vreko/docs/\` directly. That's the service's job.
|
|
876
|
+
|
|
877
|
+
## Integration with other Vreko skills
|
|
878
|
+
|
|
879
|
+
- \`vreko-ground-truth-audit\` (published) - generates grep-based audit commands; Ground Truth's \`audit\` workflow uses its patterns.
|
|
880
|
+
- \`vreko-brand-voice\` (published) - use when Ground Truth output will surface in external communication.
|
|
881
|
+
- \`vreko-spec-writer\` (published) - use when Ground Truth discovers work that warrants a spec.
|
|
882
|
+
|
|
883
|
+
## Installation
|
|
884
|
+
|
|
885
|
+
\`\`\`bash
|
|
886
|
+
# Users install the skill file into their Claude Code skills directory:
|
|
887
|
+
mkdir -p ~/.claude/skills/ground-truth
|
|
888
|
+
curl -o ~/.claude/skills/ground-truth/SKILL.md \\
|
|
889
|
+
https://skills.vreko.dev/ground-truth/SKILL.md
|
|
890
|
+
\`\`\`
|
|
891
|
+
|
|
892
|
+
Or, bundled: Ground Truth ships as the headline skill inside \`@vreko/skills\` npm package, which \`vreko init\` installs into the appropriate directory per detected AI tool.
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
**Version:** 1.0.0
|
|
897
|
+
**Author:** Vreko / Marcelle Labs
|
|
898
|
+
**License:** MIT (methodology); skill file free to distribute
|
|
899
|
+
**Canonical URL:** https://vreko.dev/ground-truth
|
|
900
|
+
`;
|
|
901
|
+
var cliVersion = "3.0.1" ;
|
|
902
|
+
function createInitCommand() {
|
|
903
|
+
return new Command("init").description("Bootstrap Vreko for a repository").argument("[path]", "Workspace path (default: current directory)").option("-y, --yes", "Skip confirmation prompts").option("--non-interactive", "Run without prompts (extension/automation)").option("--json", "Output structured JSON result").option("--dry-run", "Show what would be configured").option("--force", "Re-initialize even if already set up").option("--skip-mcp", "Skip MCP configuration").option("--skip-service", "Skip service registration").option("--api-key <key>", "API key for Pro features").option("--dev", "Use local dev mode for MCP").option("--npm", "Use npm/npx mode for MCP").option("-q, --quiet", "Suppress informational output").option("-v, --verbose", "Show detailed detection reasoning").action(async (path, options) => {
|
|
904
|
+
const result = await runInit(path, options);
|
|
905
|
+
if (options.json) {
|
|
906
|
+
console.log(JSON.stringify(result, null, 2));
|
|
907
|
+
}
|
|
908
|
+
if (!result.success) {
|
|
909
|
+
process.exit(1);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
__name(createInitCommand, "createInitCommand");
|
|
914
|
+
async function runInit(pathArg, options) {
|
|
915
|
+
const jsonMode = !!options.json;
|
|
916
|
+
const skipPrompts = !!options.yes || !!options.nonInteractive || cliState.yes;
|
|
917
|
+
const quiet = !!options.quiet;
|
|
918
|
+
const verbose = !!options.verbose;
|
|
919
|
+
const result = {
|
|
920
|
+
success: true,
|
|
921
|
+
version: cliVersion,
|
|
922
|
+
workspace: {
|
|
923
|
+
path: "",
|
|
924
|
+
alreadyInitialized: false,
|
|
925
|
+
reinitialized: false
|
|
926
|
+
},
|
|
927
|
+
detection: {
|
|
928
|
+
stack: [],
|
|
929
|
+
monorepoType: "none",
|
|
930
|
+
packageManager: "npm",
|
|
931
|
+
gitRepo: false,
|
|
932
|
+
gitRoot: null,
|
|
933
|
+
fileCount: 0
|
|
934
|
+
},
|
|
935
|
+
configuration: {
|
|
936
|
+
configCreated: false,
|
|
937
|
+
gitignoreUpdated: false,
|
|
938
|
+
ctxStubCreated: false
|
|
939
|
+
},
|
|
940
|
+
service: {
|
|
941
|
+
started: false,
|
|
942
|
+
connected: false,
|
|
943
|
+
workspaceRegistered: false,
|
|
944
|
+
version: null,
|
|
945
|
+
skipped: !!options.skipService
|
|
946
|
+
},
|
|
947
|
+
mcp: {
|
|
948
|
+
clients: {},
|
|
949
|
+
configured: [],
|
|
950
|
+
skipped: !!options.skipMcp
|
|
951
|
+
},
|
|
952
|
+
errors: []
|
|
953
|
+
};
|
|
954
|
+
try {
|
|
955
|
+
const spinner = jsonMode ? null : ora("Detecting workspace...").start();
|
|
956
|
+
const workspacePath = resolve(pathArg || process.cwd());
|
|
957
|
+
result.workspace.path = workspacePath;
|
|
958
|
+
if (!existsSync(workspacePath)) {
|
|
959
|
+
fail(spinner, result, `Path does not exist: ${workspacePath}`);
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
if (!statSync(workspacePath).isDirectory()) {
|
|
963
|
+
fail(spinner, result, `Path is not a directory: ${workspacePath}`);
|
|
964
|
+
return result;
|
|
965
|
+
}
|
|
966
|
+
const vrekoDir = join(workspacePath, ".vreko");
|
|
967
|
+
const configPath = join(vrekoDir, "config.json");
|
|
968
|
+
const alreadyInitialized = existsSync(configPath);
|
|
969
|
+
result.workspace.alreadyInitialized = alreadyInitialized;
|
|
970
|
+
if (alreadyInitialized && options.force) {
|
|
971
|
+
result.workspace.reinitialized = true;
|
|
972
|
+
}
|
|
973
|
+
const detected = detectStack(workspacePath, verbose && !jsonMode);
|
|
974
|
+
result.detection = {
|
|
975
|
+
...detected,
|
|
976
|
+
fileCount: 0
|
|
977
|
+
};
|
|
978
|
+
if (spinner) {
|
|
979
|
+
spinner.succeed("Workspace detected");
|
|
980
|
+
}
|
|
981
|
+
if (!jsonMode && !quiet) {
|
|
982
|
+
if (detected.stack.length > 0) {
|
|
983
|
+
}
|
|
984
|
+
if (detected.monorepoType !== "none") {
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (!jsonMode && !options.dryRun) {
|
|
988
|
+
if (options.nonInteractive) {
|
|
989
|
+
process.stderr.write([
|
|
990
|
+
"",
|
|
991
|
+
"Vreko data notice:",
|
|
992
|
+
" \u2022 Session metadata (timing, file counts, risk scores) is collected locally.",
|
|
993
|
+
" \u2022 No file contents or source code ever leave your device.",
|
|
994
|
+
" \u2022 Cloud sync is opt-in and syncs metadata only.",
|
|
995
|
+
" \u2022 Run `vreko purge` at any time to delete all local data.",
|
|
996
|
+
" \u2022 Privacy policy: https://vreko.dev/privacy",
|
|
997
|
+
""
|
|
998
|
+
].join("\n"));
|
|
999
|
+
} else if (!skipPrompts) {
|
|
1000
|
+
const consentResult = await clack.confirm({
|
|
1001
|
+
message: "Vreko collects session metadata (timing, file counts, risk scores) locally. No file contents leave your device. Agree to continue?",
|
|
1002
|
+
initialValue: true
|
|
1003
|
+
});
|
|
1004
|
+
if (clack.isCancel(consentResult)) {
|
|
1005
|
+
clack.cancel("Cancelled.");
|
|
1006
|
+
return result;
|
|
1007
|
+
}
|
|
1008
|
+
if (!consentResult) {
|
|
1009
|
+
clack.cancel("Setup cancelled. No data was collected.");
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!skipPrompts && !options.dryRun) {
|
|
1015
|
+
const proceedResult = await clack.confirm({
|
|
1016
|
+
message: `Initialize Vreko for ${basename(workspacePath)}?`,
|
|
1017
|
+
initialValue: true
|
|
1018
|
+
});
|
|
1019
|
+
if (clack.isCancel(proceedResult)) {
|
|
1020
|
+
clack.cancel("Cancelled.");
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
if (!proceedResult) {
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
const configSpinner = jsonMode ? null : ora("Configuring workspace...").start();
|
|
1028
|
+
if (!options.dryRun) {
|
|
1029
|
+
mkdirSync(vrekoDir, {
|
|
1030
|
+
recursive: true
|
|
1031
|
+
});
|
|
1032
|
+
if (!alreadyInitialized || options.force) {
|
|
1033
|
+
const config = buildWorkspaceConfig(workspacePath, detected);
|
|
1034
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1035
|
+
`);
|
|
1036
|
+
result.configuration.configCreated = true;
|
|
1037
|
+
}
|
|
1038
|
+
if (configSpinner) {
|
|
1039
|
+
configSpinner.succeed("Created .vreko/config.json");
|
|
1040
|
+
}
|
|
1041
|
+
if (detected.gitRepo) {
|
|
1042
|
+
const gitignoreUpdated = ensureGitignore(workspacePath);
|
|
1043
|
+
result.configuration.gitignoreUpdated = gitignoreUpdated;
|
|
1044
|
+
if (!jsonMode && gitignoreUpdated) {
|
|
1045
|
+
ora().succeed("Updated .gitignore");
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const ctxPath = join(vrekoDir, ".ctx");
|
|
1049
|
+
if (!existsSync(ctxPath)) {
|
|
1050
|
+
writeFileSync(ctxPath, "# Vreko compiled context - auto-generated\n");
|
|
1051
|
+
result.configuration.ctxStubCreated = true;
|
|
1052
|
+
if (!jsonMode) {
|
|
1053
|
+
ora().succeed("Created .vreko/.ctx");
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
if (configSpinner) {
|
|
1058
|
+
configSpinner.info("Would create .vreko/config.json");
|
|
1059
|
+
}
|
|
1060
|
+
if (detected.gitRepo && !jsonMode) {
|
|
1061
|
+
ora().info("Would update .gitignore");
|
|
1062
|
+
}
|
|
1063
|
+
if (!jsonMode) {
|
|
1064
|
+
ora().info("Would create .vreko/.ctx");
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (!jsonMode) {
|
|
1068
|
+
}
|
|
1069
|
+
if (!options.skipService) {
|
|
1070
|
+
const daemonSpinner = jsonMode ? null : ora("Connecting to service...").start();
|
|
1071
|
+
if (!options.dryRun) {
|
|
1072
|
+
const daemonResult = await registerWithDaemon(workspacePath, daemonSpinner, jsonMode);
|
|
1073
|
+
result.service = {
|
|
1074
|
+
...result.service,
|
|
1075
|
+
...daemonResult
|
|
1076
|
+
};
|
|
1077
|
+
if (!daemonResult.connected) {
|
|
1078
|
+
const daemonErrorMsg = "\u26A0 Service could not be reached - Vreko is initialized but workspace sync is disabled. Run `vr service start` to enable syncing." + (daemonResult.errorMessage ? ` ${daemonResult.errorMessage}` : "");
|
|
1079
|
+
if (!jsonMode) {
|
|
1080
|
+
console.warn(daemonErrorMsg);
|
|
1081
|
+
}
|
|
1082
|
+
result.errors.push("Service connection failed - init completed without service registration");
|
|
1083
|
+
} else {
|
|
1084
|
+
try {
|
|
1085
|
+
const client = getDaemonClient();
|
|
1086
|
+
if (client) {
|
|
1087
|
+
const seedResult = await client.call("workspace/seed-knowledge", {
|
|
1088
|
+
workspace: workspacePath
|
|
1089
|
+
});
|
|
1090
|
+
if (!jsonMode && seedResult.seeded > 0) {
|
|
1091
|
+
ora().succeed(`Seeded ${seedResult.seeded} intelligence patterns into workspace`);
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
await client.call("workspace/trigger-workspace-json-write", {
|
|
1095
|
+
workspace: workspacePath
|
|
1096
|
+
});
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
console.warn("init-core: workspace/trigger-workspace-json-write failed (non-fatal)", {
|
|
1099
|
+
error
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
await client.call("workspace/write-from-scan-profile", {
|
|
1104
|
+
workspace: workspacePath,
|
|
1105
|
+
...options.force && {
|
|
1106
|
+
force: true
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.warn("init-core: workspace/write-from-scan-profile failed (non-fatal)", {
|
|
1111
|
+
error
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
console.warn("init-core: knowledge seeding failed (non-fatal)", {
|
|
1117
|
+
error
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
} else {
|
|
1122
|
+
if (daemonSpinner) {
|
|
1123
|
+
daemonSpinner.info("Would register workspace with service");
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (!jsonMode) {
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (!options.skipMcp) {
|
|
1130
|
+
const mcpSpinner = jsonMode ? null : ora("Configuring AI tools...").start();
|
|
1131
|
+
if (!options.dryRun) {
|
|
1132
|
+
const mcpResult = await configureMCP(workspacePath, options.apiKey, options.dev, options.npm, skipPrompts, jsonMode, mcpSpinner);
|
|
1133
|
+
result.mcp = {
|
|
1134
|
+
...result.mcp,
|
|
1135
|
+
...mcpResult
|
|
1136
|
+
};
|
|
1137
|
+
} else {
|
|
1138
|
+
if (mcpSpinner) {
|
|
1139
|
+
mcpSpinner.info("Would configure detected AI tools");
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (!jsonMode) {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (!options.dryRun) {
|
|
1146
|
+
const claudeSpinner = jsonMode ? null : ora("Generating Claude Code integration...").start();
|
|
1147
|
+
try {
|
|
1148
|
+
let baselineData;
|
|
1149
|
+
if (result.service.connected) {
|
|
1150
|
+
try {
|
|
1151
|
+
const client = getDaemonClient();
|
|
1152
|
+
const record = await client.call("baseline/get", {
|
|
1153
|
+
workspace: workspacePath
|
|
1154
|
+
});
|
|
1155
|
+
if (record?.fragileFiles !== void 0 || record?.coChangeClusters !== void 0) {
|
|
1156
|
+
baselineData = {
|
|
1157
|
+
fragileFiles: record.fragileFiles,
|
|
1158
|
+
coChangeClusters: record.coChangeClusters
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const claudeResult = generateClaudeIntegration({
|
|
1165
|
+
workspacePath,
|
|
1166
|
+
overwrite: false,
|
|
1167
|
+
includeChannel: false
|
|
1168
|
+
}, baselineData);
|
|
1169
|
+
if (claudeResult.filesWritten.length > 0) {
|
|
1170
|
+
if (claudeSpinner) {
|
|
1171
|
+
claudeSpinner.succeed(`Generated Claude Code integration (${claudeResult.filesWritten.length} files)`);
|
|
1172
|
+
}
|
|
1173
|
+
if (!jsonMode && !quiet) {
|
|
1174
|
+
for (const _f of claudeResult.filesWritten) {
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
} else {
|
|
1178
|
+
if (claudeSpinner) {
|
|
1179
|
+
claudeSpinner.info("Claude Code integration already present");
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} catch {
|
|
1183
|
+
if (claudeSpinner) {
|
|
1184
|
+
claudeSpinner.warn("Claude Code integration skipped (non-fatal)");
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (!jsonMode) {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (!options.dryRun && !skipPrompts) {
|
|
1191
|
+
const claudeDir = join(homedir(), ".claude");
|
|
1192
|
+
const hasClaudeCode = existsSync(claudeDir);
|
|
1193
|
+
if (hasClaudeCode) {
|
|
1194
|
+
try {
|
|
1195
|
+
const skillDir = join(homedir(), ".claude", "skills", "ground-truth");
|
|
1196
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
1197
|
+
if (existsSync(skillPath)) {
|
|
1198
|
+
if (!jsonMode) {
|
|
1199
|
+
ora().info("Ground Truth skill already installed");
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
const installSkill = await clack.confirm({
|
|
1203
|
+
message: "Install the Ground Truth skill for Claude Code? (Recommended - helps Claude work effectively in this codebase)",
|
|
1204
|
+
initialValue: true
|
|
1205
|
+
});
|
|
1206
|
+
if (!clack.isCancel(installSkill) && installSkill === true) {
|
|
1207
|
+
mkdirSync(skillDir, {
|
|
1208
|
+
recursive: true
|
|
1209
|
+
});
|
|
1210
|
+
writeFileSync(skillPath, GROUND_TRUTH_SKILL_CONTENT, "utf8");
|
|
1211
|
+
if (!jsonMode) {
|
|
1212
|
+
ora().succeed(`Ground Truth skill installed at ${skillPath}`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
} catch (skillError) {
|
|
1217
|
+
const skillMessage = skillError instanceof Error ? skillError.message : String(skillError);
|
|
1218
|
+
if (!jsonMode) {
|
|
1219
|
+
ora().warn(`Ground Truth skill install failed (non-fatal): ${skillMessage}`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (!options.dryRun) {
|
|
1225
|
+
try {
|
|
1226
|
+
await installHook("claude-code", workspacePath);
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
if (!jsonMode && !quiet) {
|
|
1229
|
+
console.warn(`[vr init] Hook installation skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (!options.dryRun) {
|
|
1234
|
+
const fileCount = await countSourceFiles(workspacePath);
|
|
1235
|
+
result.detection.fileCount = fileCount;
|
|
1236
|
+
if (!jsonMode && !quiet) {
|
|
1237
|
+
const configuredTools = result.mcp.configured;
|
|
1238
|
+
const toolList = configuredTools.length > 0 ? configuredTools.join(", ") : null;
|
|
1239
|
+
const supervisorOk = result.service.supervisorInstalled !== false;
|
|
1240
|
+
console.log();
|
|
1241
|
+
console.log(chalk.green(" \u2713 Workspace registered"));
|
|
1242
|
+
if (supervisorOk) {
|
|
1243
|
+
console.log(chalk.green(" \u2713 Daemon running (supervised)"));
|
|
1244
|
+
} else {
|
|
1245
|
+
console.log(chalk.yellow(" \u26A0 Supervisor not installed (you may need to run vreko service install)"));
|
|
1246
|
+
console.log(chalk.green(" \u2713 Daemon running (this session only - will not restart on crash)"));
|
|
1247
|
+
}
|
|
1248
|
+
if (toolList) {
|
|
1249
|
+
console.log(chalk.green(` \u2713 MCP config written to ${toolList}`));
|
|
1250
|
+
}
|
|
1251
|
+
console.log();
|
|
1252
|
+
console.log(chalk.cyan(" \u2192 Restart Claude Desktop once to load vreko."));
|
|
1253
|
+
console.log(chalk.cyan(" You won't need to do this again."));
|
|
1254
|
+
console.log();
|
|
1255
|
+
if (supervisorOk) {
|
|
1256
|
+
console.log(chalk.white(" vreko is now ready. Try asking your AI agent about this codebase."));
|
|
1257
|
+
} else {
|
|
1258
|
+
console.log(chalk.yellow(" vreko is running but unsupervised. Run `vr doctor` if you hit issues."));
|
|
1259
|
+
}
|
|
1260
|
+
console.log();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
const authStatus = checkAuthToken();
|
|
1264
|
+
if (!authStatus.hasToken && !jsonMode) {
|
|
1265
|
+
console.warn("\n\u26A0\uFE0F Sync is configured but no API key was found.");
|
|
1266
|
+
console.warn(" Workspace metadata will not sync to the intelligence platform.");
|
|
1267
|
+
console.warn(" Run `vreko login` or set VREKO_API_KEY.\n");
|
|
1268
|
+
}
|
|
1269
|
+
return result;
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1272
|
+
result.success = false;
|
|
1273
|
+
result.error = message;
|
|
1274
|
+
return result;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
__name(runInit, "runInit");
|
|
1278
|
+
function detectStack(workspacePath, verbose) {
|
|
1279
|
+
const stack = [];
|
|
1280
|
+
const result = {
|
|
1281
|
+
stack,
|
|
1282
|
+
monorepoType: "none",
|
|
1283
|
+
packageManager: "npm",
|
|
1284
|
+
gitRepo: false,
|
|
1285
|
+
gitRoot: null
|
|
1286
|
+
};
|
|
1287
|
+
const gitRoot = findGitRoot(workspacePath);
|
|
1288
|
+
if (gitRoot) {
|
|
1289
|
+
result.gitRepo = true;
|
|
1290
|
+
result.gitRoot = gitRoot;
|
|
1291
|
+
}
|
|
1292
|
+
const lockfiles = [
|
|
1293
|
+
[
|
|
1294
|
+
"pnpm-lock.yaml",
|
|
1295
|
+
"pnpm"
|
|
1296
|
+
],
|
|
1297
|
+
[
|
|
1298
|
+
"bun.lockb",
|
|
1299
|
+
"bun"
|
|
1300
|
+
],
|
|
1301
|
+
[
|
|
1302
|
+
"yarn.lock",
|
|
1303
|
+
"yarn"
|
|
1304
|
+
],
|
|
1305
|
+
[
|
|
1306
|
+
"package-lock.json",
|
|
1307
|
+
"npm"
|
|
1308
|
+
]
|
|
1309
|
+
];
|
|
1310
|
+
for (const [file, pm] of lockfiles) {
|
|
1311
|
+
if (existsSync(join(workspacePath, file))) {
|
|
1312
|
+
result.packageManager = pm;
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const signals = [
|
|
1317
|
+
// [file/pattern, stack label, verbose detail]
|
|
1318
|
+
[
|
|
1319
|
+
"next.config.js",
|
|
1320
|
+
"Next.js"
|
|
1321
|
+
],
|
|
1322
|
+
[
|
|
1323
|
+
"next.config.ts",
|
|
1324
|
+
"Next.js"
|
|
1325
|
+
],
|
|
1326
|
+
[
|
|
1327
|
+
"next.config.mjs",
|
|
1328
|
+
"Next.js"
|
|
1329
|
+
],
|
|
1330
|
+
[
|
|
1331
|
+
"nuxt.config.ts",
|
|
1332
|
+
"Nuxt"
|
|
1333
|
+
],
|
|
1334
|
+
[
|
|
1335
|
+
"svelte.config.js",
|
|
1336
|
+
"SvelteKit"
|
|
1337
|
+
],
|
|
1338
|
+
[
|
|
1339
|
+
"astro.config.mjs",
|
|
1340
|
+
"Astro"
|
|
1341
|
+
],
|
|
1342
|
+
[
|
|
1343
|
+
"remix.config.js",
|
|
1344
|
+
"Remix"
|
|
1345
|
+
],
|
|
1346
|
+
[
|
|
1347
|
+
"tsconfig.json",
|
|
1348
|
+
"TypeScript"
|
|
1349
|
+
],
|
|
1350
|
+
[
|
|
1351
|
+
"Cargo.toml",
|
|
1352
|
+
"Rust"
|
|
1353
|
+
],
|
|
1354
|
+
[
|
|
1355
|
+
"go.mod",
|
|
1356
|
+
"Go"
|
|
1357
|
+
],
|
|
1358
|
+
[
|
|
1359
|
+
"pyproject.toml",
|
|
1360
|
+
"Python"
|
|
1361
|
+
],
|
|
1362
|
+
[
|
|
1363
|
+
"requirements.txt",
|
|
1364
|
+
"Python"
|
|
1365
|
+
],
|
|
1366
|
+
[
|
|
1367
|
+
"Gemfile",
|
|
1368
|
+
"Ruby"
|
|
1369
|
+
],
|
|
1370
|
+
[
|
|
1371
|
+
"composer.json",
|
|
1372
|
+
"PHP"
|
|
1373
|
+
],
|
|
1374
|
+
[
|
|
1375
|
+
"Package.swift",
|
|
1376
|
+
"Swift"
|
|
1377
|
+
],
|
|
1378
|
+
[
|
|
1379
|
+
".env",
|
|
1380
|
+
"env-config"
|
|
1381
|
+
]
|
|
1382
|
+
];
|
|
1383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1384
|
+
for (const [file, label] of signals) {
|
|
1385
|
+
if (!seen.has(label) && existsSync(join(workspacePath, file))) {
|
|
1386
|
+
stack.push(label);
|
|
1387
|
+
seen.add(label);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
const pkgPath = join(workspacePath, "package.json");
|
|
1391
|
+
if (existsSync(pkgPath)) {
|
|
1392
|
+
try {
|
|
1393
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1394
|
+
const allDeps = {
|
|
1395
|
+
...pkg.dependencies || {},
|
|
1396
|
+
...pkg.devDependencies || {}
|
|
1397
|
+
};
|
|
1398
|
+
const depSignals = [
|
|
1399
|
+
[
|
|
1400
|
+
"drizzle-orm",
|
|
1401
|
+
"Drizzle"
|
|
1402
|
+
],
|
|
1403
|
+
[
|
|
1404
|
+
"prisma",
|
|
1405
|
+
"Prisma"
|
|
1406
|
+
],
|
|
1407
|
+
[
|
|
1408
|
+
"pg",
|
|
1409
|
+
"PostgreSQL"
|
|
1410
|
+
],
|
|
1411
|
+
[
|
|
1412
|
+
"mysql2",
|
|
1413
|
+
"MySQL"
|
|
1414
|
+
],
|
|
1415
|
+
[
|
|
1416
|
+
"better-sqlite3",
|
|
1417
|
+
"SQLite"
|
|
1418
|
+
],
|
|
1419
|
+
[
|
|
1420
|
+
"mongoose",
|
|
1421
|
+
"MongoDB"
|
|
1422
|
+
],
|
|
1423
|
+
[
|
|
1424
|
+
"redis",
|
|
1425
|
+
"Redis"
|
|
1426
|
+
],
|
|
1427
|
+
[
|
|
1428
|
+
"tailwindcss",
|
|
1429
|
+
"Tailwind"
|
|
1430
|
+
],
|
|
1431
|
+
[
|
|
1432
|
+
"@trpc/server",
|
|
1433
|
+
"tRPC"
|
|
1434
|
+
],
|
|
1435
|
+
[
|
|
1436
|
+
"express",
|
|
1437
|
+
"Express"
|
|
1438
|
+
],
|
|
1439
|
+
[
|
|
1440
|
+
"fastify",
|
|
1441
|
+
"Fastify"
|
|
1442
|
+
],
|
|
1443
|
+
[
|
|
1444
|
+
"hono",
|
|
1445
|
+
"Hono"
|
|
1446
|
+
]
|
|
1447
|
+
];
|
|
1448
|
+
for (const [dep, label] of depSignals) {
|
|
1449
|
+
if (!seen.has(label) && allDeps[dep]) {
|
|
1450
|
+
stack.push(label);
|
|
1451
|
+
seen.add(label);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (existsSync(join(workspacePath, "turbo.json"))) {
|
|
1458
|
+
result.monorepoType = "turborepo";
|
|
1459
|
+
} else if (existsSync(join(workspacePath, "nx.json"))) {
|
|
1460
|
+
result.monorepoType = "nx";
|
|
1461
|
+
} else if (existsSync(join(workspacePath, "lerna.json"))) {
|
|
1462
|
+
result.monorepoType = "lerna";
|
|
1463
|
+
} else if (existsSync(join(workspacePath, "pnpm-workspace.yaml"))) {
|
|
1464
|
+
result.monorepoType = "pnpm";
|
|
1465
|
+
}
|
|
1466
|
+
return result;
|
|
1467
|
+
}
|
|
1468
|
+
__name(detectStack, "detectStack");
|
|
1469
|
+
function buildWorkspaceConfig(workspacePath, detected) {
|
|
1470
|
+
return {
|
|
1471
|
+
version: 1,
|
|
1472
|
+
workspace: {
|
|
1473
|
+
path: workspacePath,
|
|
1474
|
+
name: basename(workspacePath),
|
|
1475
|
+
stack: detected.stack,
|
|
1476
|
+
monorepoType: detected.monorepoType,
|
|
1477
|
+
packageManager: detected.packageManager
|
|
1478
|
+
},
|
|
1479
|
+
protection: {
|
|
1480
|
+
mode: "auto",
|
|
1481
|
+
level: "standard"
|
|
1482
|
+
},
|
|
1483
|
+
intelligence: {
|
|
1484
|
+
enabled: true
|
|
1485
|
+
},
|
|
1486
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1487
|
+
cliVersion
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
__name(buildWorkspaceConfig, "buildWorkspaceConfig");
|
|
1491
|
+
function ensureGitignore(workspacePath) {
|
|
1492
|
+
const gitignorePath = join(workspacePath, ".gitignore");
|
|
1493
|
+
const entry = ".vreko/";
|
|
1494
|
+
if (existsSync(gitignorePath)) {
|
|
1495
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
1496
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
1497
|
+
if (lines.includes(entry) || lines.includes(".vreko")) {
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
1501
|
+
appendFileSync(gitignorePath, `${suffix}
|
|
1502
|
+
# Vreko local data
|
|
1503
|
+
${entry}
|
|
1504
|
+
`);
|
|
1505
|
+
return true;
|
|
1506
|
+
}
|
|
1507
|
+
writeFileSync(gitignorePath, `# Vreko local data
|
|
1508
|
+
${entry}
|
|
1509
|
+
`);
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
__name(ensureGitignore, "ensureGitignore");
|
|
1513
|
+
async function startAndConnectDaemon(result, spinner) {
|
|
1514
|
+
try {
|
|
1515
|
+
const client = await connectToDaemon();
|
|
1516
|
+
const health = await client.health.check();
|
|
1517
|
+
result.version = health?.version ?? null;
|
|
1518
|
+
if (health?.supervisorMode === "extension") {
|
|
1519
|
+
try {
|
|
1520
|
+
execFileSync("vreko", [
|
|
1521
|
+
"service",
|
|
1522
|
+
"install"
|
|
1523
|
+
], {
|
|
1524
|
+
stdio: "pipe",
|
|
1525
|
+
timeout: 15e3
|
|
1526
|
+
});
|
|
1527
|
+
result.supervisorInstalled = true;
|
|
1528
|
+
} catch {
|
|
1529
|
+
result.supervisorInstalled = false;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return {
|
|
1533
|
+
connected: true
|
|
1534
|
+
};
|
|
1535
|
+
} catch {
|
|
1536
|
+
if (spinner) spinner.text = "Starting service...";
|
|
1537
|
+
try {
|
|
1538
|
+
execFileSync("vreko", [
|
|
1539
|
+
"service",
|
|
1540
|
+
"start",
|
|
1541
|
+
"-d"
|
|
1542
|
+
], {
|
|
1543
|
+
stdio: "pipe",
|
|
1544
|
+
timeout: 1e4
|
|
1545
|
+
});
|
|
1546
|
+
result.started = true;
|
|
1547
|
+
const maxWait = 5e3;
|
|
1548
|
+
const interval = 200;
|
|
1549
|
+
let waited = 0;
|
|
1550
|
+
while (waited < maxWait) {
|
|
1551
|
+
try {
|
|
1552
|
+
const client = await connectToDaemon();
|
|
1553
|
+
const health = await client.health.check();
|
|
1554
|
+
result.version = health?.version ?? null;
|
|
1555
|
+
return {
|
|
1556
|
+
connected: true
|
|
1557
|
+
};
|
|
1558
|
+
} catch {
|
|
1559
|
+
await sleep(interval);
|
|
1560
|
+
waited += interval;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
connected: false
|
|
1565
|
+
};
|
|
1566
|
+
} catch (_startError) {
|
|
1567
|
+
result.errorMessage = _startError instanceof Error ? _startError.message : String(_startError);
|
|
1568
|
+
if (spinner) spinner.warn("Could not start service (init will continue without it)");
|
|
1569
|
+
return {
|
|
1570
|
+
connected: false
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
__name(startAndConnectDaemon, "startAndConnectDaemon");
|
|
1576
|
+
async function registerWorkspaceWithDaemon(workspacePath, spinner, jsonMode, result) {
|
|
1577
|
+
try {
|
|
1578
|
+
const client = getDaemonClient();
|
|
1579
|
+
if (client) {
|
|
1580
|
+
await client.call("workspace/init", {
|
|
1581
|
+
workspace: workspacePath
|
|
1582
|
+
});
|
|
1583
|
+
result.workspaceRegistered = true;
|
|
1584
|
+
if (!jsonMode && spinner) ora().succeed("Workspace registered");
|
|
1585
|
+
}
|
|
1586
|
+
} catch (_regError) {
|
|
1587
|
+
result.workspaceRegistered = false;
|
|
1588
|
+
if (!jsonMode) ora().warn("Workspace registration failed (service may need restart)");
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
__name(registerWorkspaceWithDaemon, "registerWorkspaceWithDaemon");
|
|
1592
|
+
async function registerWithDaemon(_workspacePath, spinner, jsonMode) {
|
|
1593
|
+
const result = {
|
|
1594
|
+
started: false,
|
|
1595
|
+
connected: false,
|
|
1596
|
+
workspaceRegistered: false,
|
|
1597
|
+
version: null
|
|
1598
|
+
};
|
|
1599
|
+
try {
|
|
1600
|
+
const { connected } = await startAndConnectDaemon(result, spinner);
|
|
1601
|
+
if (!connected) {
|
|
1602
|
+
if (!result.errorMessage) {
|
|
1603
|
+
result.errorMessage = "Service did not respond after start";
|
|
1604
|
+
if (spinner) spinner.warn("Service did not respond (init will continue without it)");
|
|
1605
|
+
}
|
|
1606
|
+
return result;
|
|
1607
|
+
}
|
|
1608
|
+
result.connected = true;
|
|
1609
|
+
if (spinner) {
|
|
1610
|
+
spinner.succeed(`Service running (v${result.version ?? "unknown"}, pid ${await getDaemonPid()})`);
|
|
1611
|
+
}
|
|
1612
|
+
await registerWorkspaceWithDaemon(_workspacePath, spinner, jsonMode, result);
|
|
1613
|
+
return result;
|
|
1614
|
+
} catch (_error) {
|
|
1615
|
+
result.errorMessage = _error instanceof Error ? _error.message : String(_error);
|
|
1616
|
+
if (spinner) spinner.warn("Service connection failed");
|
|
1617
|
+
return result;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
__name(registerWithDaemon, "registerWithDaemon");
|
|
1621
|
+
async function getDaemonPid() {
|
|
1622
|
+
const pidPath = getServicePidPath();
|
|
1623
|
+
try {
|
|
1624
|
+
return readFileSync(pidPath, "utf-8").trim();
|
|
1625
|
+
} catch {
|
|
1626
|
+
return "?";
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
__name(getDaemonPid, "getDaemonPid");
|
|
1630
|
+
async function configureMCP(workspacePath, apiKey, devMode, npmMode, _skipPrompts, jsonMode, spinner) {
|
|
1631
|
+
const result = {
|
|
1632
|
+
clients: {},
|
|
1633
|
+
configured: []
|
|
1634
|
+
};
|
|
1635
|
+
try {
|
|
1636
|
+
const detection = detectAIClients({
|
|
1637
|
+
cwd: workspacePath
|
|
1638
|
+
});
|
|
1639
|
+
if (detection.detected.length === 0) {
|
|
1640
|
+
if (spinner) {
|
|
1641
|
+
spinner.info("No AI tools detected (MCP configuration skipped)");
|
|
1642
|
+
}
|
|
1643
|
+
return result;
|
|
1644
|
+
}
|
|
1645
|
+
if (spinner) {
|
|
1646
|
+
spinner.stop();
|
|
1647
|
+
}
|
|
1648
|
+
for (const client of detection.needsSetup) {
|
|
1649
|
+
const clientSpinner = jsonMode ? null : ora(` ${client.displayName}...`).start();
|
|
1650
|
+
if (client.hasVreko) {
|
|
1651
|
+
result.clients[client.name] = "already_configured";
|
|
1652
|
+
if (clientSpinner) {
|
|
1653
|
+
clientSpinner.succeed(`${client.displayName} already configured`);
|
|
1654
|
+
}
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
try {
|
|
1658
|
+
const mcpConfig = getVrekoMCPConfig({
|
|
1659
|
+
apiKey,
|
|
1660
|
+
useNpx: !!npmMode,
|
|
1661
|
+
useLocalDev: !!devMode,
|
|
1662
|
+
workspaceRoot: workspacePath,
|
|
1663
|
+
client: client.format
|
|
1664
|
+
});
|
|
1665
|
+
const writeResult = writeClientConfig(client, mcpConfig);
|
|
1666
|
+
if (writeResult.success) {
|
|
1667
|
+
result.clients[client.name] = "configured";
|
|
1668
|
+
result.configured.push(client.name);
|
|
1669
|
+
if (clientSpinner) {
|
|
1670
|
+
clientSpinner.succeed(`${client.displayName} configured`);
|
|
1671
|
+
}
|
|
1672
|
+
} else {
|
|
1673
|
+
result.clients[client.name] = "failed";
|
|
1674
|
+
if (clientSpinner) {
|
|
1675
|
+
clientSpinner.fail(`${client.displayName} failed: ${writeResult.error}`);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
} catch (_error) {
|
|
1679
|
+
result.clients[client.name] = "failed";
|
|
1680
|
+
if (clientSpinner) {
|
|
1681
|
+
clientSpinner.fail(`${client.displayName} failed`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
for (const client of detection.clients) {
|
|
1686
|
+
if (!client.exists && !jsonMode) {
|
|
1687
|
+
result.clients[client.name] = "not_installed";
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return result;
|
|
1691
|
+
} catch (_error) {
|
|
1692
|
+
if (spinner) {
|
|
1693
|
+
spinner.warn("MCP configuration failed (non-fatal)");
|
|
1694
|
+
}
|
|
1695
|
+
return result;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
__name(configureMCP, "configureMCP");
|
|
1699
|
+
async function countSourceFiles(workspacePath) {
|
|
1700
|
+
const extensions = /* @__PURE__ */ new Set([
|
|
1701
|
+
".ts",
|
|
1702
|
+
".tsx",
|
|
1703
|
+
".js",
|
|
1704
|
+
".jsx",
|
|
1705
|
+
".py",
|
|
1706
|
+
".rs",
|
|
1707
|
+
".go",
|
|
1708
|
+
".rb",
|
|
1709
|
+
".php",
|
|
1710
|
+
".swift",
|
|
1711
|
+
".java",
|
|
1712
|
+
".kt",
|
|
1713
|
+
".vue",
|
|
1714
|
+
".svelte"
|
|
1715
|
+
]);
|
|
1716
|
+
const skip = /* @__PURE__ */ new Set([
|
|
1717
|
+
"node_modules",
|
|
1718
|
+
".git",
|
|
1719
|
+
"dist",
|
|
1720
|
+
"build",
|
|
1721
|
+
".vreko",
|
|
1722
|
+
"target",
|
|
1723
|
+
"__pycache__"
|
|
1724
|
+
]);
|
|
1725
|
+
let count = 0;
|
|
1726
|
+
async function walk(dir) {
|
|
1727
|
+
try {
|
|
1728
|
+
const entries = await readdir(dir, {
|
|
1729
|
+
withFileTypes: true
|
|
1730
|
+
});
|
|
1731
|
+
for (const e of entries) {
|
|
1732
|
+
if (skip.has(e.name)) {
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (e.isDirectory()) {
|
|
1736
|
+
await walk(join(dir, e.name));
|
|
1737
|
+
} else if (extensions.has(extname(e.name))) {
|
|
1738
|
+
count++;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
__name(walk, "walk");
|
|
1745
|
+
await walk(workspacePath);
|
|
1746
|
+
return count;
|
|
1747
|
+
}
|
|
1748
|
+
__name(countSourceFiles, "countSourceFiles");
|
|
1749
|
+
function sleep(ms) {
|
|
1750
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1751
|
+
}
|
|
1752
|
+
__name(sleep, "sleep");
|
|
1753
|
+
function checkAuthToken() {
|
|
1754
|
+
if (process.env.VREKO_SERVICE_TOKEN) {
|
|
1755
|
+
return {
|
|
1756
|
+
hasToken: true,
|
|
1757
|
+
source: "env-service"
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
if (process.env.VREKO_API_KEY) {
|
|
1761
|
+
return {
|
|
1762
|
+
hasToken: true,
|
|
1763
|
+
source: "env-api"
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
try {
|
|
1767
|
+
const authPath = join(homedir(), ".vreko", "auth.json");
|
|
1768
|
+
if (existsSync(authPath)) {
|
|
1769
|
+
const authContent = readFileSync(authPath, "utf-8");
|
|
1770
|
+
const auth = JSON.parse(authContent);
|
|
1771
|
+
if (auth.token) {
|
|
1772
|
+
return {
|
|
1773
|
+
hasToken: true,
|
|
1774
|
+
source: "auth-file"
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
} catch {
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
hasToken: false,
|
|
1782
|
+
source: "none"
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
__name(checkAuthToken, "checkAuthToken");
|
|
1786
|
+
function fail(spinner, result, message) {
|
|
1787
|
+
result.success = false;
|
|
1788
|
+
result.error = message;
|
|
1789
|
+
if (spinner) {
|
|
1790
|
+
spinner.fail(message);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
__name(fail, "fail");
|
|
1794
|
+
|
|
1795
|
+
export { createHooksCommand, createInitCommand, findWorkspaceRoot, generateClaudeIntegration, resolveWorkspaceRoot };
|
|
1796
|
+
//# sourceMappingURL=chunk-QWZVCJII.js.map
|
|
1797
|
+
//# sourceMappingURL=chunk-QWZVCJII.js.map
|