vibe-coding-master 0.7.35 → 0.7.37
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 +12 -4
- package/dist/backend/adapters/claude-adapter.js +3 -2
- package/dist/backend/adapters/command-runner.js +1 -0
- package/dist/backend/cli/install-vcm-harness.js +17 -5
- package/dist/backend/server.js +1 -0
- package/dist/backend/services/ccr-integration-service.js +3 -1
- package/dist/backend/services/code-intelligence-service.js +253 -0
- package/dist/backend/services/harness-service.js +38 -15
- package/dist/backend/services/lsp-plugin.js +9 -0
- package/dist/backend/services/session-service.js +6 -2
- package/dist/backend/templates/harness/architect-agent.js +22 -2
- package/dist/backend/templates/harness/coder-agent.js +1 -0
- package/dist/backend/templates/harness/gate-review.js +8 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +1 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +25 -0
- package/dist/shared/types/session.js +2 -0
- package/dist-frontend/assets/{index-DN9kwoOt.css → index-CXSOe-NN.css} +1 -1
- package/dist-frontend/assets/{index-DhLRhgSV.js → index-VillnFRo.js} +34 -34
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +72 -0
- package/scripts/verify-package.mjs +1 -0
package/README.md
CHANGED
|
@@ -277,10 +277,10 @@ gateway from the native child process. Configure CCR without enabling its
|
|
|
277
277
|
global Claude Code or Claude App takeover if those clients should remain on
|
|
278
278
|
Anthropic.
|
|
279
279
|
|
|
280
|
-
CCR/GPT child processes use
|
|
281
|
-
VCM
|
|
282
|
-
|
|
283
|
-
context override.
|
|
280
|
+
CCR/GPT child processes use a hard context limit and auto-compaction window of
|
|
281
|
+
`258400` tokens. VCM sets Claude Code's proactive compaction threshold to `90%`
|
|
282
|
+
(about `232560` tokens), leaving headroom before the gateway limit. Native
|
|
283
|
+
Claude sessions receive no VCM-owned context override.
|
|
284
284
|
|
|
285
285
|
Resume keeps the provider recorded by the existing Session. Use Restart when
|
|
286
286
|
switching between a native Claude model and `GPT-5.6 Sol (CCR)`. If CCR is
|
|
@@ -455,6 +455,7 @@ Harness Studio is the UI for VCM harness maintenance.
|
|
|
455
455
|
Use it to:
|
|
456
456
|
|
|
457
457
|
- inspect fixed harness status
|
|
458
|
+
- inspect detected project languages and verified language-server readiness
|
|
458
459
|
- run bootstrap
|
|
459
460
|
- open Harness Engineer
|
|
460
461
|
- review harness files
|
|
@@ -466,6 +467,13 @@ Use it to:
|
|
|
466
467
|
- merge task harness commits back to the connected repository branch when
|
|
467
468
|
appropriate
|
|
468
469
|
|
|
470
|
+
VCM bundles the Claude Code LSP bridge and loads it for Architect, Coder, and
|
|
471
|
+
Reviewer sessions, including CCR launches. The project environment must still
|
|
472
|
+
provide the language server for each detected language: `rust-analyzer`,
|
|
473
|
+
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
474
|
+
`jdtls`. Harness Studio reports a missing executable or failed startup probe
|
|
475
|
+
with the backend diagnostic.
|
|
476
|
+
|
|
469
477
|
Harness Engineer is task-scoped and runs from the active task worktree. The
|
|
470
478
|
backend automatically starts a fresh Harness Engineer for each active task or
|
|
471
479
|
resumes its saved Session. Durable memory is versioned with the project harness
|
|
@@ -18,8 +18,9 @@ export function createClaudeAdapter(runner) {
|
|
|
18
18
|
}
|
|
19
19
|
return result.stdout.trim();
|
|
20
20
|
},
|
|
21
|
-
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt) {
|
|
22
|
-
const args = ["--
|
|
21
|
+
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt, pluginDirs = []) {
|
|
22
|
+
const args = pluginDirs.flatMap((pluginDir) => ["--plugin-dir", pluginDir]);
|
|
23
|
+
args.push("--agent", role);
|
|
23
24
|
const sessionSettings = { ...settingsOverride };
|
|
24
25
|
if (claudeSessionId) {
|
|
25
26
|
args.push(resume ? "--resume" : "--session-id", claudeSessionId);
|
|
@@ -20,6 +20,7 @@ import { renderProjectManagerHarnessRules } from "../templates/harness/project-m
|
|
|
20
20
|
import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
|
|
21
21
|
import { renderTesterHarnessRules } from "../templates/harness/tester-agent.js";
|
|
22
22
|
import { renderVcmArchitectureInterviewSkillRules } from "../templates/harness/vcm-architecture-interview-skill.js";
|
|
23
|
+
import { renderVcmCodeNavigationSkillRules } from "../templates/harness/vcm-code-navigation-skill.js";
|
|
23
24
|
import { renderVcmFinalAcceptanceSkillRules } from "../templates/harness/vcm-final-acceptance-skill.js";
|
|
24
25
|
import { renderVcmHarnessBootstrapSkillRules } from "../templates/harness/vcm-harness-bootstrap-skill.js";
|
|
25
26
|
import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/vcm-long-running-validation-skill.js";
|
|
@@ -64,18 +65,18 @@ const AGENT_FRONTMATTER = {
|
|
|
64
65
|
},
|
|
65
66
|
architect: {
|
|
66
67
|
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
|
|
67
|
-
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent"
|
|
68
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP"
|
|
68
69
|
},
|
|
69
70
|
coder: {
|
|
70
71
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
71
|
-
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent"
|
|
72
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP"
|
|
72
73
|
},
|
|
73
74
|
tester: {
|
|
74
75
|
description: "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings."
|
|
75
76
|
},
|
|
76
77
|
reviewer: {
|
|
77
78
|
description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
|
|
78
|
-
tools: "Read, Grep, Glob, Bash, Write"
|
|
79
|
+
tools: "Read, Grep, Glob, Bash, Write, LSP"
|
|
79
80
|
},
|
|
80
81
|
translator: {
|
|
81
82
|
description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
@@ -93,6 +94,11 @@ const AGENT_FRONTMATTER = {
|
|
|
93
94
|
effort: "xhigh"
|
|
94
95
|
}
|
|
95
96
|
};
|
|
97
|
+
const REQUIRED_AGENT_TOOLS = {
|
|
98
|
+
architect: ["Agent", "LSP"],
|
|
99
|
+
coder: ["Agent", "LSP"],
|
|
100
|
+
reviewer: ["LSP"]
|
|
101
|
+
};
|
|
96
102
|
const MANAGED_FILES = [
|
|
97
103
|
{
|
|
98
104
|
path: "CLAUDE.md",
|
|
@@ -256,6 +262,12 @@ const WHOLE_FILES = [
|
|
|
256
262
|
mode: 0o644,
|
|
257
263
|
content: renderSkillFile("VCM Architecture Interview Skill", "vcm-architecture-interview", "Use when Architect must confirm user-owned behavior and contract decisions before architecture planning.", renderVcmArchitectureInterviewSkillRules())
|
|
258
264
|
},
|
|
265
|
+
{
|
|
266
|
+
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
267
|
+
category: "skill",
|
|
268
|
+
mode: 0o644,
|
|
269
|
+
content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
|
|
270
|
+
},
|
|
259
271
|
{
|
|
260
272
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
261
273
|
category: "skill",
|
|
@@ -657,8 +669,8 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
657
669
|
if (definition.memoryBlock) {
|
|
658
670
|
nextContent = ensureVcmMemoryBlock(nextContent);
|
|
659
671
|
}
|
|
660
|
-
|
|
661
|
-
nextContent = ensureAgentTool(nextContent,
|
|
672
|
+
for (const requiredTool of REQUIRED_AGENT_TOOLS[definition.agentName] ?? []) {
|
|
673
|
+
nextContent = ensureAgentTool(nextContent, requiredTool);
|
|
662
674
|
}
|
|
663
675
|
await writeIfChanged({
|
|
664
676
|
targetPath,
|
package/dist/backend/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CCR_GATEWAY_BASE_URL, CCR_GPT_EFFECTIVE_CONTEXT_TOKENS, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
|
|
1
|
+
import { CCR_GATEWAY_BASE_URL, CCR_GPT_AUTO_COMPACT_PERCENT, CCR_GPT_AUTO_COMPACT_WINDOW_TOKENS, CCR_GPT_EFFECTIVE_CONTEXT_TOKENS, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
@@ -163,6 +163,8 @@ export function createCcrIntegrationService(deps) {
|
|
|
163
163
|
ANTHROPIC_SMALL_FAST_MODEL: CCR_GPT_MODEL_ID,
|
|
164
164
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
|
|
165
165
|
CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(CCR_GPT_EFFECTIVE_CONTEXT_TOKENS),
|
|
166
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(CCR_GPT_AUTO_COMPACT_WINDOW_TOKENS),
|
|
167
|
+
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: String(CCR_GPT_AUTO_COMPACT_PERCENT),
|
|
166
168
|
CLAUDE_CONFIG_DIR: configDir,
|
|
167
169
|
NO_PROXY: noProxy,
|
|
168
170
|
no_proxy: noProxy
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { VCM_LSP_PLUGIN_DIR, VCM_LSP_PLUGIN_MANIFEST, VCM_LSP_PLUGIN_NAME } from "./lsp-plugin.js";
|
|
3
|
+
const LANGUAGE_DEFINITIONS = [
|
|
4
|
+
{
|
|
5
|
+
language: "rust",
|
|
6
|
+
label: "Rust",
|
|
7
|
+
extensions: [".rs"],
|
|
8
|
+
manifests: ["Cargo.toml"],
|
|
9
|
+
serverCommand: "rust-analyzer",
|
|
10
|
+
pluginServerName: "rust-analyzer",
|
|
11
|
+
probeArgs: ["--version"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
language: "typescript",
|
|
15
|
+
label: "TypeScript / JavaScript",
|
|
16
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"],
|
|
17
|
+
manifests: ["package.json", "tsconfig.json", "jsconfig.json"],
|
|
18
|
+
serverCommand: "typescript-language-server",
|
|
19
|
+
pluginServerName: "typescript",
|
|
20
|
+
probeArgs: ["--version"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
language: "python",
|
|
24
|
+
label: "Python",
|
|
25
|
+
extensions: [".py", ".pyi"],
|
|
26
|
+
manifests: ["pyproject.toml", "requirements.txt", "setup.py"],
|
|
27
|
+
serverCommand: "pyright-langserver",
|
|
28
|
+
pluginServerName: "pyright",
|
|
29
|
+
probeArgs: ["--version"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
language: "go",
|
|
33
|
+
label: "Go",
|
|
34
|
+
extensions: [".go"],
|
|
35
|
+
manifests: ["go.mod"],
|
|
36
|
+
serverCommand: "gopls",
|
|
37
|
+
pluginServerName: "gopls",
|
|
38
|
+
probeArgs: ["version"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
language: "cpp",
|
|
42
|
+
label: "C / C++",
|
|
43
|
+
extensions: [".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"],
|
|
44
|
+
manifests: ["CMakeLists.txt", "compile_commands.json"],
|
|
45
|
+
serverCommand: "clangd",
|
|
46
|
+
pluginServerName: "clangd",
|
|
47
|
+
probeArgs: ["--version"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
language: "java",
|
|
51
|
+
label: "Java",
|
|
52
|
+
extensions: [".java"],
|
|
53
|
+
manifests: ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
|
|
54
|
+
serverCommand: "jdtls",
|
|
55
|
+
pluginServerName: "jdtls",
|
|
56
|
+
probeArgs: ["--version"]
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 3_000;
|
|
60
|
+
const DEFAULT_CACHE_TTL_MS = 60_000;
|
|
61
|
+
export function createHarnessCodeIntelligenceDetector(fs, options = {}) {
|
|
62
|
+
const probeCache = new Map();
|
|
63
|
+
const probesInFlight = new Map();
|
|
64
|
+
const now = options.now ?? Date.now;
|
|
65
|
+
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
66
|
+
return {
|
|
67
|
+
async detect(repoRoot) {
|
|
68
|
+
return detectCodeIntelligence(fs, repoRoot, options, async (definition, executablePath) => {
|
|
69
|
+
const cacheKey = `${executablePath}\u0000${definition.probeArgs.join("\u0000")}`;
|
|
70
|
+
const cached = probeCache.get(cacheKey);
|
|
71
|
+
const currentTime = now();
|
|
72
|
+
if (cached && cached.expiresAt > currentTime) {
|
|
73
|
+
return cached.result;
|
|
74
|
+
}
|
|
75
|
+
if (!options.runner) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const existingProbe = probesInFlight.get(cacheKey);
|
|
79
|
+
if (existingProbe) {
|
|
80
|
+
return existingProbe;
|
|
81
|
+
}
|
|
82
|
+
const probePromise = options.runner.run(executablePath, definition.probeArgs, {
|
|
83
|
+
cwd: repoRoot,
|
|
84
|
+
timeoutMs: options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS
|
|
85
|
+
});
|
|
86
|
+
probesInFlight.set(cacheKey, probePromise);
|
|
87
|
+
const result = await probePromise.finally(() => probesInFlight.delete(cacheKey));
|
|
88
|
+
probeCache.set(cacheKey, {
|
|
89
|
+
expiresAt: currentTime + cacheTtlMs,
|
|
90
|
+
result
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export async function detectHarnessCodeIntelligence(fs, repoRoot, options = {}) {
|
|
98
|
+
return createHarnessCodeIntelligenceDetector(fs, options).detect(repoRoot);
|
|
99
|
+
}
|
|
100
|
+
async function detectCodeIntelligence(fs, repoRoot, options, probe) {
|
|
101
|
+
const indexPaths = await readModuleIndexPaths(fs, repoRoot);
|
|
102
|
+
const pluginServers = await readPluginServers(fs, options.pluginDir ?? VCM_LSP_PLUGIN_DIR);
|
|
103
|
+
const detectedLanguages = await Promise.all(LANGUAGE_DEFINITIONS.map(async (definition) => {
|
|
104
|
+
const detectedBy = await detectLanguage(fs, repoRoot, indexPaths, definition);
|
|
105
|
+
if (detectedBy.length === 0) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const pluginReady = pluginServers.get(definition.pluginServerName) === definition.serverCommand;
|
|
109
|
+
const executablePath = await findExecutable(fs, repoRoot, definition.serverCommand, options);
|
|
110
|
+
const probeResult = executablePath ? await probe(definition, executablePath) : undefined;
|
|
111
|
+
const serverFound = Boolean(executablePath);
|
|
112
|
+
const serverRunnable = probeResult?.exitCode === 0;
|
|
113
|
+
const state = languageState(pluginReady, serverFound, serverRunnable, Boolean(probeResult));
|
|
114
|
+
const status = {
|
|
115
|
+
language: definition.language,
|
|
116
|
+
label: definition.label,
|
|
117
|
+
serverCommand: definition.serverCommand,
|
|
118
|
+
pluginName: VCM_LSP_PLUGIN_NAME,
|
|
119
|
+
detected: true,
|
|
120
|
+
pluginReady,
|
|
121
|
+
serverFound,
|
|
122
|
+
serverRunnable,
|
|
123
|
+
state,
|
|
124
|
+
error: languageError(state, definition.serverCommand, probeResult),
|
|
125
|
+
detectedBy
|
|
126
|
+
};
|
|
127
|
+
return status;
|
|
128
|
+
}));
|
|
129
|
+
const languages = detectedLanguages.filter((language) => language !== undefined);
|
|
130
|
+
const readyCount = languages.filter((language) => language.state === "ready").length;
|
|
131
|
+
return {
|
|
132
|
+
state: languages.length === 0
|
|
133
|
+
? "not_detected"
|
|
134
|
+
: readyCount === languages.length
|
|
135
|
+
? "ready"
|
|
136
|
+
: readyCount === 0
|
|
137
|
+
? "missing"
|
|
138
|
+
: "partial",
|
|
139
|
+
languages
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function languageState(pluginReady, serverFound, serverRunnable, probeCompleted) {
|
|
143
|
+
if (!pluginReady) {
|
|
144
|
+
return "plugin_missing";
|
|
145
|
+
}
|
|
146
|
+
if (!serverFound) {
|
|
147
|
+
return "server_missing";
|
|
148
|
+
}
|
|
149
|
+
if (!probeCompleted) {
|
|
150
|
+
return "server_unverified";
|
|
151
|
+
}
|
|
152
|
+
return serverRunnable ? "ready" : "server_failed";
|
|
153
|
+
}
|
|
154
|
+
function languageError(state, command, probeResult) {
|
|
155
|
+
if (state === "plugin_missing") {
|
|
156
|
+
return `VCM LSP plugin manifest is missing or does not declare ${command}.`;
|
|
157
|
+
}
|
|
158
|
+
if (state === "server_missing") {
|
|
159
|
+
return `${command} was not found in the VCM backend PATH.`;
|
|
160
|
+
}
|
|
161
|
+
if (state === "server_unverified") {
|
|
162
|
+
return `${command} was found but has not been probed by this runtime.`;
|
|
163
|
+
}
|
|
164
|
+
if (state !== "server_failed") {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const detail = firstNonEmptyLine(probeResult?.stderr, probeResult?.stdout);
|
|
168
|
+
return detail
|
|
169
|
+
? `${command} failed its startup probe. ${detail}`
|
|
170
|
+
: `${command} failed its startup probe with exit code ${probeResult?.exitCode ?? 1}.`;
|
|
171
|
+
}
|
|
172
|
+
async function readPluginServers(fs, pluginDir) {
|
|
173
|
+
const manifestPath = pluginDir === VCM_LSP_PLUGIN_DIR
|
|
174
|
+
? VCM_LSP_PLUGIN_MANIFEST
|
|
175
|
+
: path.join(pluginDir, ".claude-plugin", "plugin.json");
|
|
176
|
+
if (!await fs.pathExists(manifestPath)) {
|
|
177
|
+
return new Map();
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const manifest = await fs.readJson(manifestPath);
|
|
181
|
+
return new Map(Object.entries(manifest.lspServers ?? {})
|
|
182
|
+
.filter((entry) => typeof entry[1]?.command === "string")
|
|
183
|
+
.map(([name, server]) => [name, server.command]));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return new Map();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function readModuleIndexPaths(fs, repoRoot) {
|
|
190
|
+
const indexPath = path.join(repoRoot, ".ai/generated/module-index.json");
|
|
191
|
+
if (!await fs.pathExists(indexPath)) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const value = await fs.readJson(indexPath);
|
|
196
|
+
return collectPathLikeStrings(value);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function detectLanguage(fs, repoRoot, indexPaths, definition) {
|
|
203
|
+
const evidence = new Set();
|
|
204
|
+
for (const manifest of definition.manifests) {
|
|
205
|
+
if (await fs.pathExists(path.join(repoRoot, manifest))) {
|
|
206
|
+
evidence.add(manifest);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (const filePath of indexPaths) {
|
|
210
|
+
if (definition.extensions.includes(path.extname(filePath).toLowerCase())) {
|
|
211
|
+
evidence.add(".ai/generated/module-index.json");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return Array.from(evidence);
|
|
216
|
+
}
|
|
217
|
+
async function findExecutable(fs, repoRoot, command, options) {
|
|
218
|
+
const platform = options.platform ?? process.platform;
|
|
219
|
+
const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
|
|
220
|
+
const extensions = platform === "win32"
|
|
221
|
+
? (options.pathExt ?? process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
222
|
+
: [""];
|
|
223
|
+
for (const directory of pathEnv.split(path.delimiter)) {
|
|
224
|
+
for (const extension of extensions) {
|
|
225
|
+
const candidate = path.resolve(directory || repoRoot, `${command}${extension.toLowerCase()}`);
|
|
226
|
+
if (await fs.pathExists(candidate)) {
|
|
227
|
+
return candidate;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
function firstNonEmptyLine(...values) {
|
|
234
|
+
for (const value of values) {
|
|
235
|
+
const line = value?.split(/\r?\n/).map((part) => part.trim()).find(Boolean);
|
|
236
|
+
if (line) {
|
|
237
|
+
return line.length > 300 ? `${line.slice(0, 297)}...` : line;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
function collectPathLikeStrings(value) {
|
|
243
|
+
if (typeof value === "string") {
|
|
244
|
+
return value.includes("/") || path.extname(value) ? [value] : [];
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.flatMap(collectPathLikeStrings);
|
|
248
|
+
}
|
|
249
|
+
if (value && typeof value === "object") {
|
|
250
|
+
return Object.values(value).flatMap(collectPathLikeStrings);
|
|
251
|
+
}
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
@@ -17,6 +17,7 @@ import { renderProjectManagerHarnessRules } from "../templates/harness/project-m
|
|
|
17
17
|
import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
|
|
18
18
|
import { renderTesterHarnessRules } from "../templates/harness/tester-agent.js";
|
|
19
19
|
import { renderVcmArchitectureInterviewSkillRules } from "../templates/harness/vcm-architecture-interview-skill.js";
|
|
20
|
+
import { renderVcmCodeNavigationSkillRules } from "../templates/harness/vcm-code-navigation-skill.js";
|
|
20
21
|
import { renderVcmFinalAcceptanceSkillRules } from "../templates/harness/vcm-final-acceptance-skill.js";
|
|
21
22
|
import { renderVcmHarnessBootstrapSkillRules } from "../templates/harness/vcm-harness-bootstrap-skill.js";
|
|
22
23
|
import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/vcm-long-running-validation-skill.js";
|
|
@@ -29,6 +30,7 @@ import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } f
|
|
|
29
30
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
30
31
|
import { VcmError } from "../errors.js";
|
|
31
32
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
33
|
+
import { createHarnessCodeIntelligenceDetector } from "./code-intelligence-service.js";
|
|
32
34
|
const execFileAsync = promisify(execFile);
|
|
33
35
|
const BOOTSTRAP_SESSION_PATH = ".ai/vcm/bootstrap/session.json";
|
|
34
36
|
const HARNESS_ENGINEER_SESSION_PATH = ".ai/vcm/harness-engineer/session.json";
|
|
@@ -110,6 +112,14 @@ const HARNESS_FILES = [
|
|
|
110
112
|
ownership: "whole-file",
|
|
111
113
|
renderRules: renderVcmArchitectureInterviewSkillRules
|
|
112
114
|
},
|
|
115
|
+
{
|
|
116
|
+
kind: "skill-vcm-code-navigation",
|
|
117
|
+
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
118
|
+
title: "VCM Code Navigation Skill",
|
|
119
|
+
frontmatter: renderSkillFrontmatter("vcm-code-navigation", "Use when Architect or Reviewer must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths."),
|
|
120
|
+
ownership: "whole-file",
|
|
121
|
+
renderRules: renderVcmCodeNavigationSkillRules
|
|
122
|
+
},
|
|
113
123
|
{
|
|
114
124
|
kind: "skill-vcm-route-message",
|
|
115
125
|
path: ".claude/skills/vcm-route-message/SKILL.md",
|
|
@@ -187,7 +197,8 @@ const HARNESS_FILES = [
|
|
|
187
197
|
path: ".claude/agents/reviewer.md",
|
|
188
198
|
title: "Reviewer Agent",
|
|
189
199
|
memoryBlock: true,
|
|
190
|
-
|
|
200
|
+
requiredTools: ["LSP"],
|
|
201
|
+
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Grep, Glob, Bash, Write, LSP" }),
|
|
191
202
|
renderRules: renderReviewerAgentRules
|
|
192
203
|
},
|
|
193
204
|
{
|
|
@@ -260,8 +271,9 @@ const HARNESS_FILES = [
|
|
|
260
271
|
path: ".claude/agents/architect.md",
|
|
261
272
|
title: "Architect Agent",
|
|
262
273
|
memoryBlock: true,
|
|
274
|
+
requiredTools: ["Agent", "LSP"],
|
|
263
275
|
blankLineBeforeEnd: true,
|
|
264
|
-
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
|
|
276
|
+
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP" }),
|
|
265
277
|
renderRules: renderArchitectHarnessRules
|
|
266
278
|
},
|
|
267
279
|
{
|
|
@@ -269,7 +281,8 @@ const HARNESS_FILES = [
|
|
|
269
281
|
path: ".claude/agents/coder.md",
|
|
270
282
|
title: "Coder Agent",
|
|
271
283
|
memoryBlock: true,
|
|
272
|
-
|
|
284
|
+
requiredTools: ["Agent", "LSP"],
|
|
285
|
+
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP" }),
|
|
273
286
|
renderRules: renderCoderHarnessRules
|
|
274
287
|
},
|
|
275
288
|
{
|
|
@@ -284,14 +297,20 @@ const HARNESS_FILES = [
|
|
|
284
297
|
export function createHarnessService(deps) {
|
|
285
298
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
286
299
|
const vcmVersion = deps.vcmVersion ?? "unknown";
|
|
300
|
+
const codeIntelligenceDetector = createHarnessCodeIntelligenceDetector(deps.fs, {
|
|
301
|
+
runner: deps.commandRunner
|
|
302
|
+
});
|
|
287
303
|
return {
|
|
288
304
|
async getHarnessStatus(repoRoot) {
|
|
289
|
-
const analyses = await
|
|
305
|
+
const [analyses, codeIntelligence] = await Promise.all([
|
|
306
|
+
analyzeHarnessFiles(deps.fs, repoRoot),
|
|
307
|
+
codeIntelligenceDetector.detect(repoRoot)
|
|
308
|
+
]);
|
|
290
309
|
const legacyChanges = await analyzeLegacyCodexHarnessPaths(deps.fs, repoRoot);
|
|
291
310
|
const manifestChange = deps.runFixedInstaller
|
|
292
311
|
? await analyzeHarnessManifest(deps.fs, repoRoot, vcmVersion)
|
|
293
312
|
: undefined;
|
|
294
|
-
return renderHarnessStatus(await readHarnessRevisionValue(deps.fs, repoRoot), analyses, legacyChanges, manifestChange);
|
|
313
|
+
return renderHarnessStatus(await readHarnessRevisionValue(deps.fs, repoRoot), analyses, legacyChanges, manifestChange, codeIntelligence);
|
|
295
314
|
},
|
|
296
315
|
async getHarnessFileContent(repoRoot, filePath) {
|
|
297
316
|
return readHarnessFileContent(deps.fs, repoRoot, filePath);
|
|
@@ -320,14 +339,17 @@ export function createHarnessService(deps) {
|
|
|
320
339
|
harnessCommit = (await commitHarnessVisibleChanges(deps.git, repoRoot, "chore(vcm-harness): update harness file")).harnessCommit;
|
|
321
340
|
}
|
|
322
341
|
const file = await readHarnessFileContent(deps.fs, repoRoot, definition.path);
|
|
323
|
-
const analyses = await
|
|
342
|
+
const [analyses, codeIntelligence] = await Promise.all([
|
|
343
|
+
analyzeHarnessFiles(deps.fs, repoRoot),
|
|
344
|
+
codeIntelligenceDetector.detect(repoRoot)
|
|
345
|
+
]);
|
|
324
346
|
const legacyChanges = await analyzeLegacyCodexHarnessPaths(deps.fs, repoRoot);
|
|
325
347
|
const manifestChange = deps.runFixedInstaller
|
|
326
348
|
? await analyzeHarnessManifest(deps.fs, repoRoot, vcmVersion)
|
|
327
349
|
: undefined;
|
|
328
350
|
return {
|
|
329
351
|
file,
|
|
330
|
-
status: renderHarnessStatus(await readHarnessRevisionValue(deps.fs, repoRoot), analyses, legacyChanges, manifestChange),
|
|
352
|
+
status: renderHarnessStatus(await readHarnessRevisionValue(deps.fs, repoRoot), analyses, legacyChanges, manifestChange, codeIntelligence),
|
|
331
353
|
harnessCommit
|
|
332
354
|
};
|
|
333
355
|
},
|
|
@@ -1197,7 +1219,8 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1197
1219
|
if (!match) {
|
|
1198
1220
|
const migratedContent = migrateLegacyHarnessFile(definition, currentContent, expectedBlock);
|
|
1199
1221
|
if (migratedContent) {
|
|
1200
|
-
const
|
|
1222
|
+
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(migratedContent) : migratedContent;
|
|
1223
|
+
const nextContent = ensureAgentTools(memoryUpdatedContent, definition.requiredTools);
|
|
1201
1224
|
return {
|
|
1202
1225
|
definition,
|
|
1203
1226
|
status: {
|
|
@@ -1216,9 +1239,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1216
1239
|
};
|
|
1217
1240
|
}
|
|
1218
1241
|
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1219
|
-
const nextContent = definition.
|
|
1220
|
-
? ensureAgentTool(insertedContent, "Agent")
|
|
1221
|
-
: insertedContent;
|
|
1242
|
+
const nextContent = ensureAgentTools(insertedContent, definition.requiredTools);
|
|
1222
1243
|
return {
|
|
1223
1244
|
definition,
|
|
1224
1245
|
status: {
|
|
@@ -1240,9 +1261,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1240
1261
|
const currentBlock = match[0];
|
|
1241
1262
|
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1242
1263
|
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1243
|
-
const nextContent = definition.
|
|
1244
|
-
? ensureAgentTool(memoryUpdatedContent, "Agent")
|
|
1245
|
-
: memoryUpdatedContent;
|
|
1264
|
+
const nextContent = ensureAgentTools(memoryUpdatedContent, definition.requiredTools);
|
|
1246
1265
|
const action = currentContent === nextContent ? "ok" : "update";
|
|
1247
1266
|
return {
|
|
1248
1267
|
definition,
|
|
@@ -1300,7 +1319,7 @@ async function removeLegacyCodexHarnessPaths(fs, repoRoot) {
|
|
|
1300
1319
|
}
|
|
1301
1320
|
return changes;
|
|
1302
1321
|
}
|
|
1303
|
-
function renderHarnessStatus(harnessRevision, analyses, legacyChanges = [], manifestChange) {
|
|
1322
|
+
function renderHarnessStatus(harnessRevision, analyses, legacyChanges = [], manifestChange, codeIntelligence) {
|
|
1304
1323
|
const files = analyses.map((analysis) => analysis.status);
|
|
1305
1324
|
const plannedChanges = analyses
|
|
1306
1325
|
.map((analysis) => analysis.plannedChange)
|
|
@@ -1326,6 +1345,7 @@ function renderHarnessStatus(harnessRevision, analyses, legacyChanges = [], mani
|
|
|
1326
1345
|
files,
|
|
1327
1346
|
needsApply: plannedChanges.length > 0,
|
|
1328
1347
|
plannedChanges,
|
|
1348
|
+
codeIntelligence,
|
|
1329
1349
|
warnings: plannedChanges.length > 0
|
|
1330
1350
|
? ["Review and commit VCM Harness changes before starting long-running work."]
|
|
1331
1351
|
: []
|
|
@@ -1369,6 +1389,9 @@ function ensureAgentTool(content, requiredTool) {
|
|
|
1369
1389
|
const nextTools = [...tools, requiredTool].join(", ");
|
|
1370
1390
|
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
1371
1391
|
}
|
|
1392
|
+
function ensureAgentTools(content, requiredTools) {
|
|
1393
|
+
return (requiredTools ?? []).reduce(ensureAgentTool, content);
|
|
1394
|
+
}
|
|
1372
1395
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1373
1396
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
1374
1397
|
if (!legacyContent) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
export const VCM_LSP_PLUGIN_NAME = "vcm-lsp-bridge";
|
|
4
|
+
export const VCM_LSP_PLUGIN_DIR = fileURLToPath(new URL("../../../scripts/claude-plugins/vcm-lsp-bridge", import.meta.url));
|
|
5
|
+
export const VCM_LSP_PLUGIN_MANIFEST = path.join(VCM_LSP_PLUGIN_DIR, ".claude-plugin", "plugin.json");
|
|
6
|
+
const LSP_ROLES = new Set(["architect", "coder", "reviewer"]);
|
|
7
|
+
export function roleUsesLsp(role) {
|
|
8
|
+
return LSP_ROLES.has(role);
|
|
9
|
+
}
|
|
@@ -8,6 +8,7 @@ import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
|
8
8
|
import { claudeTranscriptPath } from "./claude-transcript-service.js";
|
|
9
9
|
import { readHarnessRevisionState } from "./harness-revision.js";
|
|
10
10
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
11
|
+
import { roleUsesLsp, VCM_LSP_PLUGIN_DIR } from "./lsp-plugin.js";
|
|
11
12
|
const TRANSLATOR_ROLE = "translator";
|
|
12
13
|
const HARNESS_ENGINEER_ROLE = "harness-engineer";
|
|
13
14
|
const TRANSLATION_DIR = ".ai/vcm/translations";
|
|
@@ -82,7 +83,7 @@ export function createSessionService(deps) {
|
|
|
82
83
|
? claudeTranscriptPath(taskRepoRoot, resumeClaudeSessionId, persisted?.claudeConfigDir)
|
|
83
84
|
: undefined;
|
|
84
85
|
const startCommand = {
|
|
85
|
-
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride, input.appendSystemPrompt),
|
|
86
|
+
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride, input.appendSystemPrompt, roleUsesLsp(role) ? [VCM_LSP_PLUGIN_DIR] : []),
|
|
86
87
|
cwd: taskRepoRoot
|
|
87
88
|
};
|
|
88
89
|
const runtimeSessionToken = randomUUID();
|
|
@@ -101,7 +102,10 @@ export function createSessionService(deps) {
|
|
|
101
102
|
VCM_ROLE: role,
|
|
102
103
|
VCM_SESSION_ID: claudeSessionId || undefined,
|
|
103
104
|
VCM_RUNTIME_SESSION_TOKEN: runtimeSessionToken
|
|
104
|
-
}, modelEnvironment,
|
|
105
|
+
}, modelEnvironment, {
|
|
106
|
+
...buildUsageTelemetryEnvironment(deps.apiUrl, role, model),
|
|
107
|
+
...(roleUsesLsp(role) ? { ENABLE_LSP_TOOL: "true" } : {})
|
|
108
|
+
}),
|
|
105
109
|
cols: input.cols,
|
|
106
110
|
rows: input.rows
|
|
107
111
|
});
|