@agentbridge1/cli 0.0.4 → 0.0.6

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.
@@ -4,6 +4,7 @@ exports.getDirtyWorkingTreeFiles = void 0;
4
4
  exports.renderScopedApprovalConfirmation = renderScopedApprovalConfirmation;
5
5
  exports.renderCrossingContext = renderCrossingContext;
6
6
  exports.shouldRenderSupervisionSummary = shouldRenderSupervisionSummary;
7
+ exports.finalizeWatchSupervision = finalizeWatchSupervision;
7
8
  exports.syncAndBuildSupervisionSnapshot = syncAndBuildSupervisionSnapshot;
8
9
  exports.renderWatchTaskScopeUsage = renderWatchTaskScopeUsage;
9
10
  exports.renderWatchStartupHeader = renderWatchStartupHeader;
@@ -14,6 +15,7 @@ exports.isHandoffRequiredCloseError = isHandoffRequiredCloseError;
14
15
  exports.renderIdleCloseFailure = renderIdleCloseFailure;
15
16
  exports.openOrResumeServerSessionForWatch = openOrResumeServerSessionForWatch;
16
17
  exports.normalizeDirtyWorkingTreeFiles = normalizeDirtyWorkingTreeFiles;
18
+ exports.startupDirtyClassification = startupDirtyClassification;
17
19
  exports.ensureWatchRepoClean = ensureWatchRepoClean;
18
20
  exports.runWatch = runWatch;
19
21
  const node_fs_1 = require("node:fs");
@@ -35,9 +37,12 @@ const watch_packet_handshake_1 = require("../watch-packet-handshake");
35
37
  const work_context_resolver_1 = require("../work-context-resolver");
36
38
  const test_runner_1 = require("../test-runner");
37
39
  const revert_crossing_1 = require("../revert-crossing");
40
+ const local_proof_1 = require("../local-proof");
38
41
  const supervision_1 = require("../supervision");
42
+ const preflight_changed_files_1 = require("../preflight-changed-files");
39
43
  const http_1 = require("../http");
40
44
  const error_catalog_1 = require("../error-catalog");
45
+ const proof_obligations_1 = require("../proof-obligations");
41
46
  const session_state_2 = require("../session-state");
42
47
  const file_fingerprints_1 = require("../file-fingerprints");
43
48
  const start_1 = require("./start");
@@ -119,19 +124,22 @@ function shouldRenderSupervisionSummary(lastSignature, snapshot) {
119
124
  nextSignature,
120
125
  };
121
126
  }
127
+ function finalizeWatchSupervision(state, supervision) {
128
+ return (0, supervision_1.enrichSupervisionWithLocalState)(state, supervision);
129
+ }
122
130
  async function syncAndBuildSupervisionSnapshot(input) {
123
131
  const { ctx, state, cfgDomains, changedFile, outcome, changeRequestId, unresolvedProtectedCrossing, rollout, timing, } = input;
124
132
  const contextError = (err) => (err instanceof Error ? err.message : String(err));
125
133
  if (!state.serverSessionId || !ctx) {
126
134
  return {
127
- supervision: (0, supervision_1.fallbackSupervisionSnapshot)({
135
+ supervision: finalizeWatchSupervision(state, (0, supervision_1.fallbackSupervisionSnapshot)({
128
136
  workSessionId: state.serverSessionId ?? state.id,
129
137
  changeRequestId,
130
138
  changedFiles: [...state.changedFiles],
131
139
  domains: cfgDomains,
132
140
  unresolvedProtectedCrossing,
133
141
  blocked: state.status === "blocked",
134
- }),
142
+ })),
135
143
  acceptanceReport: null,
136
144
  };
137
145
  }
@@ -191,14 +199,14 @@ async function syncAndBuildSupervisionSnapshot(input) {
191
199
  ? await timing.trackAsync("acceptance_check_fetch", fetchAcceptance)
192
200
  : await fetchAcceptance();
193
201
  return {
194
- supervision: overlayLocalChangedFilesOnSupervision((0, supervision_1.supervisionFromAcceptance)(report, cfgDomains), state.changedFiles),
202
+ supervision: finalizeWatchSupervision(state, overlayLocalChangedFilesOnSupervision((0, supervision_1.supervisionFromAcceptance)(report, cfgDomains), state.changedFiles)),
195
203
  syncError,
196
204
  acceptanceReport: report,
197
205
  };
198
206
  }
199
207
  catch (err) {
200
208
  return {
201
- supervision: (0, supervision_1.fallbackSupervisionSnapshot)({
209
+ supervision: finalizeWatchSupervision(state, (0, supervision_1.fallbackSupervisionSnapshot)({
202
210
  workSessionId: state.serverSessionId ?? state.id,
203
211
  changeRequestId,
204
212
  changedFiles: [...state.changedFiles],
@@ -206,7 +214,7 @@ async function syncAndBuildSupervisionSnapshot(input) {
206
214
  unresolvedProtectedCrossing,
207
215
  blocked: state.status === "blocked",
208
216
  serverAcceptanceUnavailable: contextError(err),
209
- }),
217
+ })),
210
218
  syncError,
211
219
  acceptanceReport: null,
212
220
  };
@@ -218,9 +226,6 @@ function normalizeInline(value) {
218
226
  function normalizeTaskSummary(value) {
219
227
  return value.trim().replace(/\s+/g, " ").toLowerCase();
220
228
  }
221
- function isActiveLocalSession(state) {
222
- return Boolean(state && state.status !== "closed" && state.id !== "none");
223
- }
224
229
  function renderWatchTaskScopeUsage() {
225
230
  return [
226
231
  "Usage:",
@@ -256,23 +261,172 @@ function inferTaskFromFiles(files, domains) {
256
261
  }
257
262
  return "Inferred from changed files/current work";
258
263
  }
264
+ const NOISY_PATH_PREFIXES = [
265
+ "node_modules/",
266
+ "dist/",
267
+ "build/",
268
+ "coverage/",
269
+ ".next/",
270
+ ".turbo/",
271
+ ".cache/",
272
+ ".git/",
273
+ ];
274
+ const HIGH_RISK_PATH_HINTS = [
275
+ "auth",
276
+ "security",
277
+ "payment",
278
+ "billing",
279
+ "database",
280
+ "db",
281
+ "migration",
282
+ "prisma",
283
+ "supabase",
284
+ "package.json",
285
+ "package-lock.json",
286
+ "pnpm-lock.yaml",
287
+ "yarn.lock",
288
+ "config",
289
+ "agentbridge",
290
+ "session",
291
+ "proof",
292
+ "protocol",
293
+ ];
294
+ function isGeneratedOrNoisyPath(file) {
295
+ const normalized = normalizeInline(file);
296
+ if (NOISY_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
297
+ return true;
298
+ if (normalized.endsWith(".tmp") || normalized.endsWith(".cache"))
299
+ return true;
300
+ return false;
301
+ }
302
+ function classifyDirtyWorkspace(input) {
303
+ const localChanged = new Set(input.localState?.changedFiles ?? []);
304
+ const mtimeByFile = new Map();
305
+ let newestMtime = null;
306
+ for (const file of input.files) {
307
+ const mtime = mtimeMsForFile(file);
308
+ mtimeByFile.set(file, mtime);
309
+ if (typeof mtime === "number" && (newestMtime == null || mtime > newestMtime)) {
310
+ newestMtime = mtime;
311
+ }
312
+ }
313
+ const classified = input.files.map((file) => {
314
+ const normalized = normalizeInline(file);
315
+ const noisy = isGeneratedOrNoisyPath(normalized);
316
+ const resolved = (0, domain_resolution_1.resolveDomainForFile)(normalized, input.domains);
317
+ const tier = resolved.tier ?? null;
318
+ const isHighRiskTier = tier === "tier_a" || tier === "tier_b";
319
+ const isHighRiskPath = HIGH_RISK_PATH_HINTS.some((hint) => normalized.includes(hint));
320
+ const risk = isHighRiskTier || isHighRiskPath ? "high" : "normal";
321
+ const reasons = [];
322
+ let bucket = "background_dirty";
323
+ if (noisy) {
324
+ bucket = "generated_noisy";
325
+ reasons.push("generated/noisy path");
326
+ }
327
+ else if (localChanged.has(normalized)) {
328
+ bucket = "likely_current_work";
329
+ reasons.push("already tracked in local session");
330
+ }
331
+ else {
332
+ const mtime = mtimeByFile.get(normalized) ?? null;
333
+ const RECENT_WINDOW_MS = 15 * 60 * 1000;
334
+ if (newestMtime != null && mtime != null && newestMtime - mtime <= RECENT_WINDOW_MS) {
335
+ bucket = "likely_current_work";
336
+ reasons.push("recently modified");
337
+ }
338
+ else if (/^(src|app|server|modules|cli|docs|config|scripts)\//.test(normalized)) {
339
+ bucket = "likely_current_work";
340
+ reasons.push("source/docs/config path");
341
+ }
342
+ else {
343
+ reasons.push("pre-existing or unrelated dirty background");
344
+ }
345
+ }
346
+ if (risk === "high")
347
+ reasons.push("high-risk path/domain");
348
+ return {
349
+ file: normalized,
350
+ bucket,
351
+ reasons,
352
+ domain: resolved.domain ?? null,
353
+ tier,
354
+ risk,
355
+ mtimeMs: mtimeByFile.get(normalized) ?? null,
356
+ };
357
+ });
358
+ const rankScore = (entry) => {
359
+ const riskScore = entry.risk === "high" ? 1000 : 0;
360
+ const bucketScore = entry.bucket === "likely_current_work" ? 200 : entry.bucket === "background_dirty" ? 100 : 0;
361
+ const mtimeScore = entry.mtimeMs ?? 0;
362
+ return riskScore + bucketScore + mtimeScore;
363
+ };
364
+ const sortedRelevant = classified
365
+ .filter((entry) => entry.bucket !== "generated_noisy")
366
+ .sort((a, b) => rankScore(b) - rankScore(a));
367
+ const topRelevant = sortedRelevant.slice(0, 10).map((entry) => entry.file);
368
+ const hiddenCount = Math.max(0, classified.length - topRelevant.length);
369
+ const highRiskTop = sortedRelevant.filter((entry) => entry.risk === "high").slice(0, 3).map((entry) => entry.file);
370
+ return { classified, topRelevant, hiddenCount, highRiskTop };
371
+ }
372
+ function renderDirtyWorkspaceDetails(classified) {
373
+ const renderBucket = (bucket, title) => {
374
+ const entries = classified.filter((entry) => entry.bucket === bucket);
375
+ if (entries.length === 0)
376
+ return [];
377
+ return [
378
+ `${title}:`,
379
+ ...entries.map((entry) => `- ${entry.file} [risk=${entry.risk}; domain=${entry.domain ?? "Unknown"}; reason=${entry.reasons.join("; ")}]`),
380
+ "",
381
+ ];
382
+ };
383
+ return [
384
+ "Details (dirty workspace classification):",
385
+ ...renderBucket("likely_current_work", "Likely current work"),
386
+ ...renderBucket("background_dirty", "Background dirty"),
387
+ ...renderBucket("generated_noisy", "Generated/noisy"),
388
+ ].join("\n");
389
+ }
259
390
  function renderBrainstormingStatus() {
260
391
  return [
261
- "No coding changes detected yet.",
392
+ "No coding changes detected.",
262
393
  "Mode: brainstorming/planning",
263
394
  "Next: When your agent starts changing files, AgentBridge will review proof/scope risk.",
264
395
  "",
265
396
  ].join("\n");
266
397
  }
267
- function renderCodingDetection(changedFiles, inferredTask) {
268
- return [
269
- "AgentBridge detected coding work.",
270
- `Task: ${inferredTask}`,
271
- "Mode: coding/implementation",
398
+ function renderCodingDetection(input) {
399
+ const summary = classifyDirtyWorkspace({
400
+ files: input.changedFiles,
401
+ localState: input.localState,
402
+ domains: input.domains,
403
+ });
404
+ const hasBaseline = Boolean(input.localState && input.localState.id !== "none" && input.localState.status !== "closed");
405
+ const lines = [
406
+ "AgentBridge is watching.",
407
+ "Mode:",
408
+ "Coding changes detected",
409
+ `Task: ${input.inferredTask}`,
272
410
  "Changed files:",
273
- ...changedFiles.map((file) => `- ${file}`),
274
- "",
275
- ].join("\n");
411
+ ...summary.topRelevant.map((file) => `- ${file}`),
412
+ ...(summary.hiddenCount > 0
413
+ ? [
414
+ `+ ${summary.hiddenCount} more changed files hidden.`,
415
+ "Run `agentbridge watch --details` to inspect everything.",
416
+ ]
417
+ : []),
418
+ ];
419
+ if (!hasBaseline && input.changedFiles.length > 0) {
420
+ lines.push("", "AgentBridge found existing changes.", "If these changes happened before AgentBridge started, verify them or clean them up before trusting the work.", "AgentBridge found an already-dirty workspace.", "Some changes may be from before AgentBridge started.", "AgentBridge will still flag proof/drift risk, but this may include older work.");
421
+ }
422
+ if (summary.highRiskTop.length > 0) {
423
+ lines.push("", `High-risk changes prioritized: ${summary.highRiskTop.join(", ")}`);
424
+ }
425
+ if (input.details) {
426
+ lines.push("", renderDirtyWorkspaceDetails(summary.classified));
427
+ }
428
+ lines.push("");
429
+ return lines.join("\n");
276
430
  }
277
431
  function detectInferredDomainDrift(files, domains) {
278
432
  if (files.length <= 1)
@@ -330,6 +484,10 @@ function renderWatchStartupHeader(input) {
330
484
  `Current status: ${input.currentStatus}`,
331
485
  `Next guidance: ${input.nextGuidance}`,
332
486
  "",
487
+ "Live supervision: keep this watch session running beside Cursor.",
488
+ "Task checkpoint/close: agentbridge start",
489
+ "Record proof: agentbridge verify -- <test command>",
490
+ "",
333
491
  ].join("\n");
334
492
  }
335
493
  function renderStartupPhase(step, status) {
@@ -358,6 +516,15 @@ async function flushStdout() {
358
516
  await new Promise((resolveTick) => setImmediate(resolveTick));
359
517
  }
360
518
  function buildWatchBlockingIssue(report, options) {
519
+ const summarizePromptFiles = (files) => {
520
+ const unique = [...new Set(files)];
521
+ if (unique.length === 0)
522
+ return "current files";
523
+ const visible = unique.slice(0, 3);
524
+ if (unique.length <= 3)
525
+ return visible.join(", ");
526
+ return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
527
+ };
361
528
  if (options.strictScope && (report.out_of_scope_files ?? []).length > 0) {
362
529
  const files = [...new Set(report.out_of_scope_files ?? [])];
363
530
  return {
@@ -368,9 +535,9 @@ function buildWatchBlockingIssue(report, options) {
368
535
  suggestedPrompt: [
369
536
  "You changed files outside the allowed scope.",
370
537
  "Revert them, justify why they are required and ask to expand scope, or split them into a separate task.",
371
- `Out-of-scope files: ${files.join(", ")}`,
538
+ `Out-of-scope files: ${summarizePromptFiles(files)}`,
372
539
  ].join("\n"),
373
- nextAction: "Revert out-of-scope files, then run: agentbridge check",
540
+ nextAction: "Revert out-of-scope files, then rerun: agentbridge watch",
374
541
  };
375
542
  }
376
543
  if (report.decision === "stale_evidence") {
@@ -378,12 +545,12 @@ function buildWatchBlockingIssue(report, options) {
378
545
  return {
379
546
  errorCode: "PROOF_STALE_AFTER_CHANGE",
380
547
  whatHappened: "Verification proof is stale because files changed after the last passing verify.",
381
- whyItMatters: "Acceptance needs proof that matches the final edited files.",
548
+ whyItMatters: "Proof must match the final edited files before you can trust the agent is done.",
382
549
  files: staleFiles,
383
550
  suggestedPrompt: [
384
551
  "Your proof is stale because files changed after verification.",
385
552
  "Rerun verification after your final edit, then rerun AgentBridge watch.",
386
- `Files: ${staleFiles.join(", ")}`,
553
+ `Files: ${summarizePromptFiles(staleFiles)}`,
387
554
  ].join("\n"),
388
555
  nextAction: "Run a verification command for the final changed files.",
389
556
  };
@@ -392,26 +559,30 @@ function buildWatchBlockingIssue(report, options) {
392
559
  return {
393
560
  errorCode: "VERIFICATION_FAILED",
394
561
  whatHappened: "verification failed",
395
- whyItMatters: "Failed verification cannot be used as acceptance proof.",
562
+ whyItMatters: "Failed verification cannot count as proof.",
396
563
  files: [...new Set(report.changed_files)],
397
564
  suggestedPrompt: [
398
565
  "The last verification command failed.",
399
- "Please fix the failure and rerun verification with a passing result before handoff.",
566
+ "Please fix the failure and rerun verification with a passing result, then rerun AgentBridge watch.",
400
567
  ].join("\n"),
401
568
  nextAction: "Fix verification failures, then rerun verification on final files.",
402
569
  };
403
570
  }
571
+ const blockingObligation = (0, proof_obligations_1.primaryBlockingObligation)(report);
572
+ if (blockingObligation) {
573
+ return (0, proof_obligations_1.buildWatchBlockingIssueFromObligation)(report, blockingObligation);
574
+ }
404
575
  if (report.decision === "needs_proof") {
405
576
  const files = [...new Set(report.changed_files)];
406
577
  return {
407
578
  errorCode: "PROOF_MISSING",
408
579
  whatHappened: "No valid verification proof is attached to the current work.",
409
- whyItMatters: "Work cannot be accepted without fresh proof for the current scope.",
580
+ whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
410
581
  files,
411
582
  suggestedPrompt: [
412
- "Your work is not ready. AgentBridge found changed files with no proof attached.",
583
+ `AgentBridge found ${files.length} changed files without proof.`,
413
584
  "Run verification for the changed files, then rerun AgentBridge watch.",
414
- `Files: ${files.join(", ") || "current files"}`,
585
+ `Files: ${summarizePromptFiles(files)}`,
415
586
  ].join("\n"),
416
587
  nextAction: "Run a verification command for the changed files.",
417
588
  };
@@ -419,16 +590,25 @@ function buildWatchBlockingIssue(report, options) {
419
590
  return null;
420
591
  }
421
592
  function buildFallbackWatchBlockingIssue(supervision) {
593
+ const summarizePromptFiles = (files) => {
594
+ const unique = [...new Set(files)];
595
+ if (unique.length === 0)
596
+ return "current files";
597
+ const visible = unique.slice(0, 3);
598
+ if (unique.length <= 3)
599
+ return visible.join(", ");
600
+ return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
601
+ };
422
602
  if (supervision.decision === "needs_proof") {
423
603
  return {
424
604
  errorCode: "PROOF_MISSING",
425
605
  whatHappened: "No valid verification proof is attached to the current work.",
426
- whyItMatters: "Work cannot be accepted without fresh proof for the current scope.",
606
+ whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
427
607
  files: [],
428
608
  suggestedPrompt: [
429
- "Your work is not ready. AgentBridge found changed files with no proof attached.",
609
+ `AgentBridge found ${supervision.changedFiles.length} changed files without proof.`,
430
610
  "Run verification for the changed files, then rerun AgentBridge watch.",
431
- `Files: ${supervision.changedFiles.length > 0 ? supervision.changedFiles.join(", ") : "current files"}`,
611
+ `Files: ${summarizePromptFiles(supervision.changedFiles)}`,
432
612
  ].join("\n"),
433
613
  nextAction: "Run a verification command for the changed files.",
434
614
  };
@@ -437,11 +617,11 @@ function buildFallbackWatchBlockingIssue(supervision) {
437
617
  return {
438
618
  errorCode: "VERIFICATION_FAILED",
439
619
  whatHappened: "Current task run is blocked and cannot complete.",
440
- whyItMatters: "Blocked runs must be resolved before the task can be marked done.",
620
+ whyItMatters: "Blocked proof or scope issues must be resolved before the work is safe to trust.",
441
621
  files: supervision.changedFiles,
442
622
  suggestedPrompt: [
443
- "Resolve the problem in this run before completing the task.",
444
- `Files in current blocked state: ${supervision.changedFiles.join(", ") || "none listed"}`,
623
+ "Resolve the verification or scope problem, then rerun AgentBridge watch.",
624
+ `Files in current blocked state: ${summarizePromptFiles(supervision.changedFiles)}`,
445
625
  ].join("\n"),
446
626
  nextAction: "Resolve proof/scope problems, then rerun watch.",
447
627
  };
@@ -449,12 +629,16 @@ function buildFallbackWatchBlockingIssue(supervision) {
449
629
  return null;
450
630
  }
451
631
  function renderWatchBlockingIssue(issue) {
452
- const filesLine = issue.files.length > 0 ? issue.files.join(", ") : "none listed";
632
+ const files = [...new Set(issue.files)];
633
+ const visibleFiles = files.slice(0, 10);
634
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
635
+ const filesLine = visibleFiles.length > 0 ? visibleFiles.join(", ") : "none listed";
453
636
  return [
454
637
  "AgentBridge caught an issue",
455
638
  `What happened: ${issue.whatHappened}`,
456
639
  `Why it matters: ${issue.whyItMatters}`,
457
640
  `Files: ${filesLine}`,
641
+ ...(hiddenCount > 0 ? [`+ ${hiddenCount} more files hidden (run \`agentbridge watch --details\`).`] : []),
458
642
  `Error code: ${issue.errorCode}`,
459
643
  "Suggested prompt to send back to agent:",
460
644
  "```text",
@@ -662,9 +846,11 @@ function fileWithinScopedPaths(file, scopedPaths) {
662
846
  function startupDirtyClassification(input) {
663
847
  const workspaceDirtySnapshot = [...new Set(input.dirtyFiles)].sort();
664
848
  const localChanged = new Set(input.localState?.changedFiles ?? []);
665
- const localClaimedPaths = [...new Set(input.localState?.claimedPaths ?? [])]
666
- .map((claim) => claim.trim())
667
- .filter(Boolean);
849
+ const localClaimedPaths = input.serverReportedEmptyScope
850
+ ? []
851
+ : [...new Set(input.localState?.claimedPaths ?? [])]
852
+ .map((claim) => claim.trim())
853
+ .filter(Boolean);
668
854
  const lanePatterns = input.localState?.laneDomain != null
669
855
  ? (input.domains.find((domain) => domain.domain === input.localState?.laneDomain)?.pathPatterns ?? [])
670
856
  : [];
@@ -672,20 +858,25 @@ function startupDirtyClassification(input) {
672
858
  .map((claim) => claim.trim())
673
859
  .filter(Boolean);
674
860
  const scopeProven = claimedPaths.length > 0 || lanePatterns.length > 0 || (input.localState?.changedFiles.length ?? 0) > 0;
675
- if (input.inferredMode) {
861
+ if (input.inferredMode && claimedPaths.length === 0) {
862
+ const postBaseline = input.localState?.startedAt != null
863
+ ? (0, preflight_changed_files_1.computeCurrentWorkFiles)(input.localState, workspaceDirtySnapshot)
864
+ : workspaceDirtySnapshot;
676
865
  return {
677
- acceptanceCandidateFiles: workspaceDirtySnapshot,
866
+ acceptanceCandidateFiles: postBaseline,
678
867
  workspaceDirtySnapshot,
679
868
  unscopedStartupDirty: [],
680
- scopeProven: workspaceDirtySnapshot.length > 0,
869
+ scopeProven: postBaseline.length > 0,
681
870
  };
682
871
  }
683
872
  const acceptanceCandidateFiles = workspaceDirtySnapshot.filter((file) => {
684
- if (localChanged.has(file))
873
+ if (!input.inferredMode && localChanged.has(file))
685
874
  return true;
686
875
  if (claimedPaths.length > 0) {
687
876
  return fileWithinScopedPaths(file, claimedPaths);
688
877
  }
878
+ if (localChanged.has(file))
879
+ return true;
689
880
  if (fileWithinScopedPaths(file, lanePatterns))
690
881
  return true;
691
882
  return false;
@@ -750,7 +941,7 @@ function buildStartupScopeDriftIssue(input) {
750
941
  return {
751
942
  errorCode: "SCOPE_DRIFT_OUT_OF_SCOPE_FILE",
752
943
  whatHappened: "Outside promised scope.",
753
- whyItMatters: "Out-of-scope edits break the declared task boundary and block acceptance.",
944
+ whyItMatters: "Out-of-scope edits break the declared scope boundary and are not safe to trust.",
754
945
  files,
755
946
  suggestedPrompt: [
756
947
  "You changed files outside the allowed scope.",
@@ -856,7 +1047,7 @@ async function runWatchOnceFastPath(input) {
856
1047
  category: "WATCH_ERROR",
857
1048
  what: "Watch startup timed out while running the one-shot review.",
858
1049
  why: "The server did not return the consolidated review in time.",
859
- next: "Run `agentbridge doctor`, then retry `agentbridge start`.",
1050
+ next: "Run `agentbridge doctor`, then retry `agentbridge watch`.",
860
1051
  suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
861
1052
  })));
862
1053
  }
@@ -872,7 +1063,7 @@ async function runWatchOnceFastPath(input) {
872
1063
  // surface the structured catalog error instead of crashing with a raw throw.
873
1064
  if ((0, http_1.isCliHttpError)(err)) {
874
1065
  const parsed = (0, http_1.parseCliHttpErrorBody)(err);
875
- const rawCode = parsed?.["code"] ?? parsed?.["error"] ?? "";
1066
+ const rawCode = (0, http_1.extractHttpErrorCode)(parsed);
876
1067
  const catalogCode = (0, error_catalog_1.mapServerCodeToCatalog)(rawCode);
877
1068
  const SESSION_BINDING_ERRORS = new Set([
878
1069
  "WORK_CONTEXT_SCOPE_MISMATCH",
@@ -889,7 +1080,7 @@ async function runWatchOnceFastPath(input) {
889
1080
  category: entry?.category ?? "WORK_CONTEXT_ERROR",
890
1081
  what: entry?.what ?? err.message,
891
1082
  why: entry?.why ?? "The server rejected the local session binding.",
892
- next: entry?.next ?? 'Run `agentbridge start` to create a new session.',
1083
+ next: entry?.next ?? 'Run `agentbridge watch --allow-dirty` to review current changes.',
893
1084
  suggestedPrompt: "AgentBridge found a different active task/scope. Do not continue on the wrong task. Clear or resolve the current run, then start the intended task again.",
894
1085
  });
895
1086
  }
@@ -919,7 +1110,10 @@ async function runWatchOnceFastPath(input) {
919
1110
  await flushStdout();
920
1111
  let hadBlockingIssue = false;
921
1112
  const acceptance = result.acceptance;
922
- const supervision = (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []);
1113
+ const persistedState = (0, session_state_1.readSessionState)();
1114
+ const supervision = persistedState
1115
+ ? finalizeWatchSupervision(persistedState, (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []))
1116
+ : (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []);
923
1117
  timing.trackSync("render_output", () => {
924
1118
  process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision)}\n`);
925
1119
  });
@@ -958,7 +1152,7 @@ async function runWatch(options = {}) {
958
1152
  }
959
1153
  const gatesState = (0, gates_1.ensureGatesFile)(repoRoot);
960
1154
  if (gatesState.malformed) {
961
- process.stdout.write("[agentbridge] WARNING: .agentbridge/gates.json is unreadable or malformed — treating all gates as warn_only.\n");
1155
+ process.stdout.write("[agentbridge] WARNING: .agentbridge/gates.json is unreadable or malformed — treating all gates as hard_fail.\n");
962
1156
  process.stderr.write(`[agentbridge:audit] ${JSON.stringify({
963
1157
  eventType: "GATES_FILE_PARSE_ERROR",
964
1158
  message: gatesState.parseError ?? "unknown parse error",
@@ -968,7 +1162,11 @@ async function runWatch(options = {}) {
968
1162
  const acceptanceRolloutOptions = acceptanceRolloutOptionsFromGates(gatesState.gates);
969
1163
  const taskScopeInput = parseTaskScopeInput(options);
970
1164
  const localSession = timing.trackSync("local_session_read", () => (0, session_state_1.readSessionState)());
971
- const hasActiveLocalSession = isActiveLocalSession(localSession);
1165
+ const hasActiveLocalSession = (0, session_state_1.isActiveLocalSession)(localSession);
1166
+ if (hasActiveLocalSession && localSession && cfg.domains?.length) {
1167
+ localSession.domains = cfg.domains;
1168
+ (0, session_state_1.writeSessionState)(localSession);
1169
+ }
972
1170
  const explicitStrictMode = Boolean(taskScopeInput.task && taskScopeInput.scope);
973
1171
  if (hasActiveLocalSession && explicitStrictMode) {
974
1172
  const activeLocalSession = localSession;
@@ -986,8 +1184,8 @@ async function runWatch(options = {}) {
986
1184
  `Current scope: ${(activeLocalSession.claimedPaths ?? []).join(", ") || "(unknown)"}`,
987
1185
  "",
988
1186
  "Next action:",
989
- "- Finish/clear the current task (agentbridge done or agentbridge session abandon --reason \"...\")",
990
- '- Or explicitly start a new one: agentbridge start --summary "<new task>" --scope "<new scope>"',
1187
+ "- Finish or clear the current session (agentbridge done or agentbridge session abandon --reason \"...\")",
1188
+ '- Or run watch with a new explicit task: agentbridge watch --task "<new task>" --scope "<new scope>" --allow-dirty',
991
1189
  "",
992
1190
  ].join("\n"));
993
1191
  process.exitCode = 1;
@@ -1012,27 +1210,27 @@ async function runWatch(options = {}) {
1012
1210
  await flushStdout();
1013
1211
  process.stdout.write(renderStartupPhase("resolving project", "starting"));
1014
1212
  await flushStdout();
1213
+ const hasRecoveredDomainMap = Boolean(cfg.domains && cfg.domains.length > 0);
1015
1214
  timing.trackSync("identity_resolution", () => {
1016
- if (!cfg.activeAgentId) {
1215
+ if (explicitStrictMode && !cfg.activeAgentId) {
1017
1216
  throw new errors_1.SafeCliError({
1018
1217
  code: "IDENTITY_NO_ACTIVE_AGENT",
1019
1218
  category: "IDENTITY_ERROR",
1020
- what: "No active agent configured.",
1021
- why: "Watch needs a WorkIdentity selected for this repo.",
1219
+ what: "No active agent configured for strict watch mode.",
1220
+ why: "Strict watch mode needs an internal WorkIdentity selection for scoped task ownership.",
1022
1221
  next: "Run `agentbridge identity list` then `agentbridge use <agent-id>`.",
1023
1222
  });
1024
1223
  }
1025
- if (!cfg.domains || cfg.domains.length === 0) {
1026
- throw new errors_1.SafeCliError({
1027
- code: "CONFIG_NO_DOMAIN_MAP",
1028
- category: "CONFIG_ERROR",
1029
- what: "No recovered domain map found in .agentbridge/config.json.",
1030
- why: "Watch uses domain boundaries to enforce scoped file tracking.",
1031
- next: "Run `agentbridge init` to recover domains for this repo.",
1032
- });
1033
- }
1034
1224
  });
1035
- let activeAgentId = cfg.activeAgentId;
1225
+ if (!explicitStrictMode && !cfg.activeAgentId) {
1226
+ process.stdout.write("Startup note: no active internal agent selected. Inferred mode will continue using room-level connection and local supervision.\n");
1227
+ await flushStdout();
1228
+ }
1229
+ if (!hasRecoveredDomainMap) {
1230
+ process.stdout.write("Startup note: domain map is not fully recovered yet. AgentBridge will keep watching with best-effort drift checks.\n");
1231
+ await flushStdout();
1232
+ }
1233
+ let activeAgentId = cfg.activeAgentId ?? "inferred-room-connection";
1036
1234
  if (options.changeRequestId || options.executionSurfaceId) {
1037
1235
  (0, config_1.updateConfig)({
1038
1236
  ...(options.changeRequestId ? { activeChangeRequestId: options.changeRequestId } : {}),
@@ -1058,7 +1256,7 @@ async function runWatch(options = {}) {
1058
1256
  category: "WATCH_ERROR",
1059
1257
  what: "Watch startup timed out while resolving project context.",
1060
1258
  why: "Project/session context could not be resolved in time.",
1061
- next: "Run `agentbridge doctor`, then retry `agentbridge start`.",
1259
+ next: "Run `agentbridge doctor`, then retry `agentbridge watch`.",
1062
1260
  suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
1063
1261
  })));
1064
1262
  }
@@ -1076,7 +1274,7 @@ async function runWatch(options = {}) {
1076
1274
  const canUseFastPath = acceptanceRolloutOptions.rolloutProofTooWeak === "warn_only" &&
1077
1275
  acceptanceRolloutOptions.rolloutProofNotRelevant === "warn_only" &&
1078
1276
  acceptanceRolloutOptions.rolloutImpactCoverageGap === "warn_only";
1079
- if (options.once && explicitStrictMode && canUseFastPath) {
1277
+ if (options.once && explicitStrictMode) {
1080
1278
  process.stdout.write(renderStartupPhase("starting task", "starting"));
1081
1279
  await flushStdout();
1082
1280
  startingTaskAnnounced = true;
@@ -1117,7 +1315,7 @@ async function runWatch(options = {}) {
1117
1315
  category: "WATCH_ERROR",
1118
1316
  what: "Watch startup timed out while starting scoped task/session.",
1119
1317
  why: "Internal start did not complete before timeout.",
1120
- next: "Run `agentbridge start --summary \"...\" --scope \"...\"` manually and retry watch.",
1318
+ next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` for strict mode, or retry watch.",
1121
1319
  suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
1122
1320
  })));
1123
1321
  }
@@ -1130,7 +1328,7 @@ async function runWatch(options = {}) {
1130
1328
  category: "WATCH_ERROR",
1131
1329
  what: "Watch could not create or resume the scoped task/session.",
1132
1330
  why: reason,
1133
- next: "Run `agentbridge start --summary \"...\" --scope \"...\"` and retry watch.",
1331
+ next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` and retry.",
1134
1332
  });
1135
1333
  }
1136
1334
  process.stdout.write(renderStartupPhase("starting task", "done"));
@@ -1142,16 +1340,24 @@ async function runWatch(options = {}) {
1142
1340
  const startupSession = (0, session_state_1.readSessionState)();
1143
1341
  if (explicitStrictMode) {
1144
1342
  if (!startupSession || startupSession.status === "closed" || startupSession.id === "none") {
1145
- throw new errors_1.SafeCliError({
1146
- code: "WATCH_STARTUP_SESSION_MISSING",
1147
- category: "WATCH_ERROR",
1148
- what: "Watch startup could not find an active local session.",
1149
- why: "Session creation/resume did not complete.",
1150
- next: "Run `agentbridge start --summary \"...\" --scope \"...\"` and retry watch.",
1151
- });
1343
+ if (options.once) {
1344
+ process.stdout.write("[startup] session bootstrap unavailable; continuing one-shot strict supervision without local session binding.\n");
1345
+ await flushStdout();
1346
+ }
1347
+ else {
1348
+ throw new errors_1.SafeCliError({
1349
+ code: "WATCH_STARTUP_SESSION_MISSING",
1350
+ category: "WATCH_ERROR",
1351
+ what: "Watch startup could not find an active local session.",
1352
+ why: "Session creation/resume did not complete.",
1353
+ next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` and retry.",
1354
+ });
1355
+ }
1356
+ }
1357
+ if (startupSession?.id) {
1358
+ process.stdout.write(`[startup] session created/resumed: ${startupSession.id}\n`);
1359
+ await flushStdout();
1152
1360
  }
1153
- process.stdout.write(`[startup] session created/resumed: ${startupSession.id}\n`);
1154
- await flushStdout();
1155
1361
  }
1156
1362
  else {
1157
1363
  process.stdout.write("[startup] session: inferred mode (will start tracking when coding begins)\n");
@@ -1281,29 +1487,34 @@ async function runWatch(options = {}) {
1281
1487
  : options.changeRequestId ?? state?.changeRequestId;
1282
1488
  const executionSurfaceId = options.executionSurfaceId ?? cfg.executionSurfaceId;
1283
1489
  if (changeRequestId && executionSurfaceId) {
1284
- try {
1285
- const ctx = (0, config_1.contextFromConfig)();
1286
- const opened = await openOrResumeServerSessionForWatch({
1287
- ctx,
1288
- changeRequestId,
1289
- executionSurfaceId,
1290
- file,
1291
- activeAgentId,
1292
- inferredLaneDomain: lane.laneDomain,
1293
- });
1294
- if (opened) {
1295
- serverSessionId = opened.workSessionId;
1296
- if (opened.resumed) {
1297
- process.stdout.write(`Resumed existing run: ${opened.workSessionId}\n`);
1490
+ if (activeAgentId === "inferred-room-connection") {
1491
+ process.stdout.write("Server session sync deferred: internal agent selection is unavailable in inferred mode.\n");
1492
+ }
1493
+ else {
1494
+ try {
1495
+ const ctx = (0, config_1.contextFromConfig)();
1496
+ const opened = await openOrResumeServerSessionForWatch({
1497
+ ctx,
1498
+ changeRequestId,
1499
+ executionSurfaceId,
1500
+ file,
1501
+ activeAgentId,
1502
+ inferredLaneDomain: lane.laneDomain,
1503
+ });
1504
+ if (opened) {
1505
+ serverSessionId = opened.workSessionId;
1506
+ if (opened.resumed) {
1507
+ process.stdout.write(`Resumed existing run: ${opened.workSessionId}\n`);
1508
+ }
1509
+ }
1510
+ else {
1511
+ process.stdout.write("Task is already running but no unique active run could be resolved; continuing local-only.\n");
1298
1512
  }
1299
1513
  }
1300
- else {
1301
- process.stdout.write("Task is already running but no unique active run could be resolved; continuing local-only.\n");
1514
+ catch (err) {
1515
+ process.stdout.write(`Server session open failed; continuing local-only: ${String(err)}\n`);
1302
1516
  }
1303
1517
  }
1304
- catch (err) {
1305
- process.stdout.write(`Server session open failed; continuing local-only: ${String(err)}\n`);
1306
- }
1307
1518
  }
1308
1519
  state = (0, session_1.openLocalSession)({
1309
1520
  agentId: activeAgentId,
@@ -1312,16 +1523,17 @@ async function runWatch(options = {}) {
1312
1523
  domains: cfg.domains ?? [],
1313
1524
  serverSessionId,
1314
1525
  });
1315
- process.stdout.write([
1526
+ const sessionBannerLines = [
1316
1527
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
1317
1528
  " AgentBridge session started",
1318
1529
  "",
1319
- ` Identity ${state.agentId}`,
1320
- ` Allowed lane ${state.laneDomain ?? "unclassified"}`,
1321
- ` Server sync ${state.serverSessionId ? "enabled" : "local-only"}`,
1322
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
1323
- "",
1324
- ].join("\n"));
1530
+ ` Mode ${explicitStrictMode ? "strict/scoped" : "inferred"}`,
1531
+ ];
1532
+ if (explicitStrictMode || options.details) {
1533
+ sessionBannerLines.push(` Allowed lane ${state.laneDomain ?? "unclassified"}`, ` Server sync ${state.serverSessionId ? "enabled" : "local-only"}`);
1534
+ }
1535
+ sessionBannerLines.push("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", "");
1536
+ process.stdout.write(sessionBannerLines.join("\n"));
1325
1537
  }
1326
1538
  const outcome = (0, watch_core_1.applyFileChange)(state, file);
1327
1539
  (0, session_state_1.writeSessionState)(state);
@@ -1354,20 +1566,28 @@ async function runWatch(options = {}) {
1354
1566
  const nextSummary = shouldRenderSupervisionSummary(lastSupervisionSignature, supervision);
1355
1567
  if (nextSummary.shouldRender) {
1356
1568
  timing.trackSync("render_output", () => {
1357
- process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision)}\n`);
1569
+ process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, {
1570
+ compact: !explicitStrictMode && !options.details,
1571
+ })}\n`);
1358
1572
  });
1359
1573
  lastSupervisionSignature = nextSummary.nextSignature;
1360
1574
  }
1575
+ const renderSupervisionProofIssue = explicitStrictMode || !options.once;
1361
1576
  if (supervisionResult.acceptanceReport) {
1362
1577
  const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(supervisionResult.acceptanceReport, {
1363
1578
  strictScope: explicitStrictMode,
1364
1579
  }));
1365
1580
  const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
1366
- if (blockingIssue && issueSignature && issueSignature !== lastBlockingIssueSignature) {
1581
+ if (blockingIssue &&
1582
+ issueSignature &&
1583
+ issueSignature !== lastBlockingIssueSignature &&
1584
+ renderSupervisionProofIssue) {
1367
1585
  timing.trackSync("render_output", () => {
1368
1586
  process.stdout.write(renderWatchBlockingIssue(blockingIssue));
1369
1587
  });
1370
- hadBlockingIssue = true;
1588
+ if (explicitStrictMode) {
1589
+ hadBlockingIssue = true;
1590
+ }
1371
1591
  lastBlockingIssueSignature = issueSignature;
1372
1592
  }
1373
1593
  else if (!issueSignature) {
@@ -1377,11 +1597,16 @@ async function runWatch(options = {}) {
1377
1597
  else {
1378
1598
  const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildFallbackWatchBlockingIssue(supervision));
1379
1599
  const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
1380
- if (blockingIssue && issueSignature && issueSignature !== lastBlockingIssueSignature) {
1600
+ if (blockingIssue &&
1601
+ issueSignature &&
1602
+ issueSignature !== lastBlockingIssueSignature &&
1603
+ renderSupervisionProofIssue) {
1381
1604
  timing.trackSync("render_output", () => {
1382
1605
  process.stdout.write(renderWatchBlockingIssue(blockingIssue));
1383
1606
  });
1384
- hadBlockingIssue = true;
1607
+ if (explicitStrictMode) {
1608
+ hadBlockingIssue = true;
1609
+ }
1385
1610
  lastBlockingIssueSignature = issueSignature;
1386
1611
  }
1387
1612
  else if (!issueSignature) {
@@ -1407,6 +1632,10 @@ async function runWatch(options = {}) {
1407
1632
  if (!outcome.requiresAuthorityPrompt)
1408
1633
  return;
1409
1634
  if (outcome.requestType === "handshake") {
1635
+ if (!explicitStrictMode &&
1636
+ (outcome.crossingTier === "tier_d" || outcome.crossingTier === "tier_c")) {
1637
+ return;
1638
+ }
1410
1639
  const crossingDomain = outcome.crossingDomain;
1411
1640
  if (crossingDomain && !domainPacketsLoaded.has(crossingDomain)) {
1412
1641
  domainPacketsLoaded.add(crossingDomain);
@@ -1451,9 +1680,13 @@ async function runWatch(options = {}) {
1451
1680
  file,
1452
1681
  tier: outcome.crossingTier,
1453
1682
  })}\n`);
1454
- const response = options.daemon
1683
+ const skipInteractivePrompt = options.daemon || (options.once && !explicitStrictMode);
1684
+ const response = skipInteractivePrompt
1455
1685
  ? { decision: "deny" }
1456
1686
  : await promptDecision();
1687
+ if (skipInteractivePrompt && !options.daemon) {
1688
+ return;
1689
+ }
1457
1690
  if (options.daemon) {
1458
1691
  process.stdout.write(`[daemon] Domain crossing denied automatically (no interactive prompt in daemon mode). Use the dashboard to approve.\n`);
1459
1692
  }
@@ -1585,16 +1818,29 @@ async function runWatch(options = {}) {
1585
1818
  return;
1586
1819
  }
1587
1820
  let startupState = (0, session_state_1.readSessionState)();
1821
+ if (!explicitStrictMode && !(0, session_state_1.isActiveLocalSession)(startupState)) {
1822
+ const lane = (0, domain_resolution_1.inferLaneFromFiles)(startupDirtyFiles, cfg.domains ?? []);
1823
+ startupState = (0, session_1.openLocalSession)({
1824
+ agentId: activeAgentId,
1825
+ laneDomain: lane.laneDomain,
1826
+ claimedPaths: [...(startupState?.claimedPaths ?? [])],
1827
+ domains: cfg.domains ?? [],
1828
+ serverSessionId: startupState?.serverSessionId,
1829
+ changeRequestId: startupState?.changeRequestId,
1830
+ });
1831
+ (0, session_state_1.writeSessionState)(startupState);
1832
+ }
1588
1833
  const changeRequestId = explicitStrictMode
1589
1834
  ? options.changeRequestId ?? cfg.activeChangeRequestId ?? null
1590
1835
  : options.changeRequestId ?? startupState?.changeRequestId ?? null;
1591
1836
  let startupClaimedPaths = [];
1592
1837
  let startupServerSessionId = null;
1838
+ let serverReportedEmptyScope = false;
1593
1839
  startupClaimedPaths = [...(startupState?.claimedPaths ?? [])];
1594
1840
  if (startupState?.serverSessionId) {
1595
1841
  startupServerSessionId = startupState.serverSessionId;
1596
1842
  }
1597
- else if (changeRequestId) {
1843
+ else if (changeRequestId && explicitStrictMode) {
1598
1844
  try {
1599
1845
  const ctx = (0, config_1.contextFromConfig)();
1600
1846
  const resolved = await (0, work_context_resolver_1.findSingleActiveSessionForChangeRequest)(ctx, changeRequestId);
@@ -1606,11 +1852,22 @@ async function runWatch(options = {}) {
1606
1852
  startupServerSessionId = null;
1607
1853
  }
1608
1854
  }
1609
- if (startupServerSessionId) {
1855
+ if (startupServerSessionId && (explicitStrictMode || startupState?.serverSessionId)) {
1610
1856
  try {
1611
1857
  const ctx = (0, config_1.contextFromConfig)();
1612
1858
  const report = await timing.trackAsync("acceptance_check_fetch", async () => (0, server_sync_1.fetchAcceptanceCheck)(ctx, { workSessionId: startupServerSessionId, ...acceptanceRolloutOptions }));
1613
- startupClaimedPaths = [...new Set([...startupClaimedPaths, ...(report.claimed_paths ?? [])])];
1859
+ const serverClaimedPaths = report.claimed_paths ?? [];
1860
+ if (serverClaimedPaths.length > 0) {
1861
+ startupClaimedPaths = [...new Set([...startupClaimedPaths, ...serverClaimedPaths])];
1862
+ }
1863
+ else if (explicitStrictMode) {
1864
+ startupClaimedPaths = [...new Set(startupClaimedPaths)];
1865
+ }
1866
+ else {
1867
+ // Server session exists but reports no claimed scope — default mode treats all dirty files as in play.
1868
+ startupClaimedPaths = [];
1869
+ serverReportedEmptyScope = true;
1870
+ }
1614
1871
  }
1615
1872
  catch {
1616
1873
  // Keep local claimed paths when server claimed paths are unavailable.
@@ -1622,13 +1879,20 @@ async function runWatch(options = {}) {
1622
1879
  claimedPaths: startupClaimedPaths,
1623
1880
  domains: cfg.domains ?? [],
1624
1881
  inferredMode: !explicitStrictMode,
1882
+ serverReportedEmptyScope,
1625
1883
  }));
1626
1884
  if (startupClassification.workspaceDirtySnapshot.length === 0 && !explicitStrictMode) {
1627
1885
  process.stdout.write(renderBrainstormingStatus());
1628
1886
  }
1629
1887
  else if (!explicitStrictMode) {
1630
1888
  const inferredTask = inferTaskFromFiles(startupClassification.workspaceDirtySnapshot, cfg.domains ?? []);
1631
- process.stdout.write(renderCodingDetection(startupClassification.workspaceDirtySnapshot, inferredTask));
1889
+ process.stdout.write(renderCodingDetection({
1890
+ changedFiles: startupClassification.workspaceDirtySnapshot,
1891
+ inferredTask,
1892
+ localState: startupState,
1893
+ domains: cfg.domains ?? [],
1894
+ details: options.details,
1895
+ }));
1632
1896
  }
1633
1897
  else {
1634
1898
  process.stdout.write(`Startup dirty snapshot detected ${startupClassification.workspaceDirtySnapshot.length} file(s).\n`);
@@ -1679,8 +1943,59 @@ async function runWatch(options = {}) {
1679
1943
  (0, session_state_1.writeSessionState)(startupState);
1680
1944
  process.stdout.write(`Linked local session ${startupState.id} to server session ${startupServerSessionId} before startup supervision.\n`);
1681
1945
  }
1682
- for (const file of startupClassification.acceptanceCandidateFiles) {
1683
- await onAbsolutePathChange((0, node_path_1.resolve)((0, node_process_1.cwd)(), file));
1946
+ if (options.once && !explicitStrictMode) {
1947
+ const postBaselineFiles = startupClassification.acceptanceCandidateFiles;
1948
+ const firstChanged = postBaselineFiles[0];
1949
+ if (firstChanged) {
1950
+ await onAbsolutePathChange((0, node_path_1.resolve)((0, node_process_1.cwd)(), firstChanged));
1951
+ }
1952
+ const seededState = (0, session_state_1.readSessionState)();
1953
+ if (seededState) {
1954
+ seededState.changedFiles = [...postBaselineFiles];
1955
+ (0, session_state_1.writeSessionState)(seededState);
1956
+ }
1957
+ }
1958
+ else {
1959
+ const startupFilesForChangeHandler = startupClassification.acceptanceCandidateFiles;
1960
+ for (const file of startupFilesForChangeHandler) {
1961
+ await onAbsolutePathChange((0, node_path_1.resolve)((0, node_process_1.cwd)(), file));
1962
+ }
1963
+ }
1964
+ if (options.once && !explicitStrictMode && startupClassification.acceptanceCandidateFiles.length > 0) {
1965
+ const localProofEvaluation = timing.trackSync("local_proof_evaluation", () => (0, local_proof_1.evaluateLocalProof)(startupClassification.acceptanceCandidateFiles, (0, session_state_1.readSessionState)()?.lastLocalVerificationRun));
1966
+ const localProofIssue = (0, local_proof_1.buildLocalProofBlockingIssue)(localProofEvaluation);
1967
+ if (localProofIssue) {
1968
+ timing.trackSync("render_output", () => {
1969
+ process.stdout.write(renderWatchBlockingIssue(localProofIssue));
1970
+ });
1971
+ hadBlockingIssue = true;
1972
+ }
1973
+ const domainDrift = detectInferredDomainDrift(startupClassification.acceptanceCandidateFiles, cfg.domains ?? []);
1974
+ if (domainDrift) {
1975
+ timing.trackSync("render_output", () => {
1976
+ process.stdout.write(renderWatchBlockingIssue(domainDrift.issue));
1977
+ });
1978
+ if (domainDrift.blocking) {
1979
+ hadBlockingIssue = true;
1980
+ }
1981
+ }
1982
+ }
1983
+ if (options.once && !explicitStrictMode) {
1984
+ const finalState = (0, session_state_1.readSessionState)();
1985
+ if (finalState && (0, session_state_1.isActiveLocalSession)(finalState)) {
1986
+ const postBaseline = timing.trackSync("baseline_classification", () => (0, preflight_changed_files_1.computeCurrentWorkFiles)(finalState));
1987
+ const supervision = finalizeWatchSupervision(finalState, (0, supervision_1.fallbackSupervisionSnapshot)({
1988
+ workSessionId: finalState.serverSessionId ?? finalState.id,
1989
+ changeRequestId: finalState.changeRequestId ?? changeRequestId ?? null,
1990
+ changedFiles: postBaseline.length > 0 ? postBaseline : [...(finalState.changedFiles ?? [])],
1991
+ domains: cfg.domains ?? [],
1992
+ unresolvedProtectedCrossing: false,
1993
+ blocked: finalState.status === "blocked",
1994
+ }));
1995
+ timing.trackSync("render_output", () => {
1996
+ process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, { compact: true })}\n`);
1997
+ });
1998
+ }
1684
1999
  }
1685
2000
  };
1686
2001
  let stop = null;