vibe-coding-master 0.7.36 → 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 +8 -0
- 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/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-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
|
@@ -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
|
@@ -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
|
});
|
|
@@ -19,6 +19,13 @@ ${renderRoleMemoryRules("architect")}
|
|
|
19
19
|
- In Debug Mode and Architecture Diagnosis Mode, writing baseline unit tests for changed code and running required L0/L1 plus applicable L2/L3 checks are part of the implementation duty; tester still owns final validation.
|
|
20
20
|
- Do not make product priority or approval decisions; route those questions back to project-manager.
|
|
21
21
|
|
|
22
|
+
### Work Persistence
|
|
23
|
+
|
|
24
|
+
- Treat Architect artifacts, not Session memory, as continuation state.
|
|
25
|
+
- Write each verified code fact, confirmed user decision, architecture decision, root cause, and validation result to its owning artifact as soon as it becomes an input to later work.
|
|
26
|
+
- Before ending any turn, ensure all information required to continue the current Architect work is present in the current artifacts.
|
|
27
|
+
- Keep artifacts current and self-contained. Replace superseded content instead of appending conversation history or investigation logs.
|
|
28
|
+
|
|
22
29
|
### Architecture Interview
|
|
23
30
|
|
|
24
31
|
- Before the first Architecture Planning step of Code-Change Flow, use \`vcm-architecture-interview\` and complete \`.ai/vcm/handoffs/architecture-brief.md\` and \`.ai/vcm/handoffs/architecture-evidence.md\`.
|
|
@@ -38,6 +45,9 @@ ${renderRoleMemoryRules("architect")}
|
|
|
38
45
|
### Planning Code Reading
|
|
39
46
|
|
|
40
47
|
- Do not plan from session memory, architecture docs, generated context, or code comments alone. Re-read current-worktree source and verify actual behavior from implementation.
|
|
48
|
+
- Use \`vcm-code-navigation\` for symbol definitions, implementations, references, call hierarchies, and bounded behavior paths. Start from generated indexes, use LSP semantic navigation when available, then read every resolved callable unit in full.
|
|
49
|
+
- Use structural search when available and Grep for dynamic registrations, configuration or string edges, macros not resolved by LSP, documentation, and explicit fallback discovery. When LSP is available, do not treat Grep as proof of a complete symbol, caller, implementation, or reference set.
|
|
50
|
+
- If LSP is unavailable or cannot resolve a required relationship, record the limitation and exact fallback evidence in \`architecture-evidence.md\`; do not claim compiler-accurate completeness from text search alone.
|
|
41
51
|
- Define the planning boundary as the affected feature or module and identify every existing or intended observable entry point for the behavior being changed.
|
|
42
52
|
- Read the complete implementation of each relevant existing entry point.
|
|
43
53
|
- Follow every project-owned call path the plan will change through cross-module calls, state reads and writes, persistence, side effects, completion and failure signals, and consumers.
|
|
@@ -66,6 +76,8 @@ ${renderRoleMemoryRules("architect")}
|
|
|
66
76
|
- When starting architecture planning, first write an \`Architecture Evidence Verification\` row to \`.ai/vcm/handoffs/planning-progress.md\`, with the evidence artifact, verified worktree revision, done criterion, and status. Complete it by checking that the evidence covers the accepted feature boundary and still matches the current worktree.
|
|
67
77
|
- After \`Architecture Evidence Verification\` is complete, add the remaining planning steps: one head step for cross-module work (architecture decision, boundaries, ownership, invariants, build-configuration proofs), one middle step per affected module from the module index in dependency order — split a module into per-file steps when it exceeds one round — and one tail step for cross-module wiring, whole-plan ledger reconciliation, and final build evidence. A small task degrades to head, one middle step, and tail.
|
|
68
78
|
- Bind every step to repository facts and machine checks: scope is module or file paths from the module index; deliverable is plan sections or ledger ID ranges; done criterion is a tool output or recorded check result — never a self-assessment.
|
|
79
|
+
- Complete a planning step only after its architecture conclusions, affected files, callable surfaces, ledger items, risks, and evidence references have been written to the corresponding sections of \`architecture-plan.md\`.
|
|
80
|
+
- Update \`architecture-plan.md\` before marking the corresponding \`planning-progress.md\` step complete. A conclusion that exists only in Session context is not completed planning work.
|
|
69
81
|
- Update \`planning-progress.md\` at the end of every planning round: mark completed steps with their evidence and leave remaining steps unchanged. Do not shrink, merge, or drop a remaining step without recording the change and its reason.
|
|
70
82
|
- If the round ends before all steps are done, report \`Planning Result: incomplete\` with the progress record; project-manager routes continuation. Never compress remaining enumeration or scaffolding into summary rows to reach \`Planning Result: complete\` within the current round — an honest \`incomplete\` with recorded progress is the required outcome.
|
|
71
83
|
- \`planning-progress.md\` is task-runtime state for continuation and routing, not part of the reviewed plan; \`architecture-plan.md\` alone remains the executable plan of record.
|
|
@@ -135,6 +147,8 @@ ${renderRoleMemoryRules("architect")}
|
|
|
135
147
|
### Debug Mode
|
|
136
148
|
|
|
137
149
|
- Project-manager may route bugs, failing tests, build/runtime failures, or unclear defects directly to architect Debug Mode.
|
|
150
|
+
- Maintain \`architect-debug.md\` throughout Debug Mode. Record the triggering failure and diagnostic evidence while investigating, write the confirmed root cause before implementing the fix, and add implementation and validation evidence as each stage completes.
|
|
151
|
+
- Do not postpone the first durable Debug record until the final handoff.
|
|
138
152
|
- Architect may read source/tests, edit code, and run focused diagnostics until root cause is known. Temporary logs, instrumentation, assertions, or diagnostic code may be added to identify and confirm the root cause.
|
|
139
153
|
- Once the root cause is confirmed, architect owns the technical change boundary for the fix. Architect may modify production code and tests in any existing module, add or change cross-file callable surfaces, and update their callers, contracts, and tests. No pre-approved module or file list limits Debug Mode implementation.
|
|
140
154
|
- When editing production code or tests in Debug Mode, read and follow \`docs/CODING_STANDARDS.md\`.
|
|
@@ -164,6 +178,7 @@ Do not assume existing code or comments are correct. Read the implementation to
|
|
|
164
178
|
Before choosing or implementing a fix:
|
|
165
179
|
|
|
166
180
|
- Define the affected feature or module and identify every observable entry point for the failing behavior.
|
|
181
|
+
- Use \`vcm-code-navigation\` to resolve definitions, implementations, references, callers, callees, and call hierarchy before following the complete implementation path.
|
|
167
182
|
- Read the relevant project and module architecture documents, public contracts, generated context, tests, handoff artifacts, and runtime evidence.
|
|
168
183
|
- Starting from each entry point, read the complete implementation of every reachable project-owned function, method, handler, callback, or command.
|
|
169
184
|
- Recursively follow every project-owned call until no unresolved project-owned callee remains. Read each symbol once and record recursive or cyclic calls.
|
|
@@ -175,8 +190,8 @@ Before choosing or implementing a fix:
|
|
|
175
190
|
|
|
176
191
|
Maintain a \`Code Reading Closure\` in \`.ai/vcm/handoffs/architecture-diagnosis.md\`:
|
|
177
192
|
|
|
178
|
-
| Symbol |
|
|
179
|
-
|
|
193
|
+
| Symbol | Definition | Implementations | Called By | Calls | State Read/Written | Side Effects | Resolution Evidence | Status |
|
|
194
|
+
|---|---|---|---|---|---|---|---|---|
|
|
180
195
|
|
|
181
196
|
\`Status\` must be \`read\`, \`external-boundary\`, or \`generated-boundary\`.
|
|
182
197
|
|
|
@@ -187,10 +202,15 @@ The code-reading phase is complete only when:
|
|
|
187
202
|
- every indirect callback, event, hook, queue, route, and dynamic dispatch path has been resolved
|
|
188
203
|
- every relevant state reader and writer has been read
|
|
189
204
|
- every relevant cross-file surface caller and consumer has been read
|
|
205
|
+
- every semantic relationship records LSP, structural-search, Grep-fallback, runtime, external-boundary, or generated-boundary evidence
|
|
190
206
|
- no unresolved project-owned symbol remains
|
|
191
207
|
|
|
192
208
|
Do not diagnose the root cause or choose a fix before the Code Reading Closure is complete.
|
|
193
209
|
|
|
210
|
+
After Code Reading Closure is complete, write \`Current Architecture\`, \`Failure Trace\`, \`Architecture Assessment\`, and \`Required Architecture Direction\` before modifying code.
|
|
211
|
+
|
|
212
|
+
Update \`Implementation And Validation\` as implementation and validation stages complete. Do not keep the diagnosis direction or completed validation evidence only in Session context.
|
|
213
|
+
|
|
194
214
|
After completing the code-reading closure, reconstruct and analyze:
|
|
195
215
|
|
|
196
216
|
- **Ownership:** owners of state, decisions, lifecycle transitions, side effects, and durable artifacts.
|