vibe-coding-master 0.2.0 → 0.2.2

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
@@ -22,7 +22,6 @@ Each role runs as a real Claude Code process inside an embedded terminal. The GU
22
22
  - Permission mode selection before start, resume, or restart:
23
23
  - `default`
24
24
  - `bypassPermissions`
25
- - `--dangerously-skip-permissions`
26
25
  - PM-mediated role messaging through VCM-dispatched route files.
27
26
  - Manual and automatic orchestration modes.
28
27
  - Two-stage VCM harness setup: deterministic fixed install plus AI-assisted bootstrap.
@@ -188,18 +187,20 @@ The left sidebar is intentionally compact and collapsible:
188
187
 
189
188
  - `Repository Path`: path input on one row; `Recent` and `Connect` on the next row.
190
189
  - `Repository`: connected path, branch, and working tree state. `Working tree: uncommitted changes` means `git status --porcelain` is not empty.
191
- - `Settings`: `Theme`, `Round alert`, `Try alert`, `Messages`, and `Events`.
190
+ - `Settings`: `Theme`, `Flow pause alert`, `Try alert`, `Messages`, and `Events`.
192
191
  - `VCM Harness`: fixed-install status, bootstrap completion checks, and the bootstrap terminal when one is running.
193
192
  - `New Task`: one `task name` input.
194
193
  - `Tasks`: task list and task status.
195
194
 
196
195
  All sidebar sections are collapsed by default. When no task is selected, `Repository Path` opens by default.
197
196
 
197
+ When VCM is connected to an active task, the bottom of the sidebar shows a task status dock. It stays outside the collapsible groups and shows the task title, task status, start time, total elapsed time, total flow-round count, and Claude Code active runtime. If a flow round is currently active or settling, the dock also shows that round's start time and Claude Code active runtime.
198
+
198
199
  ## Translation
199
200
 
200
201
  The task header has a global `Translate` button next to `Close Task`. It opens a translation panel beside the embedded terminal for the role consoles and keeps the same on/off setting while switching roles. The terminal and translation panel split the available width evenly.
201
202
 
202
- The task header does not include a manual `Refresh` button. Task status, role status, messages, orchestration state, and round completion state refresh automatically. The remaining `Refresh` button lives only in the sidebar `VCM Harness` section and is for rechecking harness files.
203
+ The task header does not include a manual `Refresh` button. Task status, role status, messages, orchestration state, and flow pause state refresh automatically. The remaining `Refresh` button lives only in the sidebar `VCM Harness` section and is for rechecking harness files.
203
204
 
204
205
  Translation settings are local and stored in:
205
206
 
@@ -211,7 +212,7 @@ The same file stores recent repository paths. The translation API key is stored
211
212
 
212
213
  The sidebar `Settings` section also stores the UI theme preference in this file. The default is `system`, which follows the OS/browser color-scheme preference; users can cycle between `System`, `Light`, and `Dark`.
213
214
 
214
- The same sidebar also has a `Round alert` toggle. It is on by default and controls the in-app prompt plus a soft two-note completion chime that fires when VCM detects that the current full conversation round is complete. The `Try alert` button triggers the same local prompt and sound for testing.
215
+ The same sidebar also has a `Flow pause alert` toggle. It is on by default and controls the local alert that fires when VCM detects that the current role flow has stopped advancing. Short flows use a weak reminder: the soft two-note chime plays 3 times, 1.4 seconds apart. Flows lasting 10 minutes or longer use a strong reminder: VCM shows an alert dialog and repeats the chime until the user confirms it. The `Try alert` button always triggers the strong reminder for testing.
215
216
 
216
217
  Translation behavior:
217
218
 
@@ -222,6 +223,8 @@ Translation behavior:
222
223
  - VCM tails those transcript files in the backend. Closing the translation panel does not stop capture; the tailer stops only when the role session is stopped/restarted or the task is closed.
