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 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 an effective context limit of `258400` tokens.
281
- VCM leaves Claude Code's automatic compaction threshold unset so Claude Code
282
- can compact against that limit. Native Claude sessions receive no VCM-owned
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 = ["--agent", role];
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);
@@ -6,6 +6,7 @@ export function createCommandRunner() {
6
6
  const result = await execa(command, args, {
7
7
  cwd: options.cwd,
8
8
  env: options.env,
9
+ timeout: options.timeoutMs,
9
10
  reject: false
10
11
  });
11
12
  return {
@@ -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
- if (definition.agentName === "architect") {
661
- nextContent = ensureAgentTool(nextContent, "Agent");
672
+ for (const requiredTool of REQUIRED_AGENT_TOOLS[definition.agentName] ?? []) {
673
+ nextContent = ensureAgentTool(nextContent, requiredTool);
662
674
  }
663
675
  await writeIfChanged({
664
676
  targetPath,
@@ -246,6 +246,7 @@ export function createDefaultServerDeps(options = {}) {
246
246
  });
247
247
  const harnessService = createHarnessService({
248
248
  fs,
249
+ commandRunner: runner,
249
250
  git,
250
251
  runtime,
251
252
  harnessEngineerSessions: sessionService,
@@ -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
- frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Grep, Glob, Bash, Write" }),
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
- frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
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 analyzeHarnessFiles(deps.fs, repoRoot);
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 analyzeHarnessFiles(deps.fs, repoRoot);
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 nextContent = definition.memoryBlock ? ensureVcmMemoryBlock(migratedContent) : migratedContent;
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.kind === "agent-architect"
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.kind === "agent-architect"
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, buildUsageTelemetryEnvironment(deps.apiUrl, role, model)),
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
  });