vibe-coding-master 0.4.26 → 0.4.27

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.
@@ -268,7 +268,6 @@ async function main() {
268
268
  const operations = [];
269
269
  await assertDirectory(projectRoot, "Project root");
270
270
  const manifest = await buildManifest(projectRoot);
271
- await installManifest({ projectRoot, manifest, dryRun, operations });
272
271
  for (const definition of MANAGED_FILES) {
273
272
  await installManagedFile({ projectRoot, definition, dryRun, operations });
274
273
  }
@@ -284,6 +283,13 @@ async function main() {
284
283
  }
285
284
  await removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations });
286
285
  await removeLegacyCodexHarnessPaths({ projectRoot, dryRun, operations });
286
+ await installManifest({
287
+ projectRoot,
288
+ manifest,
289
+ dryRun,
290
+ operations,
291
+ forceWrite: hasRealHarnessChange(operations)
292
+ });
287
293
  printReport({ projectRoot, dryRun, operations });
288
294
  }
289
295
  function parseArgs(argv) {
@@ -449,13 +455,17 @@ function directoryCategory(directory) {
449
455
  }
450
456
  return "harness-tool-directory";
451
457
  }
452
- async function installManifest({ projectRoot, manifest, dryRun, operations }) {
458
+ async function installManifest({ projectRoot, manifest, dryRun, operations, forceWrite = false }) {
453
459
  const targetPath = path.join(projectRoot, MANIFEST_PATH);
454
460
  const currentManifest = await readOptionalJson(targetPath);
455
461
  if (currentManifest && manifestBodyEqual(currentManifest, manifest)) {
456
462
  operations.push(skip(MANIFEST_PATH, "unchanged"));
457
463
  return;
458
464
  }
465
+ if (!forceWrite && currentManifest && manifestBodyEqual(currentManifest, manifest, { ignoreHarnessVersion: true })) {
466
+ operations.push(skip(MANIFEST_PATH, "version-only change ignored"));
467
+ return;
468
+ }
459
469
  await writeIfChanged({
460
470
  targetPath,
461
471
  relativePath: MANIFEST_PATH,
@@ -731,15 +741,23 @@ async function pathExists(absolutePath) {
731
741
  throw error;
732
742
  });
733
743
  }
734
- function manifestBodyEqual(left, right) {
744
+ function manifestBodyEqual(left, right, options = {}) {
735
745
  const normalizedLeft = { ...left };
736
746
  const normalizedRight = { ...right };
737
747
  delete normalizedLeft.installedAt;
738
748
  delete normalizedLeft.updatedAt;
739
749
  delete normalizedRight.installedAt;
740
750
  delete normalizedRight.updatedAt;
751
+ if (options.ignoreHarnessVersion) {
752
+ delete normalizedLeft.harnessVersion;
753
+ delete normalizedRight.harnessVersion;
754
+ }
741
755
  return JSON.stringify(normalizedLeft) === JSON.stringify(normalizedRight);
742
756
  }
757
+ function hasRealHarnessChange(operations) {
758
+ return operations.some((operation) => operation.path !== MANIFEST_PATH &&
759
+ (operation.status === "done" || operation.status === "plan"));
760
+ }
743
761
  function resolveInside(root, relativePath) {
744
762
  if (path.isAbsolute(relativePath)) {
745
763
  fail(`Path must be relative: ${relativePath}`);
@@ -1284,11 +1284,7 @@ async function analyzeHarnessManifest(fs, repoRoot, vcmVersion) {
1284
1284
  };
1285
1285
  }
1286
1286
  if (installedVersion !== vcmVersion) {
1287
- return {
1288
- path: MANIFEST_PATH,
1289
- action: "update",
1290
- reason: `VCM fixed harness manifest version is ${installedVersion ?? "missing"}; current VCM version is ${vcmVersion}.`
1291
- };
1287
+ return undefined;
1292
1288
  }
1293
1289
  return undefined;
1294
1290
  }
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import path from "node:path";
3
2
  import { VCM_ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
4
3
  import { VcmError } from "../errors.js";
@@ -43,10 +42,10 @@ export function createSessionService(deps) {
43
42
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
44
43
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
45
44
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
46
- const claudeSessionId = launchMode === "resume"
45
+ const resumeClaudeSessionId = launchMode === "resume"
47
46
  ? persisted?.claudeSessionId
48
- : randomUUID();
49
- if (!claudeSessionId) {
47
+ : undefined;
48
+ if (launchMode === "resume" && !resumeClaudeSessionId) {
50
49
  throw new VcmError({
51
50
  code: "CLAUDE_SESSION_MISSING",
52
51
  message: `${role} does not have a session id to resume.`,
@@ -54,11 +53,14 @@ export function createSessionService(deps) {
54
53
  hint: "Start the role once before using Resume."
55
54
  });
56
55
  }
56
+ const claudeSessionId = resumeClaudeSessionId ?? "";
57
57
  const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
58
58
  ? persisted.transcriptPath
59
- : claudeTranscriptPath(taskRepoRoot, claudeSessionId);
59
+ : resumeClaudeSessionId
60
+ ? claudeTranscriptPath(taskRepoRoot, resumeClaudeSessionId)
61
+ : undefined;
60
62
  const startCommand = {
61
- ...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
63
+ ...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
62
64
  cwd: taskRepoRoot
63
65
  };
64
66
  const runtimeSession = await deps.runtime.createSession({
@@ -73,7 +75,7 @@ export function createSessionService(deps) {
73
75
  VCM_TASK_REPO_ROOT: taskRepoRoot,
74
76
  VCM_TASK_SLUG: taskSlug,
75
77
  VCM_ROLE: role,
76
- VCM_SESSION_ID: claudeSessionId
78
+ VCM_SESSION_ID: claudeSessionId || undefined
77
79
  },
78
80
  cols: input.cols,
79
81
  rows: input.rows
@@ -120,10 +122,10 @@ export function createSessionService(deps) {
120
122
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
121
123
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
122
124
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
123
- const claudeSessionId = launchMode === "resume"
125
+ const resumeClaudeSessionId = launchMode === "resume"
124
126
  ? persisted?.claudeSessionId
125
- : randomUUID();
126
- if (!claudeSessionId) {
127
+ : undefined;
128
+ if (launchMode === "resume" && !resumeClaudeSessionId) {
127
129
  throw new VcmError({
128
130
  code: "TRANSLATOR_SESSION_MISSING",
129
131
  message: "Translator does not have a session id to resume.",
@@ -135,11 +137,14 @@ export function createSessionService(deps) {
135
137
  const launchCwd = launchMode === "resume"
136
138
  ? persisted?.cwd ?? taskContext.taskRepoRoot
137
139
  : taskContext.taskRepoRoot;
140
+ const claudeSessionId = resumeClaudeSessionId ?? "";
138
141
  const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
139
142
  ? persisted.transcriptPath
140
- : claudeTranscriptPath(launchCwd, claudeSessionId);
143
+ : resumeClaudeSessionId
144
+ ? claudeTranscriptPath(launchCwd, resumeClaudeSessionId)
145
+ : undefined;
141
146
  const startCommand = {
142
- ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
147
+ ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
143
148
  cwd: launchCwd
144
149
  };
145
150
  const runtimeSession = await deps.runtime.createSession({
@@ -154,7 +159,7 @@ export function createSessionService(deps) {
154
159
  VCM_TASK_REPO_ROOT: taskContext.taskRepoRoot,
155
160
  VCM_TASK_SLUG: taskContext.taskSlug,
156
161
  VCM_ROLE: TRANSLATOR_ROLE,
157
- VCM_SESSION_ID: claudeSessionId
162
+ VCM_SESSION_ID: claudeSessionId || undefined
158
163
  },
159
164
  cols: input.cols,
160
165
  rows: input.rows
@@ -197,10 +202,10 @@ export function createSessionService(deps) {
197
202
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
198
203
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
199
204
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
200
- const claudeSessionId = launchMode === "resume"
205
+ const resumeClaudeSessionId = launchMode === "resume"
201
206
  ? persisted?.claudeSessionId
202
- : randomUUID();
203
- if (!claudeSessionId) {
207
+ : undefined;
208
+ if (launchMode === "resume" && !resumeClaudeSessionId) {
204
209
  throw new VcmError({
205
210
  code: "HARNESS_ENGINEER_SESSION_MISSING",
206
211
  message: "Harness Engineer does not have a session id to resume.",
@@ -212,11 +217,14 @@ export function createSessionService(deps) {
212
217
  const launchCwd = launchMode === "resume"
213
218
  ? persisted?.cwd ?? taskContext.taskRepoRoot
214
219
  : taskContext.taskRepoRoot;
220
+ const claudeSessionId = resumeClaudeSessionId ?? "";
215
221
  const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
216
222
  ? persisted.transcriptPath
217
- : claudeTranscriptPath(launchCwd, claudeSessionId);
223
+ : resumeClaudeSessionId
224
+ ? claudeTranscriptPath(launchCwd, resumeClaudeSessionId)
225
+ : undefined;
218
226
  const startCommand = {
219
- ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
227
+ ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
220
228
  cwd: launchCwd
221
229
  };
222
230
  const runtimeSession = await deps.runtime.createSession({
@@ -231,7 +239,7 @@ export function createSessionService(deps) {
231
239
  VCM_TASK_REPO_ROOT: taskContext.taskRepoRoot,
232
240
  VCM_TASK_SLUG: taskContext.taskSlug,
233
241
  VCM_ROLE: HARNESS_ENGINEER_ROLE,
234
- VCM_SESSION_ID: claudeSessionId
242
+ VCM_SESSION_ID: claudeSessionId || undefined
235
243
  },
236
244
  cols: input.cols,
237
245
  rows: input.rows
@@ -298,7 +306,9 @@ export function createSessionService(deps) {
298
306
  ...session,
299
307
  cwd: targetCwd,
300
308
  previousCwd: session.cwd,
301
- transcriptPath: claudeTranscriptPath(targetCwd, session.claudeSessionId),
309
+ transcriptPath: session.claudeSessionId
310
+ ? claudeTranscriptPath(targetCwd, session.claudeSessionId)
311
+ : session.transcriptPath,
302
312
  updatedAt: timestamp
303
313
  };
304
314
  deps.registry.upsert(normalizeProjectScopedRecordForPersistence(updated));
@@ -509,10 +519,11 @@ export function createSessionService(deps) {
509
519
  const timestamp = now();
510
520
  const isTurnEnd = isTurnEndHook(input.eventName);
511
521
  const isCompact = isCompactHook(input.eventName);
522
+ const sessionIdentity = nextHookSessionIdentity(current, input);
512
523
  const updated = {
513
524
  ...current,
514
- claudeSessionId: input.sessionId ?? current.claudeSessionId,
515
- transcriptPath: input.transcriptPath ?? current.transcriptPath,
525
+ claudeSessionId: sessionIdentity.claudeSessionId,
526
+ transcriptPath: sessionIdentity.transcriptPath,
516
527
  cwd: input.cwd ?? current.cwd,
517
528
  activityStatus: isTurnEnd ? "idle" : isCompact ? current.activityStatus : "running",
518
529
  lastHookEventAt: timestamp,
@@ -633,10 +644,11 @@ export function createSessionService(deps) {
633
644
  const timestamp = now();
634
645
  const isTurnEnd = isTurnEndHook(input.eventName);
635
646
  const isCompact = isCompactHook(input.eventName);
647
+ const sessionIdentity = nextHookSessionIdentity(current, input);
636
648
  const updated = {
637
649
  ...current,
638
- claudeSessionId: input.sessionId ?? current.claudeSessionId,
639
- transcriptPath: input.transcriptPath ?? current.transcriptPath,
650
+ claudeSessionId: sessionIdentity.claudeSessionId,
651
+ transcriptPath: sessionIdentity.transcriptPath,
640
652
  cwd: input.cwd ?? current.cwd,
641
653
  activityStatus: isTurnEnd ? "idle" : isCompact ? current.activityStatus : "running",
642
654
  lastHookEventAt: timestamp,
@@ -800,10 +812,11 @@ export function createSessionService(deps) {
800
812
  const timestamp = now();
801
813
  const isTurnEnd = isTurnEndHook(input.eventName);
802
814
  const isCompact = isCompactHook(input.eventName);
815
+ const sessionIdentity = nextHookSessionIdentity(current, input);
803
816
  const updated = {
804
817
  ...current,
805
- claudeSessionId: input.sessionId ?? current.claudeSessionId,
806
- transcriptPath: input.transcriptPath ?? current.transcriptPath,
818
+ claudeSessionId: sessionIdentity.claudeSessionId,
819
+ transcriptPath: sessionIdentity.transcriptPath,
807
820
  cwd: input.cwd ?? current.cwd,
808
821
  activityStatus: isTurnEnd ? "idle" : isCompact ? current.activityStatus : "running",
809
822
  lastHookEventAt: timestamp,
@@ -890,6 +903,9 @@ function toRoleSessionRecordView(record, runtime) {
890
903
  };
891
904
  }
892
905
  function matchesRoleHookSession(record, input) {
906
+ if (!record.claudeSessionId && !record.transcriptPath) {
907
+ return input.eventName === "UserPromptSubmit";
908
+ }
893
909
  if (input.sessionId && record.claudeSessionId === input.sessionId) {
894
910
  return true;
895
911
  }
@@ -901,6 +917,20 @@ function matchesRoleHookSession(record, input) {
901
917
  }
902
918
  return false;
903
919
  }
920
+ function nextHookSessionIdentity(current, input) {
921
+ const canRecordFirstClaudeSessionId = Boolean(!current.claudeSessionId &&
922
+ input.eventName === "UserPromptSubmit" &&
923
+ input.sessionId);
924
+ const hasConfirmedClaudeSessionId = Boolean(current.claudeSessionId || canRecordFirstClaudeSessionId);
925
+ return {
926
+ claudeSessionId: canRecordFirstClaudeSessionId
927
+ ? input.sessionId ?? current.claudeSessionId
928
+ : current.claudeSessionId,
929
+ transcriptPath: hasConfirmedClaudeSessionId
930
+ ? input.transcriptPath ?? current.transcriptPath
931
+ : current.transcriptPath
932
+ };
933
+ }
904
934
  function isTurnEndHook(eventName) {
905
935
  return eventName === "Stop" || eventName === "StopFailure";
906
936
  }
@@ -1068,6 +1098,9 @@ function buildHarnessRefreshPrompt(role) {
1068
1098
  ].join("\n");
1069
1099
  }
1070
1100
  async function persistTaskSession(fs, repoRoot, stateRoot, session) {
1101
+ if (!hasConfirmedClaudeSessionId(session)) {
1102
+ return;
1103
+ }
1071
1104
  const sessionPath = getTaskSessionPath(repoRoot, stateRoot, session.taskSlug);
1072
1105
  const empty = createEmptyTaskSessionRecord(session.taskSlug, session.updatedAt);
1073
1106
  const current = await fs.pathExists(sessionPath)
@@ -1104,6 +1137,9 @@ async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoo
1104
1137
  await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
1105
1138
  }
1106
1139
  async function persistTranslatorSession(fs, repoRoot, session) {
1140
+ if (!hasConfirmedClaudeSessionId(session)) {
1141
+ return;
1142
+ }
1107
1143
  await fs.writeJsonAtomic(resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH), {
1108
1144
  version: 1,
1109
1145
  role: session.role,
@@ -1115,6 +1151,9 @@ async function persistTranslatorSession(fs, repoRoot, session) {
1115
1151
  });
1116
1152
  }
1117
1153
  async function persistHarnessEngineerSession(fs, repoRoot, session) {
1154
+ if (!hasConfirmedClaudeSessionId(session)) {
1155
+ return;
1156
+ }
1118
1157
  await fs.writeJsonAtomic(resolveRepoPath(repoRoot, HARNESS_ENGINEER_SESSION_PATH), {
1119
1158
  version: 1,
1120
1159
  role: session.role,
@@ -1128,6 +1167,9 @@ async function persistHarnessEngineerSession(fs, repoRoot, session) {
1128
1167
  function isProjectRoleSessionFile(value) {
1129
1168
  return "record" in value && typeof value.record === "object" && value.record !== null;
1130
1169
  }
1170
+ function hasConfirmedClaudeSessionId(session) {
1171
+ return session.claudeSessionId.trim().length > 0;
1172
+ }
1131
1173
  function createEmptyTaskSessionRecord(taskSlug, updatedAt) {
1132
1174
  return {
1133
1175
  version: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.4.26",
3
+ "version": "0.4.27",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [