vibe-coding-master 0.3.31 → 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.
@@ -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 = [
@@ -44,6 +44,7 @@ export function createNodePtyTerminalRuntime(deps) {
44
44
  process: child,
45
45
  listeners: new Set(),
46
46
  logWriter,
47
+ replayBuffer: "",
47
48
  disposed: false
48
49
  };
49
50
  entries.set(session.id, entry);
@@ -52,6 +53,7 @@ export function createNodePtyTerminalRuntime(deps) {
52
53
  return;
53
54
  }
54
55
  entry.session.lastOutputAt = now();
56
+ entry.replayBuffer = appendTerminalReplay(entry.replayBuffer, data);
55
57
  entry.logWriter.append(data);
56
58
  emit(entry, {
57
59
  sessionId: session.id,
@@ -138,7 +140,18 @@ export function createNodePtyTerminalRuntime(deps) {
138
140
  subscribe(sessionId, listener, options = {}) {
139
141
  const entry = getEntry(entries, sessionId);
140
142
  entry.listeners.add(listener);
141
- if (options.replay !== false && entry.input.logPath) {
143
+ if (options.replay !== false && entry.replayBuffer) {
144
+ listener({
145
+ id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
146
+ sessionId,
147
+ taskSlug: entry.session.taskSlug,
148
+ role: entry.session.role,
149
+ type: "output",
150
+ timestamp: now(),
151
+ data: entry.replayBuffer
152
+ });
153
+ }
154
+ else if (options.replay !== false && entry.input.logPath) {
142
155
  void readTerminalReplayText(deps.fs, entry.input.logPath)
143
156
  .then((data) => {
144
157
  if (!data || !entry.listeners.has(listener)) {
@@ -228,6 +241,12 @@ export function tailTerminalReplay(data, limitBytes = TERMINAL_REPLAY_TAIL_LIMIT
228
241
  const firstLineBreak = tail.indexOf("\n");
229
242
  return firstLineBreak >= 0 ? tail.slice(firstLineBreak + 1) : tail;
230
243
  }
244
+ export function appendTerminalReplay(current, data, limitBytes = TERMINAL_REPLAY_TAIL_LIMIT_BYTES) {
245
+ if (!data) {
246
+ return current;
247
+ }
248
+ return tailTerminalReplay(`${current}${data}`, limitBytes);
249
+ }
231
250
  export function buildPtyEnvironment(baseEnv, inputEnv = {}) {
232
251
  const env = {
233
252
  ...baseEnv,
@@ -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
+ }