223
224
  - Translation events are cached under the task runtime repo at `.ai/vcm/translation/<task>/<role>/<session-id>.jsonl` and delivered to the frontend through HTTP polling.
224
225
  - The polling cursor is the next expected seq: `after=18` acknowledges seq `1..17` and returns seq `18+`; there is no snapshot mismatch error.
226
+ - The translation panel retains the most recent 500 entries per role session in frontend/backend memory. Older entries are pruned from the live panel state and event cache to keep long sessions responsive.
227
+ - Failed output translations are tracked in a backend failure list. When failures exist, the panel shows `Ignore N` and `Retry N`; retry reuses the original entry id so the failed row is replaced by the normal translating/translated flow. If an old failed entry is pruned by the 500-entry cap, its failure-list item is removed too.
225
228
  - Assistant prose is shown as English source while translating, then replaced by the translated Chinese result.
226
229
  - Assistant prose renders Markdown in the panel, including headings, lists, code fences, tables, and links.
227
230
  - Tool calls and tool results are preserved as dim one-line rows such as `● Bash({"command":"npm test"})`.
@@ -245,10 +248,10 @@ CLAUDE.md
245
248
  .claude/agents/architect.md
246
249
  .claude/agents/coder.md
247
250
  .claude/agents/reviewer.md
248
- .claude/skills/vcm-route-message.md
249
- .claude/skills/vcm-final-acceptance.md
250
- .claude/skills/vcm-long-running-validation.md
251
- .claude/skills/vcm-harness-bootstrap.md
251
+ .claude/skills/vcm-route-message/SKILL.md
252
+ .claude/skills/vcm-final-acceptance/SKILL.md
253
+ .claude/skills/vcm-long-running-validation/SKILL.md
254
+ .claude/skills/vcm-harness-bootstrap/SKILL.md
252
255
  .ai/vcm-harness-manifest.json
253
256
  .ai/tools/generate-module-index
254
257
  .ai/tools/generate-public-surface
@@ -257,6 +260,9 @@ CLAUDE.md
257
260
  .github/pull_request_template.md
258
261
  ```
259
262
 
263
+ Repo-local skills are installed as `.claude/skills/<skill-name>/SKILL.md` so
264
+ Claude Code can register them.
265
+
260
266
  If a managed-block file already exists, VCM preserves user-authored content and only inserts or replaces the VCM block:
261
267
 
262
268
  ```md
@@ -366,13 +372,15 @@ VCM Harness injects Claude Code `UserPromptSubmit` and `Stop` hooks into `.claud
366
372
 
367
373
  The implementation keeps only the active manual/auto orchestration mode. It does not expose pause/resume, stage/approve/reject, or a separate agent-facing message CLI.
368
374
 
369
- ## Round Completion Alerts
375
+ ## Flow Pause Alerts
370
376
 
371
- VCM detects conversation completion from hook-driven role activity state, not PTY silence or message history. `UserPromptSubmit` marks a role `running`, and `Stop` marks that role `idle` with a stop timestamp.
377
+ VCM detects flow pauses from Claude Code hook events, not PTY silence, message history, or pending route files. `UserPromptSubmit` starts a new flow round when none is active, or continues the current round when it fires within the settle window.
372
378
 
373
- For role chains, VCM waits for the final role to reach hook `Stop`. For example, if PM sends work to Coder and Coder sends a result back to PM, the round is not complete when Coder finishes; it is complete only after PM reaches `Stop` for the final response. Pending route files block completion because more dispatch work is waiting; message history does not define completion.
379
+ When a `Stop` hook fires, VCM marks the round as settling for 10 seconds. If another `UserPromptSubmit` fires inside that window, the same round continues and VCM increments the prompt-submit count. If no new prompt is accepted before the deadline, VCM marks the round as `paused`.
374
380
 
375
- When `Round alert` is enabled, the frontend polls the task round state, deduplicates each completion id, shows a small `Round complete` prompt, and plays the local completion chime.
381
+ The normal path is timer-driven: `Stop` starts a backend settle timer, and `UserPromptSubmit` cancels it. Reading the round state also checks expired deadlines as a recovery fallback after process restarts, sleep, or missed timers.
382
+
383
+ When `Flow pause alert` is enabled, the frontend polls the task round state and deduplicates each pause id. Flow duration is measured from the first `UserPromptSubmit` to the last `Stop`; the 10 second settle window is not counted. If the paused flow lasted less than 10 minutes, it plays the local chime 3 times at 1.4 second intervals. If the paused flow lasted 10 minutes or longer, it shows a modal `Flow paused` alert and repeats the local chime until the user clicks `Confirm`. A pause can mean normal completion, user decision needed, dispatch failure, or another workflow interruption; the point is to get the user to look with the right amount of urgency.
376
384
 
377
385
  ## Resume Behavior
378
386
 
@@ -389,6 +397,8 @@ Session buttons behave as follows:
389
397
  - `Restart`: stops the current process if needed, creates a new UUID, and starts a fresh Claude session.
390
398
  - `Stop`: stops the embedded terminal process and leaves the persisted Claude session id resumable.
391
399
 
400
+ Embedded terminal output is still written to raw role log files under `.ai/vcm/handoffs/logs/`. When a browser reconnects to a running terminal, VCM replays only the tail of that log, capped at 2 MB, rather than streaming the entire historical log back into xterm. This keeps long sessions responsive while preserving full logs on disk until task cleanup.
401
+
392
402
  ## Local Project Files
393
403
 
394
404
  For a connected repository, VCM uses:
@@ -25,9 +25,6 @@ export function createClaudeAdapter(runner) {
25
25
  if (permissionMode === "bypassPermissions") {
26
26
  args.push("--permission-mode", "bypassPermissions");
27
27
  }
28
- else if (permissionMode === "dangerously-skip-permissions") {
29
- args.push("--dangerously-skip-permissions");
30
- }
31
28
  return {
32
29
  command,
33
30
  args,
@@ -20,6 +20,20 @@ export function createNodeFileSystemAdapter() {
20
20
  async readText(targetPath) {
21
21
  return fs.readFile(targetPath, "utf8");
22
22
  },
23
+ async readTextTail(targetPath, maxBytes) {
24
+ const handle = await fs.open(targetPath, "r");
25
+ try {
26
+ const stat = await handle.stat();
27
+ const length = Math.min(Math.max(0, Math.floor(maxBytes)), stat.size);
28
+ const position = stat.size - length;
29
+ const buffer = Buffer.alloc(length);
30
+ await handle.read(buffer, 0, length, position);
31
+ return buffer.toString("utf8");
32
+ }
33
+ finally {
34
+ await handle.close();
35
+ }
36
+ },
23
37
  async writeText(targetPath, content) {
24
38
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
25
39
  await fs.writeFile(targetPath, content, "utf8");
@@ -6,20 +6,11 @@ export function registerRoundRoutes(app, deps) {
6
6
  const config = await deps.projectService.loadConfig(project.repoRoot);
7
7
  const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
8
8
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
9
- const sessions = await deps.sessionService.listRoleSessions(project.repoRoot, request.params.taskSlug);
10
- const pendingRoutes = await deps.messageService.listPendingRouteFiles({
11
- repoRoot: project.repoRoot,
12
- taskRepoRoot,
9
+ return deps.roundService.getTaskRoundState({
13
10
  stateRepoRoot: taskRepoRoot,
14
11
  stateRoot: config.stateRoot,
15
- handoffDir: task.handoffDir,
16
12
  taskSlug: request.params.taskSlug
17
13
  });
18
- return deps.roundService.getTaskRoundState({
19
- taskSlug: request.params.taskSlug,
20
- sessions,
21
- pendingRouteCount: pendingRoutes.length
22
- });
23
14
  });
24
15
  }
25
16
  async function requireCurrentProject(projectService) {
@@ -64,6 +64,14 @@ export function registerTranslationRoutes(app, deps) {
64
64
  await requireCurrentProject(deps.projectService);
65
65
  return deps.translationService.retryTranslation(request.params.sessionId, request.params.translationId);
66
66
  });
67
+ app.post("/api/translation/sessions/:sessionId/failures/ignore", async (request) => {
68
+ await requireCurrentProject(deps.projectService);
69
+ return deps.translationService.ignoreTranslationFailures(request.params.sessionId);
70
+ });
71
+ app.post("/api/translation/sessions/:sessionId/failures/retry", async (request) => {
72
+ await requireCurrentProject(deps.projectService);
73
+ return deps.translationService.retryFailedTranslations(request.params.sessionId);
74
+ });
67
75
  }
68
76
  function parseRole(role) {
69
77
  if (!isRoleName(role)) {
@@ -1,5 +1,6 @@
1
1
  import * as pty from "node-pty";
2
2
  import { VcmError } from "../errors.js";
3
+ export const TERMINAL_REPLAY_TAIL_LIMIT_BYTES = 2 * 1024 * 1024;
3
4
  export function createNodePtyTerminalRuntime(deps) {
4
5
  const entries = new Map();
5
6
  const now = deps.now ?? (() => new Date().toISOString());
@@ -113,7 +114,7 @@ export function createNodePtyTerminalRuntime(deps) {
113
114
  const entry = getEntry(entries, sessionId);
114
115
  entry.listeners.add(listener);
115
116
  if (options.replay !== false) {
116
- void deps.fs.readText(entry.input.logPath)
117
+ void readTerminalReplayText(deps.fs, entry.input.logPath)
117
118
  .then((data) => {
118
119
  if (!data || !entry.listeners.has(listener)) {
119
120
  return;
@@ -138,6 +139,25 @@ export function createNodePtyTerminalRuntime(deps) {
138
139
  }
139
140
  };
140
141
  }
142
+ async function readTerminalReplayText(fs, logPath) {
143
+ const data = fs.readTextTail
144
+ ? await fs.readTextTail(logPath, TERMINAL_REPLAY_TAIL_LIMIT_BYTES)
145
+ : tailTerminalReplay(await fs.readText(logPath));
146
+ return tailTerminalReplay(data);
147
+ }
148
+ export function tailTerminalReplay(data, limitBytes = TERMINAL_REPLAY_TAIL_LIMIT_BYTES) {
149
+ if (limitBytes <= 0 || Buffer.byteLength(data, "utf8") <= limitBytes) {
150
+ return data;
151
+ }
152
+ let start = Math.max(0, data.length - limitBytes);
153
+ let tail = data.slice(start);
154
+ while (Buffer.byteLength(tail, "utf8") > limitBytes && start < data.length) {
155
+ start += Math.max(1, Math.ceil((Buffer.byteLength(tail, "utf8") - limitBytes) / 4));
156
+ tail = data.slice(start);
157
+ }
158
+ const firstLineBreak = tail.indexOf("\n");
159
+ return firstLineBreak >= 0 ? tail.slice(firstLineBreak + 1) : tail;
160
+ }
141
161
  export function buildPtyEnvironment(baseEnv, inputEnv = {}) {
142
162
  const env = {
143
163
  ...baseEnv,
@@ -83,8 +83,6 @@ export async function createServer(deps, options = {}) {
83
83
  registerRoundRoutes(app, {
84
84
  projectService: deps.projectService,
85
85
  taskService: deps.taskService,
86
- sessionService: deps.sessionService,
87
- messageService: deps.messageService,
88
86
  roundService: deps.roundService
89
87
  });
90
88
  registerTranslationRoutes(app, {
@@ -164,14 +162,8 @@ export function createDefaultServerDeps(options = {}) {
164
162
  sessionService,
165
163
  taskService
166
164
  });
167
- const claudeHookService = createClaudeHookService({
168
- projectService,
169
- taskService,
170
- sessionService,
171
- messageService
172
- });
165
+ const roundService = createRoundService({ fs });
173
166
  const transcripts = createClaudeTranscriptService();
174
- const roundService = createRoundService();
175
167
  const translationService = createTranslationService({
176
168
  runtime,
177
169
  sessionRegistry: registry,
@@ -182,6 +174,14 @@ export function createDefaultServerDeps(options = {}) {
182
174
  appSettings,
183
175
  provider: createOpenAiCompatibleTranslationProvider()
184
176
  });
177
+ const claudeHookService = createClaudeHookService({
178
+ projectService,
179
+ taskService,
180
+ sessionService,
181
+ messageService,
182
+ roundService,
183
+ translationService
184
+ });
185
185
  return {
186
186
  appSettings,
187
187
  projectService,
@@ -179,9 +179,12 @@ function normalizeSettingsFile(input) {
179
179
  }
180
180
  function normalizePreferences(input) {
181
181
  const candidate = isObject(input) ? input : {};
182
+ const rawFlowPauseAlerts = "flowPauseAlerts" in candidate
183
+ ? candidate.flowPauseAlerts
184
+ : candidate.roundCompletionAlerts;
182
185
  return {
183
186
  themeMode: normalizeThemeMode(candidate.themeMode),
184
- roundCompletionAlerts: candidate.roundCompletionAlerts !== false
187
+ flowPauseAlerts: rawFlowPauseAlerts !== false
185
188
  };
186
189
  }
187
190
  function normalizeThemeMode(input) {
@@ -42,6 +42,24 @@ export function createClaudeHookService(deps) {
42
42
  transcriptPath: stringOrUndefined(input.event.transcript_path),
43
43
  cwd: stringOrUndefined(input.event.cwd)
44
44
  });
45
+ await deps.roundService.recordClaudeHookEvent({
46
+ stateRepoRoot: context.taskRepoRoot,
47
+ stateRoot: context.config.stateRoot,
48
+ taskSlug: input.taskSlug,
49
+ role: input.role,
50
+ eventName
51
+ });
52
+ if (session) {
53
+ await deps.translationService.recordConversationBoundary({
54
+ repoRoot: context.project.repoRoot,
55
+ taskRepoRoot: context.taskRepoRoot,
56
+ taskSlug: input.taskSlug,
57
+ role: input.role,
58
+ sessionId: session.id,
59
+ boundaryKind: "start",
60
+ occurredAt: session.lastPromptSubmittedAt ?? session.updatedAt
61
+ });
62
+ }
45
63
  const submitted = await deps.messageService.confirmPromptSubmitted({
46
64
  repoRoot: context.project.repoRoot,
47
65
  taskRepoRoot: context.taskRepoRoot,
@@ -76,6 +94,24 @@ export function createClaudeHookService(deps) {
76
94
  transcriptPath: stringOrUndefined(input.event.transcript_path),
77
95
  cwd: stringOrUndefined(input.event.cwd)
78
96
  });
97
+ await deps.roundService.recordClaudeHookEvent({
98
+ stateRepoRoot: context.taskRepoRoot,
99
+ stateRoot: context.config.stateRoot,
100
+ taskSlug: input.taskSlug,
101
+ role: input.role,
102
+ eventName
103
+ });
104
+ if (session) {
105
+ await deps.translationService.recordConversationBoundary({
106
+ repoRoot: context.project.repoRoot,
107
+ taskRepoRoot: context.taskRepoRoot,
108
+ taskSlug: input.taskSlug,
109
+ role: input.role,
110
+ sessionId: session.id,
111
+ boundaryKind: "end",
112
+ occurredAt: session.lastStopAt ?? session.updatedAt
113
+ });
114
+ }
79
115
  const dispatched = await deps.messageService.scanAndDispatchPendingRouteFiles({
80
116
  repoRoot: context.project.repoRoot,
81
117
  taskRepoRoot: context.taskRepoRoot,
@@ -55,29 +55,33 @@ const HARNESS_FILES = [
55
55
  },
56
56
  {
57
57
  kind: "skill-vcm-route-message",
58
- path: ".claude/skills/vcm-route-message.md",
58
+ path: ".claude/skills/vcm-route-message/SKILL.md",
59
59
  title: "VCM Route Message Skill",
60
+ frontmatter: renderSkillFrontmatter("vcm-route-message", "Use when a VCM role needs to hand off work, ask a question, report a result, report a blocker, or raise a finding to another VCM role."),
60
61
  ownership: "whole-file",
61
62
  renderRules: renderVcmRouteMessageSkillRules
62
63
  },
63
64
  {
64
65
  kind: "skill-vcm-final-acceptance",
65
- path: ".claude/skills/vcm-final-acceptance.md",
66
+ path: ".claude/skills/vcm-final-acceptance/SKILL.md",
66
67
  title: "VCM Final Acceptance Skill",
68
+ frontmatter: renderSkillFrontmatter("vcm-final-acceptance", "Use when project-manager is ready to decide whether a VCM-managed task can be accepted, returned for follow-up, or blocked for a decision."),
67
69
  ownership: "whole-file",
68
70
  renderRules: renderVcmFinalAcceptanceSkillRules
69
71
  },
70
72
  {
71
73
  kind: "skill-vcm-harness-bootstrap",
72
- path: ".claude/skills/vcm-harness-bootstrap.md",
74
+ path: ".claude/skills/vcm-harness-bootstrap/SKILL.md",
73
75
  title: "VCM Harness Bootstrap Skill",
76
+ frontmatter: renderSkillFrontmatter("vcm-harness-bootstrap", "Use when VCM needs AI-assisted project understanding to finish or refresh project-specific harness content."),
74
77
  ownership: "whole-file",
75
78
  renderRules: renderVcmHarnessBootstrapSkillRules
76
79
  },
77
80
  {
78
81
  kind: "skill-vcm-long-running-validation",
79
- path: ".claude/skills/vcm-long-running-validation.md",
82
+ path: ".claude/skills/vcm-long-running-validation/SKILL.md",
80
83
  title: "VCM Long-Running Validation Skill",
84
+ frontmatter: renderSkillFrontmatter("vcm-long-running-validation", "Use for builds, browser checks, E2E tests, release suites, or any validation command that may take long enough for shell-completion callbacks to become unreliable."),
81
85
  ownership: "whole-file",
82
86
  renderRules: renderVcmLongRunningValidationSkillRules
83
87
  },
@@ -469,6 +473,9 @@ function isPlainObject(value) {
469
473
  function renderAgentFrontmatter(name, description) {
470
474
  return `---\nname: ${name}\ndescription: ${description}\ntools: Read, Grep, Glob, Bash, Edit, Write\n---`;
471
475
  }
476
+ function renderSkillFrontmatter(name, description) {
477
+ return `---\nname: ${name}\ndescription: ${description}\n---`;
478
+ }
472
479
  function resolveHarnessPath(repoRoot, relativePath) {
473
480
  return path.join(repoRoot, relativePath);
474
481
  }
@@ -512,7 +519,7 @@ async function checkFixedHarness(fs, repoRoot) {
512
519
  const requiredPaths = [
513
520
  "CLAUDE.md",
514
521
  ".ai/vcm-harness-manifest.json",
515
- ".claude/skills/vcm-harness-bootstrap.md",
522
+ ".claude/skills/vcm-harness-bootstrap/SKILL.md",
516
523
  ".ai/tools/generate-module-index",
517
524
  ".ai/tools/generate-public-surface"
518
525
  ];
@@ -810,9 +817,6 @@ function buildClaudeStartCommand(command = "claude", permissionMode = "default",
810
817
  if (permissionMode === "bypassPermissions") {
811
818
  args.push("--permission-mode", "bypassPermissions");
812
819
  }
813
- else if (permissionMode === "dangerously-skip-permissions") {
814
- args.push("--dangerously-skip-permissions");
815
- }
816
820
  return {
817
821
  command,
818
822
  args,