vibe-coding-master 0.3.32 → 0.4.1

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
@@ -486,7 +486,7 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
486
486
 
487
487
  VCM also JSON-merges `.claude/settings.json` to install Claude Code `PreToolUse`, `UserPromptSubmit`, `Stop`, and `PermissionRequest` hooks plus a managed `env.BASH_DEFAULT_TIMEOUT_MS` so foreground watch windows fit inside the Bash tool timeout. The hooks post directly to the local VCM backend, so roles do not need a VCM CLI command to confirm delivery or report turn completion. The `Stop` hook forwards the backend response to Claude Code, which lets VCM block turn-end while a validation job is still running.
488
488
 
489
- Bootstrap is AI-assisted. VCM starts a visible temporary Claude Code session in the connected repository and asks it to use the `vcm-harness-bootstrap` skill. Bootstrap fills project-specific content and generated context:
489
+ Bootstrap is AI-assisted. VCM starts or resumes the project-scoped Harness Engineer Claude Code session in the connected repository and asks it to use the `vcm-harness-bootstrap` skill. Bootstrap fills project-specific content and generated context:
490
490
 
491
491
  ```text
492
492
  CLAUDE.md project context outside the VCM managed block
@@ -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 = [
@@ -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 });
@@ -165,14 +167,6 @@ export function createDefaultServerDeps(options = {}) {
165
167
  const registry = createSessionRegistry();
166
168
  const artifactService = createArtifactService(fs);
167
169
  const projectService = createProjectService({ fs, git, appSettings });
168
- const harnessService = createHarnessService({
169
- fs,
170
- git,
171
- runtime,
172
- projectService,
173
- apiUrl: options.apiUrl,
174
- runFixedInstaller: createScriptFixedHarnessInstaller(path.join(getAppRoot(), "scripts/install-vcm-harness.mjs"))
175
- });
176
170
  const taskService = createTaskService({ fs, git, artifactService, projectService });
177
171
  const sessionService = createSessionService({
178
172
  fs,
@@ -184,6 +178,13 @@ export function createDefaultServerDeps(options = {}) {
184
178
  taskService,
185
179
  apiUrl: options.apiUrl
186
180
  });
181
+ const harnessService = createHarnessService({
182
+ fs,
183
+ git,
184
+ runtime,
185
+ harnessEngineerSessions: sessionService,
186
+ runFixedInstaller: createScriptFixedHarnessInstaller(path.join(getAppRoot(), "scripts/install-vcm-harness.mjs"))
187
+ });
187
188
  const commandDispatcher = createCommandDispatcher({
188
189
  runtime,
189
190
  sessionService,
@@ -262,6 +263,7 @@ export function createDefaultServerDeps(options = {}) {
262
263
  translationService,
263
264
  appSettings,
264
265
  runtime,
266
+ harnessService,
265
267
  gatewayService,
266
268
  jobGuard: createJobGuardService(),
267
269
  translationWorkerService
@@ -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,29 @@ 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
+ await deps.harnessService?.recordHarnessBootstrapHook(context.project.repoRoot, {
87
+ eventName,
88
+ sessionId: session?.id,
89
+ claudeSessionId: stringOrUndefined(input.event.session_id)
90
+ });
91
+ return {
92
+ ok: true,
93
+ eventName,
94
+ taskSlug: input.taskSlug,
95
+ role: input.role,
96
+ sessionUpdated: Boolean(session),
97
+ dispatchedCount: 0
98
+ };
99
+ }
66
100
  async function resolveHookTaskSlug(repoRoot, input) {
67
101
  if (!isGateReviewerRoleName(input.role)) {
68
102
  return input.taskSlug;
@@ -337,7 +371,7 @@ export function createClaudeHookService(deps) {
337
371
  }
338
372
  async function handlePermissionRequestHook(input) {
339
373
  if (!isVcmRoleName(input.role)) {
340
- if (isTranslatorToolRoleName(input.role)) {
374
+ if (isTranslatorToolRoleName(input.role) || isHarnessEngineerToolRoleName(input.role)) {
341
375
  const preferences = await deps.appSettings.getPreferences();
342
376
  if (preferences.permissionRequestMode !== "allowAll") {
343
377
  return undefined;
@@ -384,6 +418,9 @@ export function createClaudeHookService(deps) {
384
418
  if (isTranslatorToolRoleName(input.role)) {
385
419
  return processTranslatorHook(input);
386
420
  }
421
+ if (isHarnessEngineerToolRoleName(input.role)) {
422
+ return processHarnessEngineerHook(input);
423
+ }
387
424
  const eventName = parseHookEvent(input.event.hook_event_name);
388
425
  if (eventName === "UserPromptSubmit") {
389
426
  return handleUserPromptSubmitHook(input);
@@ -402,6 +439,9 @@ export function createClaudeHookService(deps) {
402
439
  if (isTranslatorToolRoleName(input.role)) {
403
440
  return processTranslatorHook(input);
404
441
  }
442
+ if (isHarnessEngineerToolRoleName(input.role)) {
443
+ return processHarnessEngineerHook(input);
444
+ }
405
445
  return processStopHook(input, { allowBlock: true });
406
446
  },
407
447
  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
+ }