vibe-coding-master 0.3.23 → 0.3.24
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/dist/backend/adapters/git-adapter.js +53 -0
- package/dist/backend/api/artifact-routes.js +1 -28
- package/dist/backend/api/harness-routes.js +10 -0
- package/dist/backend/api/task-routes.js +2 -6
- package/dist/backend/gateway/gateway-service.js +3 -16
- package/dist/backend/server.js +3 -1
- package/dist/backend/services/artifact-service.js +1 -30
- package/dist/backend/services/harness-service.js +106 -0
- package/dist/backend/services/session-service.js +1 -4
- package/dist/backend/services/status-service.js +1 -6
- package/dist/backend/services/task-service.js +42 -110
- package/dist/backend/templates/harness/project-manager-agent.js +10 -1
- package/dist/backend/templates/harness/vcm-route-message-skill.js +8 -2
- package/dist-frontend/assets/{index-C2xTNLmb.js → index-CKWy15WL.js} +46 -47
- package/dist-frontend/index.html +1 -1
- package/docs/cc-best-practices.md +1 -1
- package/docs/full-harness-baseline.md +0 -1
- package/docs/gateway-design.md +9 -13
- package/docs/product-design.md +18 -39
- package/docs/vcm-cc-best-practices.md +0 -1
- package/package.json +1 -1
|
@@ -81,6 +81,18 @@ export function createGitAdapter(runner) {
|
|
|
81
81
|
}
|
|
82
82
|
return result.stdout;
|
|
83
83
|
},
|
|
84
|
+
async getStagedStatus(repoRoot) {
|
|
85
|
+
const result = await runGit(runner, repoRoot, ["diff", "--cached", "--name-status"]);
|
|
86
|
+
if (result.exitCode !== 0) {
|
|
87
|
+
throw new VcmError({
|
|
88
|
+
code: "GIT_ERROR",
|
|
89
|
+
message: "Unable to read staged Git changes.",
|
|
90
|
+
statusCode: 400,
|
|
91
|
+
hint: result.stderr
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return result.stdout;
|
|
95
|
+
},
|
|
84
96
|
async isIgnored(repoRoot, repoRelativePath) {
|
|
85
97
|
const result = await runGit(runner, repoRoot, ["check-ignore", "-q", "--", repoRelativePath]);
|
|
86
98
|
if (result.exitCode === 0) {
|
|
@@ -111,6 +123,47 @@ export function createGitAdapter(runner) {
|
|
|
111
123
|
hint: result.stderr
|
|
112
124
|
});
|
|
113
125
|
},
|
|
126
|
+
async addPaths(repoRoot, paths) {
|
|
127
|
+
if (paths.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const result = await runGit(runner, repoRoot, ["add", "--", ...paths]);
|
|
131
|
+
if (result.exitCode !== 0) {
|
|
132
|
+
throw new VcmError({
|
|
133
|
+
code: "GIT_ADD_FAILED",
|
|
134
|
+
message: "Unable to stage harness changes.",
|
|
135
|
+
statusCode: 409,
|
|
136
|
+
hint: result.stderr || result.stdout
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
async commit(repoRoot, message) {
|
|
141
|
+
const result = await runGit(runner, repoRoot, ["commit", "-m", message]);
|
|
142
|
+
if (result.exitCode !== 0) {
|
|
143
|
+
throw new VcmError({
|
|
144
|
+
code: "GIT_COMMIT_FAILED",
|
|
145
|
+
message: "Unable to commit harness changes.",
|
|
146
|
+
statusCode: 409,
|
|
147
|
+
hint: result.stderr || result.stdout
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return this.getHeadCommit(repoRoot);
|
|
151
|
+
},
|
|
152
|
+
async rebase(repoRoot, upstream) {
|
|
153
|
+
const result = await runGit(runner, repoRoot, ["rebase", upstream]);
|
|
154
|
+
if (result.exitCode !== 0) {
|
|
155
|
+
throw new VcmError({
|
|
156
|
+
code: "GIT_REBASE_FAILED",
|
|
157
|
+
message: "Unable to rebase task branch onto the harness commit.",
|
|
158
|
+
statusCode: 409,
|
|
159
|
+
hint: result.stderr || result.stdout
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
stdout: result.stdout,
|
|
164
|
+
stderr: result.stderr
|
|
165
|
+
};
|
|
166
|
+
},
|
|
114
167
|
async createWorktree(input) {
|
|
115
168
|
const result = await runGit(runner, input.repoRoot, [
|
|
116
169
|
"worktree",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDispatchableRole
|
|
1
|
+
import { isDispatchableRole } from "../../shared/constants.js";
|
|
2
2
|
import { VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerArtifactRoutes(app, deps) {
|
|
@@ -52,33 +52,6 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
52
52
|
});
|
|
53
53
|
return { ok: true };
|
|
54
54
|
});
|
|
55
|
-
app.get("/api/tasks/:taskSlug/logs/:role", async (request) => {
|
|
56
|
-
const project = await requireCurrentProject(deps.projectService);
|
|
57
|
-
if (!isRoleName(request.params.role)) {
|
|
58
|
-
throw new VcmError({
|
|
59
|
-
code: "UNKNOWN_ROLE",
|
|
60
|
-
message: `Unknown role: ${request.params.role}`,
|
|
61
|
-
statusCode: 400
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
65
|
-
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
66
|
-
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
67
|
-
const artifactPath = paths.roleLogPaths[request.params.role];
|
|
68
|
-
if (!artifactPath) {
|
|
69
|
-
return {
|
|
70
|
-
role: request.params.role,
|
|
71
|
-
content: ""
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
role: request.params.role,
|
|
76
|
-
content: await deps.artifactService.readArtifact({
|
|
77
|
-
repoRoot: taskRepoRoot,
|
|
78
|
-
artifactPath
|
|
79
|
-
})
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
55
|
}
|
|
83
56
|
function parseDispatchableRole(role) {
|
|
84
57
|
if (!isDispatchableRole(role)) {
|
|
@@ -16,6 +16,16 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
16
16
|
const project = await requireCurrentProject(deps.projectService);
|
|
17
17
|
return deps.harnessService.applyHarness(project.repoRoot);
|
|
18
18
|
});
|
|
19
|
+
app.post("/api/projects/harness/tasks/:taskSlug/commit-and-rebase", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
22
|
+
return deps.harnessService.commitAndRebaseTask(project.repoRoot, {
|
|
23
|
+
taskSlug: task.taskSlug,
|
|
24
|
+
branch: task.branch,
|
|
25
|
+
worktreePath: task.worktreePath,
|
|
26
|
+
changedFiles: request.body?.changedFiles ?? []
|
|
27
|
+
});
|
|
28
|
+
});
|
|
19
29
|
app.get("/api/projects/harness/bootstrap", async () => {
|
|
20
30
|
const project = await requireCurrentProject(deps.projectService);
|
|
21
31
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPATCHABLE_ROLES
|
|
1
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
2
2
|
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerTaskRoutes(app, deps) {
|
|
@@ -66,6 +66,7 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
|
|
|
66
66
|
createdAt: timestamp,
|
|
67
67
|
updatedAt: timestamp,
|
|
68
68
|
repoRoot,
|
|
69
|
+
worktreePath: `${repoRoot}/.claude/worktrees/${taskSlug}`,
|
|
69
70
|
branch: "unknown",
|
|
70
71
|
handoffDir,
|
|
71
72
|
status: "stopped"
|
|
@@ -79,18 +80,13 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
|
|
|
79
80
|
}
|
|
80
81
|
function degradedArtifactSummary(handoffDir) {
|
|
81
82
|
const roleCommandsDir = `${handoffDir}/role-commands`;
|
|
82
|
-
const logsDir = `${handoffDir}/logs`;
|
|
83
83
|
const messagesDir = `${handoffDir}/messages`;
|
|
84
84
|
return {
|
|
85
85
|
paths: {
|
|
86
86
|
handoffDir,
|
|
87
87
|
roleCommandsDir,
|
|
88
|
-
logsDir,
|
|
89
88
|
messagesDir,
|
|
90
89
|
roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
|
|
91
|
-
roleLogPaths: Object.fromEntries(ROLE_NAMES
|
|
92
|
-
.filter((role) => role !== "codex-translator")
|
|
93
|
-
.map((role) => [role, `${logsDir}/${role}.log`])),
|
|
94
90
|
messageRoutePaths: {},
|
|
95
91
|
architecturePlanPath: `${handoffDir}/architecture-plan.md`,
|
|
96
92
|
knownIssuesPath: `${handoffDir}/known-issues.md`,
|
|
@@ -352,17 +352,6 @@ export function createGatewayService(deps) {
|
|
|
352
352
|
}
|
|
353
353
|
async function pullCurrent() {
|
|
354
354
|
const project = await ensureProject();
|
|
355
|
-
const settings = await syncDesktopContext(await deps.settings.loadSettings());
|
|
356
|
-
if (settings.currentTaskSlug) {
|
|
357
|
-
const task = await deps.taskService.loadTask(project.repoRoot, settings.currentTaskSlug);
|
|
358
|
-
if (!task.worktreePath && task.cleanupStatus !== "cleaned") {
|
|
359
|
-
throw new VcmError({
|
|
360
|
-
code: "GATEWAY_PULL_BLOCKED_BY_INLINE_TASK",
|
|
361
|
-
message: `Inline task "${task.taskSlug}" uses the base repository.`,
|
|
362
|
-
statusCode: 409
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
355
|
const pulled = await deps.projectService.pullCurrentProject();
|
|
367
356
|
return [
|
|
368
357
|
"Connected repository updated.",
|
|
@@ -377,7 +366,7 @@ export function createGatewayService(deps) {
|
|
|
377
366
|
if (tasks.length === 0) {
|
|
378
367
|
return "No tasks. Use /create-task <task-slug> [title] to create one.";
|
|
379
368
|
}
|
|
380
|
-
return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.
|
|
369
|
+
return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.branch}`).join("\n");
|
|
381
370
|
}
|
|
382
371
|
async function useTask(selector) {
|
|
383
372
|
const project = await ensureProject();
|
|
@@ -405,8 +394,7 @@ export function createGatewayService(deps) {
|
|
|
405
394
|
const project = await ensureProject();
|
|
406
395
|
const task = await deps.taskService.createTask(project.repoRoot, {
|
|
407
396
|
taskSlug,
|
|
408
|
-
title
|
|
409
|
-
createWorktree: true
|
|
397
|
+
title
|
|
410
398
|
});
|
|
411
399
|
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
412
400
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
@@ -460,7 +448,7 @@ export function createGatewayService(deps) {
|
|
|
460
448
|
return [
|
|
461
449
|
`Task created and initialized: ${task.taskSlug}`,
|
|
462
450
|
`branch: ${task.branch}`,
|
|
463
|
-
`worktree: ${task.worktreePath
|
|
451
|
+
`worktree: ${task.worktreePath}`,
|
|
464
452
|
`orchestration: ${template.autoOrchestration ? "auto" : "manual"}`,
|
|
465
453
|
`translation: ${preferences.translationEnabled ? "on" : "off"}`,
|
|
466
454
|
`sessions: ${startedRoles.join(", ")}`
|
|
@@ -523,7 +511,6 @@ export function createGatewayService(deps) {
|
|
|
523
511
|
deps.roundService.stopTask(taskSlug);
|
|
524
512
|
const result = await deps.taskService.cleanupTask(project.repoRoot, taskSlug, {
|
|
525
513
|
force: true,
|
|
526
|
-
deleteBranch: Boolean(task.worktreePath),
|
|
527
514
|
forceDeleteBranch: true
|
|
528
515
|
});
|
|
529
516
|
clearFailedTranslation(project.repoRoot, taskSlug);
|
package/dist/backend/server.js
CHANGED
|
@@ -82,7 +82,8 @@ export async function createServer(deps, options = {}) {
|
|
|
82
82
|
});
|
|
83
83
|
registerHarnessRoutes(app, {
|
|
84
84
|
projectService: deps.projectService,
|
|
85
|
-
harnessService: deps.harnessService
|
|
85
|
+
harnessService: deps.harnessService,
|
|
86
|
+
taskService: deps.taskService
|
|
86
87
|
});
|
|
87
88
|
registerTaskRoutes(app, {
|
|
88
89
|
projectService: deps.projectService,
|
|
@@ -170,6 +171,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
170
171
|
const projectService = createProjectService({ fs, git, appSettings });
|
|
171
172
|
const harnessService = createHarnessService({
|
|
172
173
|
fs,
|
|
174
|
+
git,
|
|
173
175
|
runtime,
|
|
174
176
|
projectService,
|
|
175
177
|
apiUrl: options.apiUrl,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { DISPATCHABLE_ROLES
|
|
2
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
3
3
|
import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
@@ -25,25 +25,16 @@ export function createArtifactService(fs) {
|
|
|
25
25
|
return {
|
|
26
26
|
getHandoffPaths(_repoRoot, handoffDir) {
|
|
27
27
|
const roleCommandsDir = path.posix.join(handoffDir, "role-commands");
|
|
28
|
-
const logsDir = path.posix.join(handoffDir, "logs");
|
|
29
28
|
const messagesDir = path.posix.join(handoffDir, "messages");
|
|
30
29
|
return {
|
|
31
30
|
handoffDir,
|
|
32
31
|
roleCommandsDir,
|
|
33
|
-
logsDir,
|
|
34
32
|
messagesDir,
|
|
35
33
|
roleCommandPaths: {
|
|
36
34
|
architect: path.posix.join(roleCommandsDir, "architect.md"),
|
|
37
35
|
coder: path.posix.join(roleCommandsDir, "coder.md"),
|
|
38
36
|
reviewer: path.posix.join(roleCommandsDir, "reviewer.md")
|
|
39
37
|
},
|
|
40
|
-
roleLogPaths: {
|
|
41
|
-
"project-manager": path.posix.join(logsDir, "project-manager.log"),
|
|
42
|
-
architect: path.posix.join(logsDir, "architect.log"),
|
|
43
|
-
coder: path.posix.join(logsDir, "coder.log"),
|
|
44
|
-
reviewer: path.posix.join(logsDir, "reviewer.log"),
|
|
45
|
-
"codex-reviewer": path.posix.join(logsDir, "codex-reviewer.log")
|
|
46
|
-
},
|
|
47
38
|
messageRoutePaths: getDefaultMessageRoutePaths(messagesDir),
|
|
48
39
|
architecturePlanPath: path.posix.join(handoffDir, "architecture-plan.md"),
|
|
49
40
|
knownIssuesPath: path.posix.join(handoffDir, "known-issues.md"),
|
|
@@ -59,7 +50,6 @@ export function createArtifactService(fs) {
|
|
|
59
50
|
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
60
51
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.handoffDir));
|
|
61
52
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.roleCommandsDir));
|
|
62
|
-
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.logsDir));
|
|
63
53
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.messagesDir));
|
|
64
54
|
return paths;
|
|
65
55
|
},
|
|
@@ -168,25 +158,6 @@ export function createArtifactService(fs) {
|
|
|
168
158
|
async saveRoleCommand(input) {
|
|
169
159
|
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
170
160
|
await fs.writeText(resolveRepoPath(input.repoRoot, paths.roleCommandPaths[input.role]), input.content);
|
|
171
|
-
},
|
|
172
|
-
async appendRoleLog(input) {
|
|
173
|
-
if (!ROLE_NAMES.includes(input.role)) {
|
|
174
|
-
throw new VcmError({
|
|
175
|
-
code: "UNKNOWN_ROLE",
|
|
176
|
-
message: `Unknown role: ${input.role}`,
|
|
177
|
-
statusCode: 400
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
181
|
-
const roleLogPath = paths.roleLogPaths[input.role];
|
|
182
|
-
if (!roleLogPath) {
|
|
183
|
-
throw new VcmError({
|
|
184
|
-
code: "ROLE_LOG_UNAVAILABLE",
|
|
185
|
-
message: `${input.role} does not write a handoff log.`,
|
|
186
|
-
statusCode: 409
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
await fs.appendText(resolveRepoPath(input.repoRoot, roleLogPath), input.content);
|
|
190
161
|
}
|
|
191
162
|
};
|
|
192
163
|
}
|
|
@@ -246,6 +246,73 @@ export function createHarnessService(deps) {
|
|
|
246
246
|
: "VCM Harness updated. Review these files and commit the harness changes before starting long-running work."
|
|
247
247
|
};
|
|
248
248
|
},
|
|
249
|
+
async commitAndRebaseTask(repoRoot, input) {
|
|
250
|
+
if (!deps.git) {
|
|
251
|
+
throw new VcmError({
|
|
252
|
+
code: "HARNESS_GIT_UNAVAILABLE",
|
|
253
|
+
message: "Git-backed harness sync is not available in this VCM runtime.",
|
|
254
|
+
statusCode: 501
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const changedFiles = dedupeHarnessChanges(input.changedFiles);
|
|
258
|
+
const changedPaths = changedFiles.map((change) => change.path);
|
|
259
|
+
const taskBranch = await deps.git.getCurrentBranch(input.worktreePath);
|
|
260
|
+
if (taskBranch !== input.branch) {
|
|
261
|
+
throw new VcmError({
|
|
262
|
+
code: "HARNESS_TASK_BRANCH_MISMATCH",
|
|
263
|
+
message: "The selected task worktree is not on its recorded task branch.",
|
|
264
|
+
statusCode: 409,
|
|
265
|
+
hint: `Expected ${input.branch}, found ${taskBranch}.`
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const taskStatus = await deps.git.getStatusPorcelain(input.worktreePath);
|
|
269
|
+
if (taskStatus.trim()) {
|
|
270
|
+
throw new VcmError({
|
|
271
|
+
code: "HARNESS_TASK_DIRTY",
|
|
272
|
+
message: "The selected task worktree has uncommitted changes.",
|
|
273
|
+
statusCode: 409,
|
|
274
|
+
hint: "Commit or clean the task worktree before rebasing it onto the harness commit."
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const preExistingStaged = await deps.git.getStagedStatus(repoRoot);
|
|
278
|
+
if (preExistingStaged.trim()) {
|
|
279
|
+
throw new VcmError({
|
|
280
|
+
code: "HARNESS_BASE_STAGED_CHANGES",
|
|
281
|
+
message: "The connected repository already has staged changes.",
|
|
282
|
+
statusCode: 409,
|
|
283
|
+
hint: "Commit, unstage, or clean existing staged changes before using Commit & rebase task."
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const baseBranch = await deps.git.getCurrentBranch(repoRoot);
|
|
287
|
+
const baseCommitBefore = await deps.git.getHeadCommit(repoRoot);
|
|
288
|
+
let harnessCommit;
|
|
289
|
+
let committed = false;
|
|
290
|
+
if (changedPaths.length > 0) {
|
|
291
|
+
await deps.git.addPaths(repoRoot, changedPaths);
|
|
292
|
+
const stagedHarnessChanges = await deps.git.getStagedStatus(repoRoot);
|
|
293
|
+
if (stagedHarnessChanges.trim()) {
|
|
294
|
+
harnessCommit = await deps.git.commit(repoRoot, "chore: update VCM harness");
|
|
295
|
+
committed = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const baseCommitAfter = await deps.git.getHeadCommit(repoRoot);
|
|
299
|
+
await deps.git.rebase(input.worktreePath, baseCommitAfter);
|
|
300
|
+
return {
|
|
301
|
+
taskSlug: input.taskSlug,
|
|
302
|
+
branch: input.branch,
|
|
303
|
+
worktreePath: input.worktreePath,
|
|
304
|
+
baseBranch,
|
|
305
|
+
baseCommitBefore,
|
|
306
|
+
baseCommitAfter,
|
|
307
|
+
harnessCommit,
|
|
308
|
+
committed,
|
|
309
|
+
rebased: true,
|
|
310
|
+
changedFiles,
|
|
311
|
+
message: committed
|
|
312
|
+
? `Committed VCM harness update ${shortCommit(baseCommitAfter)} on ${baseBranch} and rebased ${input.branch}.`
|
|
313
|
+
: `No new harness commit was needed; rebased ${input.branch} onto ${shortCommit(baseCommitAfter)}.`
|
|
314
|
+
};
|
|
315
|
+
},
|
|
249
316
|
async getBootstrapStatus(repoRoot) {
|
|
250
317
|
return getHarnessBootstrapStatus(deps, repoRoot, now);
|
|
251
318
|
},
|
|
@@ -320,6 +387,45 @@ export function createHarnessService(deps) {
|
|
|
320
387
|
}
|
|
321
388
|
};
|
|
322
389
|
}
|
|
390
|
+
function dedupeHarnessChanges(changedFiles) {
|
|
391
|
+
const seen = new Set();
|
|
392
|
+
const changes = [];
|
|
393
|
+
for (const change of changedFiles) {
|
|
394
|
+
const normalizedPath = normalizeHarnessGitPath(change.path);
|
|
395
|
+
if (seen.has(normalizedPath)) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
seen.add(normalizedPath);
|
|
399
|
+
changes.push({
|
|
400
|
+
...change,
|
|
401
|
+
path: normalizedPath
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return changes;
|
|
405
|
+
}
|
|
406
|
+
function normalizeHarnessGitPath(value) {
|
|
407
|
+
if (!value || value.includes("\0") || path.posix.isAbsolute(value)) {
|
|
408
|
+
throw new VcmError({
|
|
409
|
+
code: "HARNESS_CHANGED_PATH_INVALID",
|
|
410
|
+
message: "Harness changed file path is invalid.",
|
|
411
|
+
statusCode: 400,
|
|
412
|
+
hint: value
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const normalized = path.posix.normalize(value).replace(/^\.\//, "");
|
|
416
|
+
if (normalized === "." || normalized.startsWith("../")) {
|
|
417
|
+
throw new VcmError({
|
|
418
|
+
code: "HARNESS_CHANGED_PATH_INVALID",
|
|
419
|
+
message: "Harness changed file path must stay inside the repository.",
|
|
420
|
+
statusCode: 400,
|
|
421
|
+
hint: value
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return normalized;
|
|
425
|
+
}
|
|
426
|
+
function shortCommit(commit) {
|
|
427
|
+
return commit.slice(0, 7);
|
|
428
|
+
}
|
|
323
429
|
async function analyzeHarnessFiles(fs, repoRoot) {
|
|
324
430
|
const analyses = [];
|
|
325
431
|
for (const definition of HARNESS_FILES) {
|
|
@@ -30,7 +30,6 @@ export function createSessionService(deps) {
|
|
|
30
30
|
const isCodexRole = isCodexRoleName(role);
|
|
31
31
|
const isTranslator = role === CODEX_TRANSLATOR_ROLE;
|
|
32
32
|
const sessionRepoRoot = isTranslator ? repoRoot : taskRepoRoot;
|
|
33
|
-
const sessionLogPath = isTranslator ? undefined : paths.roleLogPaths[role];
|
|
34
33
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
35
34
|
const model = isCodexRole
|
|
36
35
|
? normalizeCodexModel(input.model ?? persisted?.model)
|
|
@@ -72,8 +71,7 @@ export function createSessionService(deps) {
|
|
|
72
71
|
VCM_SESSION_ID: claudeSessionId
|
|
73
72
|
},
|
|
74
73
|
cols: input.cols,
|
|
75
|
-
rows: input.rows
|
|
76
|
-
...(sessionLogPath ? { logPath: resolveRepoPath(sessionRepoRoot, sessionLogPath) } : {})
|
|
74
|
+
rows: input.rows
|
|
77
75
|
});
|
|
78
76
|
const timestamp = now();
|
|
79
77
|
const record = {
|
|
@@ -91,7 +89,6 @@ export function createSessionService(deps) {
|
|
|
91
89
|
cwd: startCommand.cwd,
|
|
92
90
|
terminalBackend: "node-pty",
|
|
93
91
|
pid: runtimeSession.pid,
|
|
94
|
-
...(sessionLogPath ? { logPath: sessionLogPath } : {}),
|
|
95
92
|
roleCommandPath: isDispatchableRole(role)
|
|
96
93
|
? paths.roleCommandPaths[role]
|
|
97
94
|
: undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPATCHABLE_ROLES
|
|
1
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
2
2
|
import { isOpenFileLimitError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
4
4
|
export function createStatusService(deps) {
|
|
@@ -45,18 +45,13 @@ export function createStatusService(deps) {
|
|
|
45
45
|
}
|
|
46
46
|
function degradedArtifactSummary(handoffDir) {
|
|
47
47
|
const roleCommandsDir = `${handoffDir}/role-commands`;
|
|
48
|
-
const logsDir = `${handoffDir}/logs`;
|
|
49
48
|
const messagesDir = `${handoffDir}/messages`;
|
|
50
49
|
return {
|
|
51
50
|
paths: {
|
|
52
51
|
handoffDir,
|
|
53
52
|
roleCommandsDir,
|
|
54
|
-
logsDir,
|
|
55
53
|
messagesDir,
|
|
56
54
|
roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
|
|
57
|
-
roleLogPaths: Object.fromEntries(ROLE_NAMES
|
|
58
|
-
.filter((role) => role !== "codex-translator")
|
|
59
|
-
.map((role) => [role, `${logsDir}/${role}.log`])),
|
|
60
55
|
messageRoutePaths: {},
|
|
61
56
|
architecturePlanPath: `${handoffDir}/architecture-plan.md`,
|
|
62
57
|
knownIssuesPath: `${handoffDir}/known-issues.md`,
|