vibe-coding-master 0.3.32 → 0.4.0
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/api/harness-routes.js +56 -0
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/translation-routes.js +4 -0
- package/dist/backend/cli/install-vcm-harness.js +12 -0
- package/dist/backend/server.js +2 -0
- package/dist/backend/services/claude-hook-service.js +37 -2
- package/dist/backend/services/harness-revision.js +32 -0
- package/dist/backend/services/harness-service.js +314 -53
- package/dist/backend/services/session-service.js +385 -13
- package/dist/backend/templates/harness/harness-engineer-agent.js +63 -0
- package/dist/shared/constants.js +11 -1
- package/dist-frontend/assets/index-CMuJhDdX.js +94 -0
- package/dist-frontend/assets/index-kJDZAzjD.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/v0.4-harness-optimization-plan.md +662 -0
- package/docs/{v0.4-custom-workflow-plan.md → v0.5-custom-workflow-plan.md} +15 -11
- package/package.json +1 -1
- package/dist-frontend/assets/index-BaIbh99h.js +0 -94
- package/dist-frontend/assets/index-BvUHLq4X.css +0 -32
|
@@ -16,6 +16,21 @@ 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.get("/api/projects/harness/file", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
return deps.harnessService.getHarnessFileContent(project.repoRoot, request.query.path ?? "");
|
|
22
|
+
});
|
|
23
|
+
app.put("/api/projects/harness/file", async (request) => {
|
|
24
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
25
|
+
if (typeof request.body?.content !== "string") {
|
|
26
|
+
throw new VcmError({
|
|
27
|
+
code: "HARNESS_FILE_CONTENT_INVALID",
|
|
28
|
+
message: "Harness file content must be a string.",
|
|
29
|
+
statusCode: 400
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return deps.harnessService.updateHarnessFileContent(project.repoRoot, request.query.path ?? "", request.body.content);
|
|
33
|
+
});
|
|
19
34
|
app.post("/api/projects/harness/tasks/:taskSlug/commit-and-rebase", async (request) => {
|
|
20
35
|
const project = await requireCurrentProject(deps.projectService);
|
|
21
36
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
@@ -42,10 +57,51 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
42
57
|
const project = await requireCurrentProject(deps.projectService);
|
|
43
58
|
return deps.harnessService.startHarnessBootstrap(project.repoRoot, request.body ?? {});
|
|
44
59
|
});
|
|
60
|
+
app.post("/api/projects/harness/bootstrap/restart", async (request) => {
|
|
61
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
62
|
+
return deps.harnessService.restartHarnessBootstrap(project.repoRoot, request.body ?? {});
|
|
63
|
+
});
|
|
64
|
+
app.post("/api/projects/harness/bootstrap/stop", async () => {
|
|
65
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
66
|
+
return deps.harnessService.stopHarnessBootstrap(project.repoRoot);
|
|
67
|
+
});
|
|
68
|
+
app.post("/api/projects/harness/bootstrap/run", async () => {
|
|
69
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
70
|
+
return deps.harnessService.runHarnessBootstrap(project.repoRoot);
|
|
71
|
+
});
|
|
72
|
+
app.get("/api/projects/harness/engineer/session", async () => {
|
|
73
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
74
|
+
return (await deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot)) ?? null;
|
|
75
|
+
});
|
|
76
|
+
app.post("/api/projects/harness/engineer/session/ensure", async (request) => {
|
|
77
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
78
|
+
return deps.sessionService.ensureProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
79
|
+
});
|
|
80
|
+
app.post("/api/projects/harness/engineer/session/start", async (request) => {
|
|
81
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
82
|
+
return deps.sessionService.startProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
83
|
+
});
|
|
84
|
+
app.post("/api/projects/harness/engineer/session/resume", async (request) => {
|
|
85
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
86
|
+
return deps.sessionService.resumeProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
87
|
+
});
|
|
88
|
+
app.post("/api/projects/harness/engineer/session/restart", async (request) => {
|
|
89
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
90
|
+
return deps.sessionService.restartProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
91
|
+
});
|
|
92
|
+
app.post("/api/projects/harness/engineer/session/stop", async () => {
|
|
93
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
94
|
+
return deps.sessionService.stopProjectHarnessEngineerSession(project.repoRoot);
|
|
95
|
+
});
|
|
96
|
+
app.post("/api/projects/harness/engineer/session/notify-harness", async () => {
|
|
97
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
98
|
+
return deps.sessionService.notifyProjectHarnessEngineerHarnessUpdated(project.repoRoot);
|
|
99
|
+
});
|
|
45
100
|
}
|
|
46
101
|
function degradedHarnessStatus(error) {
|
|
47
102
|
return {
|
|
48
103
|
version: 1,
|
|
104
|
+
harnessRevision: 0,
|
|
49
105
|
initialized: false,
|
|
50
106
|
files: [],
|
|
51
107
|
needsApply: false,
|
|
@@ -33,6 +33,11 @@ export function registerSessionRoutes(app, deps) {
|
|
|
33
33
|
const role = parseRole(request.params.role);
|
|
34
34
|
return deps.sessionService.resumeRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
|
|
35
35
|
});
|
|
36
|
+
app.post("/api/tasks/:taskSlug/sessions/:role/notify-harness", async (request) => {
|
|
37
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
38
|
+
const role = parseRole(request.params.role);
|
|
39
|
+
return deps.sessionService.notifyRoleHarnessUpdated(project.repoRoot, request.params.taskSlug, role);
|
|
40
|
+
});
|
|
36
41
|
app.post("/api/tasks/:taskSlug/sessions/:role/dispatch", async (request) => {
|
|
37
42
|
const project = await requireCurrentProject(deps.projectService);
|
|
38
43
|
if (!isDispatchableRole(request.params.role)) {
|
|
@@ -52,6 +52,10 @@ export function registerTranslationRoutes(app, deps) {
|
|
|
52
52
|
await deps.translationService.stopSession(request.params.sessionId);
|
|
53
53
|
return { ok: true };
|
|
54
54
|
});
|
|
55
|
+
app.post("/api/projects/translation/session/notify-harness", async () => {
|
|
56
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
57
|
+
return deps.sessionService.notifyProjectTranslatorHarnessUpdated(project.repoRoot);
|
|
58
|
+
});
|
|
55
59
|
app.post("/api/translation/sessions/:sessionId/retry/:translationId", async (request) => {
|
|
56
60
|
await requireCurrentProject(deps.projectService);
|
|
57
61
|
return deps.translationService.retryTranslation(request.params.sessionId, request.params.translationId);
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
|
|
8
8
|
import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
|
|
9
9
|
import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
|
|
10
|
+
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
10
11
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
11
12
|
import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
|
|
12
13
|
import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
|
|
@@ -59,6 +60,9 @@ const AGENT_FRONTMATTER = {
|
|
|
59
60
|
},
|
|
60
61
|
translator: {
|
|
61
62
|
description: "VCM project translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
63
|
+
},
|
|
64
|
+
"harness-engineer": {
|
|
65
|
+
description: "VCM project-scoped harness maintenance role for harness diagnosis, diff proposals, and VCM issue drafts."
|
|
62
66
|
}
|
|
63
67
|
};
|
|
64
68
|
const MANAGED_FILES = [
|
|
@@ -132,6 +136,14 @@ const MANAGED_FILES = [
|
|
|
132
136
|
commentStyle: "html",
|
|
133
137
|
category: "agent-translator",
|
|
134
138
|
content: renderTranslatorAgentRules()
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
path: ".claude/agents/harness-engineer.md",
|
|
142
|
+
title: "Harness Engineer Agent",
|
|
143
|
+
agentName: "harness-engineer",
|
|
144
|
+
commentStyle: "html",
|
|
145
|
+
category: "agent-harness-engineer",
|
|
146
|
+
content: renderHarnessEngineerHarnessRules()
|
|
135
147
|
}
|
|
136
148
|
];
|
|
137
149
|
const DURABLE_DOC_TEMPLATES = [
|
package/dist/backend/server.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function createServer(deps, options = {}) {
|
|
|
79
79
|
registerHarnessRoutes(app, {
|
|
80
80
|
projectService: deps.projectService,
|
|
81
81
|
harnessService: deps.harnessService,
|
|
82
|
+
sessionService: deps.sessionService,
|
|
82
83
|
taskService: deps.taskService
|
|
83
84
|
});
|
|
84
85
|
registerTaskRoutes(app, {
|
|
@@ -114,6 +115,7 @@ export async function createServer(deps, options = {}) {
|
|
|
114
115
|
registerTranslationRoutes(app, {
|
|
115
116
|
projectService: deps.projectService,
|
|
116
117
|
taskService: deps.taskService,
|
|
118
|
+
sessionService: deps.sessionService,
|
|
117
119
|
translationService: deps.translationService
|
|
118
120
|
});
|
|
119
121
|
registerGatewayRoutes(app, { gatewayService: deps.gatewayService });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isGateReviewerRoleName, isTranslatorToolRoleName, isVcmRoleName } from "../../shared/constants.js";
|
|
1
|
+
import { isGateReviewerRoleName, isHarnessEngineerToolRoleName, isTranslatorToolRoleName, isVcmRoleName } from "../../shared/constants.js";
|
|
2
2
|
import { VcmError } from "../errors.js";
|
|
3
3
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
4
4
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
@@ -44,6 +44,17 @@ export function createClaudeHookService(deps) {
|
|
|
44
44
|
}
|
|
45
45
|
return { project };
|
|
46
46
|
}
|
|
47
|
+
async function getProjectToolHookContext(toolLabel) {
|
|
48
|
+
const project = await deps.projectService.getCurrentProject();
|
|
49
|
+
if (!project) {
|
|
50
|
+
throw new VcmError({
|
|
51
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
52
|
+
message: `Connect a repository before accepting ${toolLabel} hooks.`,
|
|
53
|
+
statusCode: 409
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { project };
|
|
57
|
+
}
|
|
47
58
|
async function processTranslatorHook(input) {
|
|
48
59
|
const eventName = parseHookEvent(input.event.hook_event_name);
|
|
49
60
|
const context = await getTranslatorHookContext();
|
|
@@ -63,6 +74,24 @@ export function createClaudeHookService(deps) {
|
|
|
63
74
|
dispatchedCount: 0
|
|
64
75
|
};
|
|
65
76
|
}
|
|
77
|
+
async function processHarnessEngineerHook(input) {
|
|
78
|
+
const eventName = parseHookEvent(input.event.hook_event_name);
|
|
79
|
+
const context = await getProjectToolHookContext("Harness Engineer");
|
|
80
|
+
const session = await deps.sessionService.recordProjectHarnessEngineerHookEvent(context.project.repoRoot, {
|
|
81
|
+
eventName,
|
|
82
|
+
sessionId: stringOrUndefined(input.event.session_id),
|
|
83
|
+
transcriptPath: stringOrUndefined(input.event.transcript_path),
|
|
84
|
+
cwd: stringOrUndefined(input.event.cwd)
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
eventName,
|
|
89
|
+
taskSlug: input.taskSlug,
|
|
90
|
+
role: input.role,
|
|
91
|
+
sessionUpdated: Boolean(session),
|
|
92
|
+
dispatchedCount: 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
66
95
|
async function resolveHookTaskSlug(repoRoot, input) {
|
|
67
96
|
if (!isGateReviewerRoleName(input.role)) {
|
|
68
97
|
return input.taskSlug;
|
|
@@ -337,7 +366,7 @@ export function createClaudeHookService(deps) {
|
|
|
337
366
|
}
|
|
338
367
|
async function handlePermissionRequestHook(input) {
|
|
339
368
|
if (!isVcmRoleName(input.role)) {
|
|
340
|
-
if (isTranslatorToolRoleName(input.role)) {
|
|
369
|
+
if (isTranslatorToolRoleName(input.role) || isHarnessEngineerToolRoleName(input.role)) {
|
|
341
370
|
const preferences = await deps.appSettings.getPreferences();
|
|
342
371
|
if (preferences.permissionRequestMode !== "allowAll") {
|
|
343
372
|
return undefined;
|
|
@@ -384,6 +413,9 @@ export function createClaudeHookService(deps) {
|
|
|
384
413
|
if (isTranslatorToolRoleName(input.role)) {
|
|
385
414
|
return processTranslatorHook(input);
|
|
386
415
|
}
|
|
416
|
+
if (isHarnessEngineerToolRoleName(input.role)) {
|
|
417
|
+
return processHarnessEngineerHook(input);
|
|
418
|
+
}
|
|
387
419
|
const eventName = parseHookEvent(input.event.hook_event_name);
|
|
388
420
|
if (eventName === "UserPromptSubmit") {
|
|
389
421
|
return handleUserPromptSubmitHook(input);
|
|
@@ -402,6 +434,9 @@ export function createClaudeHookService(deps) {
|
|
|
402
434
|
if (isTranslatorToolRoleName(input.role)) {
|
|
403
435
|
return processTranslatorHook(input);
|
|
404
436
|
}
|
|
437
|
+
if (isHarnessEngineerToolRoleName(input.role)) {
|
|
438
|
+
return processHarnessEngineerHook(input);
|
|
439
|
+
}
|
|
405
440
|
return processStopHook(input, { allowBlock: true });
|
|
406
441
|
},
|
|
407
442
|
handlePermissionRequestHook
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
2
|
+
const HARNESS_REVISION_PATH = ".ai/vcm/harness/revision.json";
|
|
3
|
+
export async function readHarnessRevisionState(fs, repoRoot) {
|
|
4
|
+
const revisionPath = resolveRepoPath(repoRoot, HARNESS_REVISION_PATH);
|
|
5
|
+
if (!await fs.pathExists(revisionPath)) {
|
|
6
|
+
return {
|
|
7
|
+
version: 1,
|
|
8
|
+
revision: 0
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const payload = await fs.readJson(revisionPath);
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
revision: normalizeRevision(payload.revision),
|
|
15
|
+
updatedAt: typeof payload.updatedAt === "string" ? payload.updatedAt : undefined
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export async function bumpHarnessRevision(fs, repoRoot, updatedAt) {
|
|
19
|
+
const current = await readHarnessRevisionState(fs, repoRoot);
|
|
20
|
+
const next = {
|
|
21
|
+
version: 1,
|
|
22
|
+
revision: current.revision + 1,
|
|
23
|
+
updatedAt
|
|
24
|
+
};
|
|
25
|
+
await fs.writeJsonAtomic(resolveRepoPath(repoRoot, HARNESS_REVISION_PATH), next);
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
function normalizeRevision(value) {
|
|
29
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0
|
|
30
|
+
? value
|
|
31
|
+
: 0;
|
|
32
|
+
}
|