@runfusion/fusion 0.37.0 → 0.38.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.
Files changed (57) hide show
  1. package/dist/bin.js +1510 -234
  2. package/dist/client/.vite/manifest.json +58 -58
  3. package/dist/client/assets/AgentDetailView-CquLtIia.js +18 -0
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-niLSqGAE.js → AgentPermissionPolicyEditor-Dk-oEs1X.js} +1 -1
  5. package/dist/client/assets/AgentsView-BZVOpQ05.js +29 -0
  6. package/dist/client/assets/ChatView-CErtz86Y.js +6 -0
  7. package/dist/client/assets/{DevServerView-0Gb44WOb.js → DevServerView-BRA00XoT.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-BSQA0XM9.js → DirectoryPicker-D496NTjZ.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-XZbaqEFF.js → DocumentsView-DIRlgdeB.js} +1 -1
  10. package/dist/client/assets/{EvalsView-CJNtXxX9.js → EvalsView-By5mtr80.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DR8E3i36.js → ExperimentalAgentOnboardingModal-Br57oNdX.js} +1 -1
  12. package/dist/client/assets/GoalsView-B65urz9k.css +1 -0
  13. package/dist/client/assets/{GoalsView-CN1zQ_Ax.js → GoalsView-COI14CWJ.js} +1 -1
  14. package/dist/client/assets/{InsightsView-D_jRaMyn.js → InsightsView-Hm7vX2lr.js} +2 -2
  15. package/dist/client/assets/{MemoryView-D90vdPnp.js → MemoryView-BLozyYur.js} +1 -1
  16. package/dist/client/assets/{NodesView-C1zuedAe.js → NodesView-bXC6WoE2.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-DBJDDVfM.js → PiExtensionsManager-DEiqcMkx.js} +2 -2
  18. package/dist/client/assets/{PluginManager-Ceg64P2Z.js → PluginManager-l4224zxk.js} +1 -1
  19. package/dist/client/assets/{ResearchView-XRwWdczc.js → ResearchView-CpsR1Q56.js} +1 -1
  20. package/dist/client/assets/{SecretsView-TCrKyCme.js → SecretsView-Bwme1zZI.js} +1 -1
  21. package/dist/client/assets/{SettingsModal-C1TczsEr.js → SettingsModal-32vUNWRe.js} +1 -1
  22. package/dist/client/assets/SettingsModal-D_uIvf6n.css +1 -0
  23. package/dist/client/assets/SettingsModal-DdrXxMPW.js +31 -0
  24. package/dist/client/assets/{SetupWizardModal-D6moSTT3.js → SetupWizardModal-BFD9E0H_.js} +1 -1
  25. package/dist/client/assets/{SkillsView-BDHLPVBi.js → SkillsView-CZMLY7F_.js} +1 -1
  26. package/dist/client/assets/{StashRecoveryView-DZGa591R.js → StashRecoveryView-jgZeWbZo.js} +1 -1
  27. package/dist/client/assets/{TodoView-nr_SnCJv.js → TodoView-DXdnOymj.js} +2 -2
  28. package/dist/client/assets/{dashboard-view-0XeZ1FTR.js → dashboard-view-H8SnaCPP.js} +2 -2
  29. package/dist/client/assets/{folder-open-iURwQbEg.js → folder-open-Dh5_gv6L.js} +1 -1
  30. package/dist/client/assets/index-B9hAW3eU.css +1 -0
  31. package/dist/client/assets/index-w1dPHtmq.js +742 -0
  32. package/dist/client/assets/{upload-Beqg4S4L.js → upload-ntU2YHPl.js} +1 -1
  33. package/dist/client/assets/{users-C2o86ZCR.js → users-BWK61s9U.js} +1 -1
  34. package/dist/client/index.html +40 -3
  35. package/dist/client/version.json +1 -1
  36. package/dist/droid-cli/package.json +1 -1
  37. package/dist/extension.js +1326 -182
  38. package/dist/pi-claude-cli/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  40. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  42. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  43. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  44. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  46. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  47. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  48. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  49. package/package.json +1 -1
  50. package/dist/client/assets/AgentDetailView-T_AHPxcG.js +0 -18
  51. package/dist/client/assets/AgentsView-CQhaLHz_.js +0 -29
  52. package/dist/client/assets/ChatView-B60kYfdk.js +0 -6
  53. package/dist/client/assets/GoalsView-CtWi0fxE.css +0 -1
  54. package/dist/client/assets/SettingsModal-0gevFhXV.css +0 -1
  55. package/dist/client/assets/SettingsModal-B8fRuWdQ.js +0 -31
  56. package/dist/client/assets/index-CjuQj4qe.js +0 -742
  57. package/dist/client/assets/index-DmrpDn7c.css +0 -1
package/dist/bin.js CHANGED
@@ -4674,7 +4674,7 @@ var init_db = __esm({
4674
4674
  DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
4675
4675
  DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
4676
4676
  DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
4677
- SCHEMA_VERSION = 92;
4677
+ SCHEMA_VERSION = 99;
4678
4678
  SCHEMA_SQL = `
4679
4679
  -- Tasks table with JSON columns for nested data
4680
4680
  CREATE TABLE IF NOT EXISTS tasks (
@@ -4696,6 +4696,7 @@ CREATE TABLE IF NOT EXISTS tasks (
4696
4696
  pausedReason TEXT,
4697
4697
  baseBranch TEXT,
4698
4698
  branch TEXT,
4699
+ autoMerge INTEGER,
4699
4700
  executionStartBranch TEXT,
4700
4701
  baseCommitSha TEXT,
4701
4702
  modelPresetId TEXT,
@@ -4707,12 +4708,16 @@ CREATE TABLE IF NOT EXISTS tasks (
4707
4708
  planningModelId TEXT,
4708
4709
  mergeRetries INTEGER,
4709
4710
  workflowStepRetries INTEGER,
4711
+ resumeLimboCount INTEGER DEFAULT 0,
4712
+ resumeLimboTipSha TEXT,
4713
+ resumeLimboStepSignature TEXT,
4710
4714
  recoveryRetryCount INTEGER,
4711
4715
  taskDoneRetryCount INTEGER DEFAULT 0,
4712
4716
  worktreeSessionRetryCount INTEGER DEFAULT 0,
4713
4717
  completionHandoffLimboRecoveryCount INTEGER DEFAULT 0,
4714
4718
  mergeConflictBounceCount INTEGER DEFAULT 0,
4715
4719
  mergeAuditBounceCount INTEGER DEFAULT 0,
4720
+ mergeTransientRetryCount INTEGER DEFAULT 0,
4716
4721
  nextRecoveryAt TEXT,
4717
4722
  error TEXT,
4718
4723
  summary TEXT,
@@ -5222,11 +5227,31 @@ CREATE TABLE IF NOT EXISTS missions (
5222
5227
  status TEXT NOT NULL,
5223
5228
  interviewState TEXT NOT NULL,
5224
5229
  baseBranch TEXT,
5230
+ branchStrategy TEXT,
5225
5231
  autoAdvance INTEGER DEFAULT 0,
5232
+ autoMerge INTEGER,
5226
5233
  createdAt TEXT NOT NULL,
5227
5234
  updatedAt TEXT NOT NULL
5228
5235
  );
5229
5236
 
5237
+ CREATE TABLE IF NOT EXISTS branch_groups (
5238
+ id TEXT PRIMARY KEY,
5239
+ sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
5240
+ sourceId TEXT NOT NULL,
5241
+ branchName TEXT NOT NULL UNIQUE,
5242
+ worktreePath TEXT,
5243
+ autoMerge INTEGER NOT NULL DEFAULT 0,
5244
+ prState TEXT NOT NULL DEFAULT 'none' CHECK (prState IN ('none','open','merged','closed')),
5245
+ prUrl TEXT,
5246
+ prNumber INTEGER,
5247
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','finalized','abandoned')),
5248
+ createdAt INTEGER NOT NULL,
5249
+ updatedAt INTEGER NOT NULL,
5250
+ closedAt INTEGER
5251
+ );
5252
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsSource ON branch_groups(sourceType, sourceId);
5253
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsBranchName ON branch_groups(branchName);
5254
+
5230
5255
  -- Goals table (strategic intent across mission timelines)
5231
5256
  CREATE TABLE IF NOT EXISTS goals (
5232
5257
  id TEXT PRIMARY KEY,
@@ -5238,6 +5263,22 @@ CREATE TABLE IF NOT EXISTS goals (
5238
5263
  );
5239
5264
  CREATE INDEX IF NOT EXISTS idxGoalsStatus ON goals(status);
5240
5265
 
5266
+ CREATE TABLE IF NOT EXISTS goal_citations (
5267
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5268
+ goalId TEXT NOT NULL,
5269
+ agentId TEXT NOT NULL,
5270
+ taskId TEXT,
5271
+ surface TEXT NOT NULL,
5272
+ sourceRef TEXT NOT NULL,
5273
+ snippet TEXT NOT NULL,
5274
+ timestamp TEXT NOT NULL
5275
+ );
5276
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsGoalId ON goal_citations(goalId);
5277
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsAgentId ON goal_citations(agentId);
5278
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsTimestamp ON goal_citations(timestamp);
5279
+ CREATE UNIQUE INDEX IF NOT EXISTS uxGoalCitationsDedup
5280
+ ON goal_citations(goalId, surface, sourceRef);
5281
+
5241
5282
  -- Milestones table (phases within a mission)
5242
5283
  CREATE TABLE IF NOT EXISTS milestones (
5243
5284
  id TEXT PRIMARY KEY,
@@ -5534,6 +5575,7 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
5534
5575
  assertion: "TEXT NOT NULL",
5535
5576
  status: "TEXT NOT NULL DEFAULT 'pending'",
5536
5577
  orderIndex: "INTEGER NOT NULL DEFAULT 0",
5578
+ sourceFeatureId: "TEXT",
5537
5579
  createdAt: "TEXT NOT NULL",
5538
5580
  updatedAt: "TEXT NOT NULL"
5539
5581
  },
@@ -7337,6 +7379,11 @@ This means a caller passed a .fusion directory where a project root was expected
7337
7379
  }
7338
7380
  if (version < 75) {
7339
7381
  this.applyMigration(75, () => {
7382
+ this.addColumnIfMissing("tasks", "mergeTransientRetryCount", "INTEGER DEFAULT 0");
7383
+ });
7384
+ }
7385
+ if (version < 76) {
7386
+ this.applyMigration(76, () => {
7340
7387
  this.db.exec(`
7341
7388
  CREATE TABLE IF NOT EXISTS experiment_sessions (
7342
7389
  id TEXT PRIMARY KEY,
@@ -7377,38 +7424,38 @@ This means a caller passed a .fusion directory where a project root was expected
7377
7424
  this.db.exec(`CREATE INDEX IF NOT EXISTS idxExperimentRecordsType ON experiment_session_records(sessionId, type)`);
7378
7425
  });
7379
7426
  }
7380
- if (version < 76) {
7381
- this.applyMigration(76, () => {
7427
+ if (version < 77) {
7428
+ this.applyMigration(77, () => {
7382
7429
  this.addColumnIfMissing("workflow_steps", "gateMode", "TEXT NOT NULL DEFAULT 'advisory'");
7383
7430
  this.db.exec("UPDATE workflow_steps SET gateMode = 'advisory'");
7384
7431
  });
7385
7432
  }
7386
- if (version < 77) {
7387
- this.applyMigration(77, () => {
7433
+ if (version < 78) {
7434
+ this.applyMigration(78, () => {
7388
7435
  this.addColumnIfMissing("tasks", "tokenBudgetSoftAlertedAt", "TEXT");
7389
7436
  this.addColumnIfMissing("tasks", "tokenBudgetHardAlertedAt", "TEXT");
7390
7437
  this.addColumnIfMissing("tasks", "tokenBudgetOverride", "TEXT");
7391
7438
  });
7392
7439
  }
7393
- if (version < 78) {
7394
- this.applyMigration(78, () => {
7440
+ if (version < 79) {
7441
+ this.applyMigration(79, () => {
7395
7442
  this.addColumnIfMissing("tasks", "branchConflictRecoveryCount", "INTEGER DEFAULT 0");
7396
7443
  this.addColumnIfMissing("tasks", "reviewerContextRetryCount", "INTEGER DEFAULT 0");
7397
7444
  this.addColumnIfMissing("tasks", "reviewerFallbackRetryCount", "INTEGER DEFAULT 0");
7398
7445
  });
7399
7446
  }
7400
- if (version < 79) {
7401
- this.applyMigration(79, () => {
7402
- this.addColumnIfMissing("tasks", "overlapBlockedBy", "TEXT");
7403
- });
7404
- }
7405
7447
  if (version < 80) {
7406
7448
  this.applyMigration(80, () => {
7407
- this.addColumnIfMissing("milestones", "acceptanceCriteria", "TEXT");
7449
+ this.addColumnIfMissing("tasks", "overlapBlockedBy", "TEXT");
7408
7450
  });
7409
7451
  }
7410
7452
  if (version < 81) {
7411
7453
  this.applyMigration(81, () => {
7454
+ this.addColumnIfMissing("milestones", "acceptanceCriteria", "TEXT");
7455
+ });
7456
+ }
7457
+ if (version < 82) {
7458
+ this.applyMigration(82, () => {
7412
7459
  this.addColumnIfMissing("tasks", "firstExecutionAt", "TEXT");
7413
7460
  this.addColumnIfMissing("tasks", "cumulativeActiveMs", "INTEGER");
7414
7461
  if (this.hasColumn("tasks", "executionStartedAt")) {
@@ -7421,13 +7468,13 @@ This means a caller passed a .fusion directory where a project root was expected
7421
7468
  }
7422
7469
  });
7423
7470
  }
7424
- if (version < 82) {
7425
- this.applyMigration(82, () => {
7471
+ if (version < 83) {
7472
+ this.applyMigration(83, () => {
7426
7473
  this.addColumnIfMissing("tasks", "worktreeSessionRetryCount", "INTEGER DEFAULT 0");
7427
7474
  });
7428
7475
  }
7429
- if (version < 83) {
7430
- this.applyMigration(83, () => {
7476
+ if (version < 84) {
7477
+ this.applyMigration(84, () => {
7431
7478
  if (!this.hasTable("secrets")) {
7432
7479
  this.db.exec(`
7433
7480
  CREATE TABLE secrets (
@@ -7451,8 +7498,8 @@ This means a caller passed a .fusion directory where a project root was expected
7451
7498
  }
7452
7499
  });
7453
7500
  }
7454
- if (version < 84) {
7455
- this.applyMigration(84, () => {
7501
+ if (version < 85) {
7502
+ this.applyMigration(85, () => {
7456
7503
  if (!this.hasColumn("tasks", "title")) {
7457
7504
  console.log("[title-id-drift] db.ts migration normalized 0 active titles");
7458
7505
  return;
@@ -7474,24 +7521,24 @@ This means a caller passed a .fusion directory where a project root was expected
7474
7521
  console.log(`[title-id-drift] db.ts migration normalized ${normalizedCount} active titles`);
7475
7522
  });
7476
7523
  }
7477
- if (version < 85) {
7478
- this.applyMigration(85, () => {
7479
- this.addColumnIfMissing("tasks", "completionHandoffLimboRecoveryCount", "INTEGER DEFAULT 0");
7480
- });
7481
- }
7482
7524
  if (version < 86) {
7483
7525
  this.applyMigration(86, () => {
7484
- this.addColumnIfMissing("tasks", "prInfos", "TEXT");
7526
+ this.addColumnIfMissing("tasks", "completionHandoffLimboRecoveryCount", "INTEGER DEFAULT 0");
7485
7527
  });
7486
7528
  }
7487
7529
  if (version < 87) {
7488
7530
  this.applyMigration(87, () => {
7489
- this.addColumnIfMissing("tasks", "deletedAt", "TEXT");
7490
- this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_deletedAt ON tasks(deletedAt)");
7531
+ this.addColumnIfMissing("tasks", "prInfos", "TEXT");
7491
7532
  });
7492
7533
  }
7493
7534
  if (version < 88) {
7494
7535
  this.applyMigration(88, () => {
7536
+ this.addColumnIfMissing("tasks", "deletedAt", "TEXT");
7537
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_deletedAt ON tasks(deletedAt)");
7538
+ });
7539
+ }
7540
+ if (version < 89) {
7541
+ this.applyMigration(89, () => {
7495
7542
  this.addColumnIfMissing("tasks", "allowResurrection", "INTEGER DEFAULT 0");
7496
7543
  try {
7497
7544
  const taskColumns = this.getTableColumns("tasks");
@@ -7516,8 +7563,8 @@ This means a caller passed a .fusion directory where a project root was expected
7516
7563
  }
7517
7564
  });
7518
7565
  }
7519
- if (version < 89) {
7520
- this.applyMigration(89, () => {
7566
+ if (version < 90) {
7567
+ this.applyMigration(90, () => {
7521
7568
  this.db.exec(`
7522
7569
  CREATE TABLE IF NOT EXISTS mergeQueue (
7523
7570
  taskId TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
@@ -7540,18 +7587,18 @@ This means a caller passed a .fusion directory where a project root was expected
7540
7587
  `);
7541
7588
  });
7542
7589
  }
7543
- if (version < 90) {
7544
- this.applyMigration(90, () => {
7545
- this.addColumnIfMissing("tasks", "scopeAutoWiden", "TEXT DEFAULT '[]'");
7546
- });
7547
- }
7548
7590
  if (version < 91) {
7549
7591
  this.applyMigration(91, () => {
7550
- this.addColumnIfMissing("missions", "baseBranch", "TEXT");
7592
+ this.addColumnIfMissing("tasks", "scopeAutoWiden", "TEXT DEFAULT '[]'");
7551
7593
  });
7552
7594
  }
7553
7595
  if (version < 92) {
7554
7596
  this.applyMigration(92, () => {
7597
+ this.addColumnIfMissing("missions", "baseBranch", "TEXT");
7598
+ });
7599
+ }
7600
+ if (version < 93) {
7601
+ this.applyMigration(93, () => {
7555
7602
  this.db.exec(`
7556
7603
  CREATE TABLE IF NOT EXISTS goals (
7557
7604
  id TEXT PRIMARY KEY,
@@ -7568,6 +7615,88 @@ This means a caller passed a .fusion directory where a project root was expected
7568
7615
  `);
7569
7616
  });
7570
7617
  }
7618
+ if (version < 94) {
7619
+ this.applyMigration(94, () => {
7620
+ this.db.exec(`
7621
+ CREATE TABLE IF NOT EXISTS goal_citations (
7622
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7623
+ goalId TEXT NOT NULL,
7624
+ agentId TEXT NOT NULL,
7625
+ taskId TEXT,
7626
+ surface TEXT NOT NULL,
7627
+ sourceRef TEXT NOT NULL,
7628
+ snippet TEXT NOT NULL,
7629
+ timestamp TEXT NOT NULL
7630
+ )
7631
+ `);
7632
+ this.db.exec(`
7633
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsGoalId
7634
+ ON goal_citations(goalId)
7635
+ `);
7636
+ this.db.exec(`
7637
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsAgentId
7638
+ ON goal_citations(agentId)
7639
+ `);
7640
+ this.db.exec(`
7641
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsTimestamp
7642
+ ON goal_citations(timestamp)
7643
+ `);
7644
+ this.db.exec(`
7645
+ CREATE UNIQUE INDEX IF NOT EXISTS uxGoalCitationsDedup
7646
+ ON goal_citations(goalId, surface, sourceRef)
7647
+ `);
7648
+ });
7649
+ }
7650
+ if (version < 96) {
7651
+ this.applyMigration(96, () => {
7652
+ this.db.exec(`
7653
+ CREATE TABLE IF NOT EXISTS branch_groups (
7654
+ id TEXT PRIMARY KEY,
7655
+ sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
7656
+ sourceId TEXT NOT NULL,
7657
+ branchName TEXT NOT NULL UNIQUE,
7658
+ worktreePath TEXT,
7659
+ autoMerge INTEGER NOT NULL DEFAULT 0,
7660
+ prState TEXT NOT NULL DEFAULT 'none' CHECK (prState IN ('none','open','merged','closed')),
7661
+ prUrl TEXT,
7662
+ prNumber INTEGER,
7663
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','finalized','abandoned')),
7664
+ createdAt INTEGER NOT NULL,
7665
+ updatedAt INTEGER NOT NULL,
7666
+ closedAt INTEGER
7667
+ )
7668
+ `);
7669
+ this.db.exec(`
7670
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsSource
7671
+ ON branch_groups(sourceType, sourceId)
7672
+ `);
7673
+ this.db.exec(`
7674
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsBranchName
7675
+ ON branch_groups(branchName)
7676
+ `);
7677
+ this.addColumnIfMissing("tasks", "autoMerge", "INTEGER");
7678
+ this.addColumnIfMissing("missions", "autoMerge", "INTEGER");
7679
+ });
7680
+ }
7681
+ if (version < 97) {
7682
+ this.applyMigration(97, () => {
7683
+ if (this.hasTable("mission_contract_assertions")) {
7684
+ this.addColumnIfMissing("mission_contract_assertions", "sourceFeatureId", "TEXT");
7685
+ }
7686
+ });
7687
+ }
7688
+ if (version < 98) {
7689
+ this.applyMigration(98, () => {
7690
+ this.addColumnIfMissing("missions", "branchStrategy", "TEXT");
7691
+ });
7692
+ }
7693
+ if (version < 99) {
7694
+ this.applyMigration(99, () => {
7695
+ this.addColumnIfMissing("tasks", "resumeLimboCount", "INTEGER DEFAULT 0");
7696
+ this.addColumnIfMissing("tasks", "resumeLimboTipSha", "TEXT");
7697
+ this.addColumnIfMissing("tasks", "resumeLimboStepSignature", "TEXT");
7698
+ });
7699
+ }
7571
7700
  }
7572
7701
  /**
7573
7702
  * Run a single migration step inside a transaction and bump the version.
@@ -12304,6 +12433,53 @@ var init_db_migrate = __esm({
12304
12433
  }
12305
12434
  });
12306
12435
 
12436
+ // ../core/src/goal-citation-extractor.ts
12437
+ function extractGoalCitations(text) {
12438
+ const normalized = String(text ?? "");
12439
+ if (normalized.length === 0) {
12440
+ return [];
12441
+ }
12442
+ const matches = [];
12443
+ const seen = /* @__PURE__ */ new Set();
12444
+ const pattern = new RegExp(GOAL_ID_PATTERN.source, GOAL_ID_PATTERN.flags);
12445
+ let match;
12446
+ while ((match = pattern.exec(normalized)) !== null) {
12447
+ const goalId = match[0];
12448
+ if (seen.has(goalId)) {
12449
+ continue;
12450
+ }
12451
+ seen.add(goalId);
12452
+ matches.push({ goalId, index: match.index });
12453
+ }
12454
+ return matches;
12455
+ }
12456
+ function buildSnippet(text, index2, max = GOAL_CITATION_SNIPPET_MAX) {
12457
+ const normalized = String(text ?? "");
12458
+ if (normalized.length === 0 || max <= 0) {
12459
+ return "";
12460
+ }
12461
+ const goalMatch = normalized.slice(Math.max(0, index2)).match(/^G-[0-9A-Z]+(?:-[0-9A-Z]+)*/);
12462
+ const goalEnd = goalMatch ? index2 + goalMatch[0].length : index2;
12463
+ let start = Math.max(0, index2 - Math.floor(max / 2));
12464
+ let end = Math.min(normalized.length, start + max);
12465
+ if (end < goalEnd) {
12466
+ end = Math.min(normalized.length, goalEnd);
12467
+ start = Math.max(0, end - max);
12468
+ } else {
12469
+ start = Math.max(0, end - max);
12470
+ }
12471
+ const snippet = normalized.slice(start, end).replace(/\s+/g, " ").trim();
12472
+ return snippet.length <= max ? snippet : snippet.slice(0, max).trim();
12473
+ }
12474
+ var GOAL_ID_PATTERN, GOAL_CITATION_SNIPPET_MAX;
12475
+ var init_goal_citation_extractor = __esm({
12476
+ "../core/src/goal-citation-extractor.ts"() {
12477
+ "use strict";
12478
+ GOAL_ID_PATTERN = /\bG-[0-9A-Z]+(?:-[0-9A-Z]+)*\b/g;
12479
+ GOAL_CITATION_SNIPPET_MAX = 200;
12480
+ }
12481
+ });
12482
+
12307
12483
  // ../core/src/duplicate-detection.ts
12308
12484
  import { createHash as createHash4 } from "node:crypto";
12309
12485
  function normalizeFingerprintPart(value) {
@@ -12560,6 +12736,18 @@ var init_duplicate_guard = __esm({
12560
12736
 
12561
12737
  // ../core/src/mission-store.ts
12562
12738
  import { EventEmitter as EventEmitter4 } from "node:events";
12739
+ function missionBranchStrategyDefaults(strategy) {
12740
+ if (!strategy) {
12741
+ return { assignmentMode: "shared" };
12742
+ }
12743
+ if (strategy.mode === "auto-per-task") {
12744
+ return { assignmentMode: "per-task-derived" };
12745
+ }
12746
+ if ((strategy.mode === "existing" || strategy.mode === "custom-new") && strategy.branchName?.trim()) {
12747
+ return { branch: strategy.branchName.trim(), assignmentMode: "shared" };
12748
+ }
12749
+ return { assignmentMode: "shared" };
12750
+ }
12563
12751
  function deriveMilestoneAcceptanceCriteriaFromFeatures(features) {
12564
12752
  const lines = features.map((feature) => {
12565
12753
  const acceptance = feature.acceptanceCriteria?.trim();
@@ -12608,6 +12796,14 @@ var init_mission_store = __esm({
12608
12796
  * Convert a database row to a Mission object.
12609
12797
  */
12610
12798
  rowToMission(row) {
12799
+ let branchStrategy;
12800
+ if (row.branchStrategy) {
12801
+ try {
12802
+ branchStrategy = JSON.parse(row.branchStrategy);
12803
+ } catch {
12804
+ branchStrategy = void 0;
12805
+ }
12806
+ }
12611
12807
  return {
12612
12808
  id: row.id,
12613
12809
  title: row.title,
@@ -12615,6 +12811,8 @@ var init_mission_store = __esm({
12615
12811
  status: row.status,
12616
12812
  interviewState: row.interviewState,
12617
12813
  baseBranch: row.baseBranch || void 0,
12814
+ branchStrategy,
12815
+ autoMerge: row.autoMerge === null ? void 0 : Boolean(row.autoMerge),
12618
12816
  autoAdvance: Boolean(row.autoAdvance),
12619
12817
  autopilotEnabled: Boolean(row.autopilotEnabled),
12620
12818
  autopilotState: row.autopilotState || "inactive",
@@ -12651,6 +12849,7 @@ var init_mission_store = __esm({
12651
12849
  return {
12652
12850
  id: row.id,
12653
12851
  milestoneId: row.milestoneId,
12852
+ sourceFeatureId: row.sourceFeatureId || void 0,
12654
12853
  title: row.title,
12655
12854
  assertion: row.assertion,
12656
12855
  status: row.status,
@@ -12778,7 +12977,8 @@ var init_mission_store = __esm({
12778
12977
  // ── Mission CRUD Operations ────────────────────────────────────────
12779
12978
  /**
12780
12979
  * Create a new mission.
12781
- * The mission starts in "planning" status with "not_started" interview state.
12980
+ * Missions are always created stopped (status `planning`, autopilot disabled).
12981
+ * Autopilot is only enabled via an explicit start/update after creation.
12782
12982
  *
12783
12983
  * @param input - Mission creation input
12784
12984
  * @returns The created mission
@@ -12793,15 +12993,17 @@ var init_mission_store = __esm({
12793
12993
  status: "planning",
12794
12994
  interviewState: "not_started",
12795
12995
  baseBranch: input.baseBranch,
12996
+ branchStrategy: input.branchStrategy,
12997
+ autoMerge: input.autoMerge,
12796
12998
  autoAdvance: false,
12797
- autopilotEnabled: input.autopilotEnabled ?? false,
12999
+ autopilotEnabled: false,
12798
13000
  autopilotState: "inactive",
12799
13001
  createdAt: now,
12800
13002
  updatedAt: now
12801
13003
  };
12802
13004
  this.db.prepare(`
12803
- INSERT INTO missions (id, title, description, status, interviewState, baseBranch, autoAdvance, autopilotEnabled, autopilotState, createdAt, updatedAt)
12804
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
13005
+ INSERT INTO missions (id, title, description, status, interviewState, baseBranch, branchStrategy, autoMerge, autoAdvance, autopilotEnabled, autopilotState, createdAt, updatedAt)
13006
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
12805
13007
  `).run(
12806
13008
  mission.id,
12807
13009
  mission.title,
@@ -12809,6 +13011,8 @@ var init_mission_store = __esm({
12809
13011
  mission.status,
12810
13012
  mission.interviewState,
12811
13013
  mission.baseBranch ?? null,
13014
+ mission.branchStrategy ? JSON.stringify(mission.branchStrategy) : null,
13015
+ mission.autoMerge === void 0 ? null : mission.autoMerge ? 1 : 0,
12812
13016
  mission.autoAdvance ? 1 : 0,
12813
13017
  mission.autopilotEnabled ? 1 : 0,
12814
13018
  mission.autopilotState ?? "inactive",
@@ -13275,6 +13479,8 @@ var init_mission_store = __esm({
13275
13479
  status = ?,
13276
13480
  interviewState = ?,
13277
13481
  baseBranch = ?,
13482
+ branchStrategy = ?,
13483
+ autoMerge = ?,
13278
13484
  autoAdvance = ?,
13279
13485
  autopilotEnabled = ?,
13280
13486
  autopilotState = ?,
@@ -13287,6 +13493,8 @@ var init_mission_store = __esm({
13287
13493
  updated.status,
13288
13494
  updated.interviewState,
13289
13495
  updated.baseBranch ?? null,
13496
+ updated.branchStrategy ? JSON.stringify(updated.branchStrategy) : null,
13497
+ updated.autoMerge === void 0 ? null : updated.autoMerge ? 1 : 0,
13290
13498
  updated.autoAdvance ? 1 : 0,
13291
13499
  updated.autopilotEnabled ? 1 : 0,
13292
13500
  updated.autopilotState ?? "inactive",
@@ -13802,7 +14010,8 @@ var init_mission_store = __esm({
13802
14010
  this.emit("feature:created", feature);
13803
14011
  this.recomputeSliceStatus(sliceId);
13804
14012
  this.applyDerivedMilestoneAcceptanceCriteria(slice.milestoneId);
13805
- return feature;
14013
+ this.ensureFeatureAssertion(feature);
14014
+ return this.getFeature(feature.id) ?? feature;
13806
14015
  }
13807
14016
  /**
13808
14017
  * Get a feature by ID.
@@ -13887,6 +14096,11 @@ var init_mission_store = __esm({
13887
14096
  if (taskIdChanged || statusChanged) {
13888
14097
  this.recomputeSliceStatus(updated.sliceId);
13889
14098
  }
14099
+ const shouldSyncAssertion = updates.title !== void 0 || updates.description !== void 0 || updates.acceptanceCriteria !== void 0;
14100
+ if (shouldSyncAssertion) {
14101
+ this.ensureFeatureAssertion(updated);
14102
+ return this.getFeature(updated.id) ?? updated;
14103
+ }
13890
14104
  return updated;
13891
14105
  }
13892
14106
  /**
@@ -13901,11 +14115,115 @@ var init_mission_store = __esm({
13901
14115
  throw new Error(`Feature ${id} not found`);
13902
14116
  }
13903
14117
  const sliceId = feature.sliceId;
14118
+ const slice = this.getSlice(sliceId);
14119
+ const milestoneId = slice?.milestoneId;
14120
+ if (milestoneId) {
14121
+ const managedAssertion = this.listContractAssertions(milestoneId).find((assertion) => assertion.sourceFeatureId === feature.id);
14122
+ if (managedAssertion) {
14123
+ this.deleteContractAssertion(managedAssertion.id);
14124
+ }
14125
+ }
13904
14126
  this.db.prepare("DELETE FROM mission_features WHERE id = ?").run(id);
13905
14127
  this.db.bumpLastModified();
13906
14128
  this.emit("feature:deleted", id);
13907
14129
  this.recomputeSliceStatus(sliceId);
13908
14130
  }
14131
+ deriveFeatureAssertion(feature) {
14132
+ const acceptanceCriteria = feature.acceptanceCriteria?.trim();
14133
+ if (acceptanceCriteria) {
14134
+ return { assertionText: acceptanceCriteria, textSource: "acceptanceCriteria" };
14135
+ }
14136
+ const description = feature.description?.trim();
14137
+ if (description) {
14138
+ return { assertionText: description, textSource: "description" };
14139
+ }
14140
+ return {
14141
+ assertionText: `Verify implementation of: ${feature.title}`,
14142
+ textSource: "fallback"
14143
+ };
14144
+ }
14145
+ ensureFeatureAssertion(feature) {
14146
+ const slice = this.getSlice(feature.sliceId);
14147
+ if (!slice) {
14148
+ throw new Error(`Slice ${feature.sliceId} not found`);
14149
+ }
14150
+ const milestoneId = slice.milestoneId;
14151
+ const { assertionText } = this.deriveFeatureAssertion(feature);
14152
+ const existing = this.listContractAssertions(milestoneId).find((assertion) => assertion.sourceFeatureId === feature.id);
14153
+ if (!existing) {
14154
+ const created = this.addContractAssertion(milestoneId, {
14155
+ title: feature.title,
14156
+ assertion: assertionText,
14157
+ status: "pending",
14158
+ sourceFeatureId: feature.id
14159
+ });
14160
+ this.linkFeatureToAssertion(feature.id, created.id);
14161
+ return;
14162
+ }
14163
+ if (existing.title !== feature.title || existing.assertion !== assertionText) {
14164
+ this.updateContractAssertion(existing.id, {
14165
+ title: feature.title,
14166
+ assertion: assertionText
14167
+ });
14168
+ }
14169
+ }
14170
+ /**
14171
+ * Backfill assertion links for legacy features that predate the FN-5695 creation-path fix.
14172
+ * Reuses deriveFeatureAssertion()/ensureFeatureAssertion text-source rules so create/update
14173
+ * and repair flows stay aligned on canonical assertion content.
14174
+ */
14175
+ backfillFeatureAssertions(options) {
14176
+ const dryRun = options?.dryRun ?? true;
14177
+ const missionFilter = options?.missionId;
14178
+ const features = missionFilter ? this.listMilestones(missionFilter).flatMap((milestone) => this.listSlices(milestone.id)).flatMap((slice) => this.listFeatures(slice.id)) : this.listMissions().flatMap((mission) => this.listMilestones(mission.id)).flatMap((milestone) => this.listSlices(milestone.id)).flatMap((slice) => this.listFeatures(slice.id));
14179
+ const report = {
14180
+ scanned: features.length,
14181
+ alreadyLinked: 0,
14182
+ repaired: [],
14183
+ skippedErrors: []
14184
+ };
14185
+ for (const feature of features) {
14186
+ try {
14187
+ const linkedAssertions = this.listAssertionsForFeature(feature.id);
14188
+ if (linkedAssertions.length > 0) {
14189
+ report.alreadyLinked += 1;
14190
+ continue;
14191
+ }
14192
+ const slice = this.getSlice(feature.sliceId);
14193
+ if (!slice) {
14194
+ throw new Error(`Slice ${feature.sliceId} not found`);
14195
+ }
14196
+ const milestoneId = slice.milestoneId;
14197
+ const { assertionText, textSource } = this.deriveFeatureAssertion(feature);
14198
+ if (dryRun) {
14199
+ report.repaired.push({
14200
+ featureId: feature.id,
14201
+ milestoneId,
14202
+ assertionId: "(dry-run)",
14203
+ textSource
14204
+ });
14205
+ continue;
14206
+ }
14207
+ const created = this.addContractAssertion(milestoneId, {
14208
+ title: feature.title,
14209
+ assertion: assertionText,
14210
+ status: "pending",
14211
+ sourceFeatureId: feature.id
14212
+ });
14213
+ this.linkFeatureToAssertion(feature.id, created.id);
14214
+ report.repaired.push({
14215
+ featureId: feature.id,
14216
+ milestoneId,
14217
+ assertionId: created.id,
14218
+ textSource
14219
+ });
14220
+ } catch (error) {
14221
+ const message = error instanceof Error ? error.message : String(error);
14222
+ report.skippedErrors.push({ featureId: feature.id, message });
14223
+ }
14224
+ }
14225
+ return report;
14226
+ }
13909
14227
  /**
13910
14228
  * Resolve the mission hierarchy for a slice.
13911
14229
  *
@@ -14498,6 +14816,7 @@ var init_mission_store = __esm({
14498
14816
  const assertion = {
14499
14817
  id,
14500
14818
  milestoneId,
14819
+ sourceFeatureId: input.sourceFeatureId,
14501
14820
  title: input.title,
14502
14821
  assertion: input.assertion,
14503
14822
  status: input.status || "pending",
@@ -14506,8 +14825,8 @@ var init_mission_store = __esm({
14506
14825
  updatedAt: now
14507
14826
  };
14508
14827
  this.db.prepare(`
14509
- INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, createdAt, updatedAt)
14510
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
14828
+ INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, sourceFeatureId, createdAt, updatedAt)
14829
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
14511
14830
  `).run(
14512
14831
  assertion.id,
14513
14832
  assertion.milestoneId,
@@ -14515,6 +14834,7 @@ var init_mission_store = __esm({
14515
14834
  assertion.assertion,
14516
14835
  assertion.status,
14517
14836
  assertion.orderIndex,
14837
+ assertion.sourceFeatureId ?? null,
14518
14838
  assertion.createdAt,
14519
14839
  assertion.updatedAt
14520
14840
  );
@@ -14956,7 +15276,10 @@ ${feature.acceptanceCriteria}`);
14956
15276
  const milestone = slice ? this.getMilestone(slice.milestoneId) : void 0;
14957
15277
  const missionId = milestone?.missionId;
14958
15278
  const mission = missionId ? this.getMission(missionId) : void 0;
15279
+ const strategyDefaults = missionBranchStrategyDefaults(mission?.branchStrategy);
14959
15280
  const resolvedBaseBranch = branchOptions?.baseBranch ?? mission?.baseBranch;
15281
+ const resolvedBranch = branchOptions?.branch ?? strategyDefaults.branch;
15282
+ const resolvedAssignmentMode = branchOptions?.assignmentMode ?? strategyDefaults.assignmentMode;
14960
15283
  const lockScope = missionId ? `mission:${missionId}` : `mission-store:${this.taskStore.getRootDir()}`;
14961
15284
  const guard = await runDeterministicDuplicateGuard(this.taskStore, {
14962
15285
  title: taskTitle || feature.title,
@@ -14970,13 +15293,13 @@ ${feature.acceptanceCriteria}`);
14970
15293
  const createdTask = await this.taskStore.createTask({
14971
15294
  title: taskTitle || feature.title,
14972
15295
  description,
14973
- branch: branchOptions?.branch,
15296
+ branch: resolvedBranch,
14974
15297
  baseBranch: resolvedBaseBranch,
14975
15298
  ...missionId ? {
14976
15299
  branchContext: {
14977
15300
  groupId: `mission:${missionId}`,
14978
15301
  source: "mission",
14979
- assignmentMode: branchOptions?.assignmentMode ?? "shared",
15302
+ assignmentMode: resolvedAssignmentMode,
14980
15303
  inheritedBaseBranch: resolvedBaseBranch
14981
15304
  }
14982
15305
  } : {}
@@ -15021,14 +15344,18 @@ ${feature.acceptanceCriteria}`);
15021
15344
  const definedFeatures = features.filter((f2) => f2.status === "defined");
15022
15345
  const milestone = this.getMilestone(slice.milestoneId);
15023
15346
  const mission = milestone ? this.getMission(milestone.missionId) : void 0;
15347
+ const strategyDefaults = missionBranchStrategyDefaults(mission?.branchStrategy);
15024
15348
  const resolvedBaseBranch = branchOptions?.baseBranch ?? mission?.baseBranch;
15349
+ const resolvedAssignmentMode = branchOptions?.assignmentMode ?? strategyDefaults.assignmentMode;
15350
+ const resolvedBranch = branchOptions?.branch ?? strategyDefaults.branch;
15025
15351
  const triaged = [];
15026
15352
  for (const feature of definedFeatures) {
15027
- const branch = branchOptions?.assignmentMode === "per-task-derived" ? branchOptions?.branch ? `${branchOptions.branch}/${feature.id.toLowerCase()}` : void 0 : branchOptions?.branch;
15353
+ const strategyBranch = resolvedAssignmentMode === "per-task-derived" ? resolvedBranch ? `${resolvedBranch}/${feature.id.toLowerCase()}` : void 0 : resolvedBranch;
15028
15354
  const updated = await this.triageFeature(feature.id, void 0, void 0, {
15029
- ...branchOptions,
15030
- branch,
15031
- baseBranch: resolvedBaseBranch
15355
+ branch: strategyBranch,
15356
+ baseBranch: resolvedBaseBranch,
15357
+ assignmentMode: resolvedAssignmentMode,
15358
+ ...branchOptions
15032
15359
  });
15033
15360
  triaged.push(updated);
15034
15361
  }
@@ -15180,17 +15507,20 @@ ${feature.acceptanceCriteria}`);
15180
15507
  validateSnapshotEnvelope(snapshot);
15181
15508
  let applied = 0;
15182
15509
  for (const mission of snapshot.payload.missions) {
15183
- this.db.prepare(`INSERT INTO missions (id, title, description, status, interviewState, autoAdvance, autopilotEnabled, autopilotState, lastAutopilotActivityAt, createdAt, updatedAt)
15184
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15510
+ this.db.prepare(`INSERT INTO missions (id, title, description, status, interviewState, baseBranch, branchStrategy, autoMerge, autoAdvance, autopilotEnabled, autopilotState, lastAutopilotActivityAt, createdAt, updatedAt)
15511
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15185
15512
  ON CONFLICT(id) DO UPDATE SET
15186
15513
  title=excluded.title, description=excluded.description, status=excluded.status, interviewState=excluded.interviewState,
15187
- autoAdvance=excluded.autoAdvance, autopilotEnabled=excluded.autopilotEnabled, autopilotState=excluded.autopilotState,
15514
+ baseBranch=excluded.baseBranch, branchStrategy=excluded.branchStrategy, autoMerge=excluded.autoMerge, autoAdvance=excluded.autoAdvance, autopilotEnabled=excluded.autopilotEnabled, autopilotState=excluded.autopilotState,
15188
15515
  lastAutopilotActivityAt=excluded.lastAutopilotActivityAt, updatedAt=excluded.updatedAt`).run(
15189
15516
  mission.id,
15190
15517
  mission.title,
15191
15518
  mission.description ?? null,
15192
15519
  mission.status,
15193
15520
  mission.interviewState,
15521
+ mission.baseBranch ?? null,
15522
+ mission.branchStrategy ? JSON.stringify(mission.branchStrategy) : null,
15523
+ mission.autoMerge === void 0 ? null : mission.autoMerge ? 1 : 0,
15194
15524
  mission.autoAdvance ? 1 : 0,
15195
15525
  mission.autopilotEnabled ? 1 : 0,
15196
15526
  mission.autopilotState,
@@ -15276,9 +15606,9 @@ ${feature.acceptanceCriteria}`);
15276
15606
  }
15277
15607
  for (const assertion of snapshot.payload.assertions) {
15278
15608
  if (!assertion.id || !assertion.milestoneId) continue;
15279
- this.db.prepare(`INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, createdAt, updatedAt)
15280
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
15281
- ON CONFLICT(id) DO UPDATE SET title=excluded.title, assertion=excluded.assertion, status=excluded.status, orderIndex=excluded.orderIndex, updatedAt=excluded.updatedAt`).run(assertion.id, assertion.milestoneId, assertion.title, assertion.assertion, assertion.status, assertion.orderIndex, assertion.createdAt, assertion.updatedAt);
15609
+ this.db.prepare(`INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, sourceFeatureId, createdAt, updatedAt)
15610
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
15611
+ ON CONFLICT(id) DO UPDATE SET title=excluded.title, assertion=excluded.assertion, status=excluded.status, orderIndex=excluded.orderIndex, sourceFeatureId=excluded.sourceFeatureId, updatedAt=excluded.updatedAt`).run(assertion.id, assertion.milestoneId, assertion.title, assertion.assertion, assertion.status, assertion.orderIndex, assertion.sourceFeatureId ?? null, assertion.createdAt, assertion.updatedAt);
15282
15612
  }
15283
15613
  for (const link of snapshot.payload.featureAssertionLinks) {
15284
15614
  if (!link.featureId || !link.assertionId) continue;
@@ -27239,6 +27569,9 @@ var init_secrets_sync_passphrase = __esm({
27239
27569
  function isFusionSiblingBranch(branch) {
27240
27570
  return FUSION_SIBLING_BRANCH_RE.test(branch);
27241
27571
  }
27572
+ function resolveEffectiveAutoMerge(task, settings) {
27573
+ return task.autoMerge ?? settings.autoMerge;
27574
+ }
27242
27575
  function resolveTaskMergeTarget(task, options = {}) {
27243
27576
  let rejected;
27244
27577
  const configuredBase = task.baseBranch?.trim();
@@ -40161,6 +40494,7 @@ var init_store = __esm({
40161
40494
  init_db();
40162
40495
  init_archive_db();
40163
40496
  init_db_migrate();
40497
+ init_goal_citation_extractor();
40164
40498
  init_mission_store();
40165
40499
  init_plugin_store();
40166
40500
  init_insight_store();
@@ -40690,6 +41024,7 @@ var init_store = __esm({
40690
41024
  baseBranch: row.baseBranch || void 0,
40691
41025
  executionStartBranch: row.executionStartBranch || void 0,
40692
41026
  branch: row.branch || void 0,
41027
+ autoMerge: row.autoMerge === null ? void 0 : row.autoMerge === 1,
40693
41028
  baseCommitSha: row.baseCommitSha || void 0,
40694
41029
  scopeOverride: row.scopeOverride ? true : void 0,
40695
41030
  scopeOverrideReason: row.scopeOverrideReason || void 0,
@@ -40704,6 +41039,9 @@ var init_store = __esm({
40704
41039
  mergeRetries: row.mergeRetries ?? void 0,
40705
41040
  workflowStepRetries: row.workflowStepRetries ?? void 0,
40706
41041
  stuckKillCount: row.stuckKillCount ?? void 0,
41042
+ resumeLimboCount: row.resumeLimboCount ?? void 0,
41043
+ resumeLimboTipSha: row.resumeLimboTipSha || void 0,
41044
+ resumeLimboStepSignature: row.resumeLimboStepSignature || void 0,
40707
41045
  postReviewFixCount: row.postReviewFixCount ?? void 0,
40708
41046
  recoveryRetryCount: row.recoveryRetryCount ?? void 0,
40709
41047
  taskDoneRetryCount: row.taskDoneRetryCount ?? void 0,
@@ -40712,6 +41050,7 @@ var init_store = __esm({
40712
41050
  verificationFailureCount: row.verificationFailureCount ?? void 0,
40713
41051
  mergeConflictBounceCount: row.mergeConflictBounceCount ?? void 0,
40714
41052
  mergeAuditBounceCount: row.mergeAuditBounceCount ?? void 0,
41053
+ mergeTransientRetryCount: row.mergeTransientRetryCount ?? void 0,
40715
41054
  branchConflictRecoveryCount: row.branchConflictRecoveryCount ?? void 0,
40716
41055
  reviewerContextRetryCount: row.reviewerContextRetryCount ?? void 0,
40717
41056
  reviewerFallbackRetryCount: row.reviewerFallbackRetryCount ?? void 0,
@@ -40835,6 +41174,28 @@ var init_store = __esm({
40835
41174
  allowResurrection: row.allowResurrection ? true : void 0
40836
41175
  };
40837
41176
  }
41177
+ rowToBranchGroup(row) {
41178
+ return {
41179
+ id: row.id,
41180
+ sourceType: row.sourceType,
41181
+ sourceId: row.sourceId,
41182
+ branchName: row.branchName,
41183
+ worktreePath: row.worktreePath ?? void 0,
41184
+ autoMerge: Boolean(row.autoMerge),
41185
+ prState: row.prState,
41186
+ prUrl: row.prUrl ?? void 0,
41187
+ prNumber: row.prNumber ?? void 0,
41188
+ status: row.status,
41189
+ createdAt: row.createdAt,
41190
+ updatedAt: row.updatedAt,
41191
+ closedAt: row.closedAt ?? void 0
41192
+ };
41193
+ }
41194
+ generateBranchGroupId() {
41195
+ const timestamp = Date.now().toString(36).toUpperCase();
41196
+ const random = Math.random().toString(36).slice(2, 8).toUpperCase();
41197
+ return `BG-${timestamp}-${random}`;
41198
+ }
40838
41199
  archiveEntryToTask(entry, slim = false) {
40839
41200
  return {
40840
41201
  id: entry.id,
@@ -40874,6 +41235,8 @@ var init_store = __esm({
40874
41235
  planningModelId: entry.planningModelId,
40875
41236
  breakIntoSubtasks: entry.breakIntoSubtasks,
40876
41237
  noCommitsExpected: entry.noCommitsExpected,
41238
+ branchContext: entry.branchContext,
41239
+ autoMerge: entry.autoMerge,
40877
41240
  modifiedFiles: slim ? void 0 : entry.modifiedFiles,
40878
41241
  missionId: entry.missionId,
40879
41242
  sliceId: entry.sliceId,
@@ -40987,6 +41350,8 @@ ${recentText}` : void 0
40987
41350
  noCommitsExpected: task.noCommitsExpected,
40988
41351
  baseBranch: task.baseBranch,
40989
41352
  branch: task.branch,
41353
+ branchContext: task.branchContext,
41354
+ autoMerge: task.autoMerge,
40990
41355
  baseCommitSha: task.baseCommitSha,
40991
41356
  mergeRetries: task.mergeRetries,
40992
41357
  error: task.error,
@@ -41027,6 +41392,99 @@ ${recentText}` : void 0
41027
41392
  createdAt: row.createdAt
41028
41393
  };
41029
41394
  }
41395
+ rowToGoalCitation(row) {
41396
+ return {
41397
+ id: row.id,
41398
+ goalId: row.goalId,
41399
+ agentId: row.agentId,
41400
+ ...row.taskId ? { taskId: row.taskId } : {},
41401
+ surface: row.surface,
41402
+ sourceRef: row.sourceRef,
41403
+ snippet: row.snippet,
41404
+ timestamp: row.timestamp
41405
+ };
41406
+ }
41407
+ recordGoalCitations(inputs) {
41408
+ if (inputs.length === 0) {
41409
+ return [];
41410
+ }
41411
+ const now = (/* @__PURE__ */ new Date()).toISOString();
41412
+ const stmt = this.db.prepare(`
41413
+ INSERT OR IGNORE INTO goal_citations (goalId, agentId, taskId, surface, sourceRef, snippet, timestamp)
41414
+ VALUES (?, ?, ?, ?, ?, ?, ?)
41415
+ RETURNING *
41416
+ `);
41417
+ const inserted = [];
41418
+ this.db.transaction(() => {
41419
+ for (const input of inputs) {
41420
+ const row = stmt.get(
41421
+ input.goalId,
41422
+ input.agentId,
41423
+ input.taskId ?? null,
41424
+ input.surface,
41425
+ input.sourceRef,
41426
+ input.snippet,
41427
+ input.timestamp ?? now
41428
+ );
41429
+ if (row) {
41430
+ inserted.push(this.rowToGoalCitation(row));
41431
+ }
41432
+ }
41433
+ if (inserted.length > 0) {
41434
+ this.db.bumpLastModified();
41435
+ }
41436
+ });
41437
+ return inserted;
41438
+ }
41439
+ listGoalCitations(filter2 = {}) {
41440
+ const clauses = [];
41441
+ const params = [];
41442
+ if (filter2.goalId) {
41443
+ clauses.push("goalId = ?");
41444
+ params.push(filter2.goalId);
41445
+ }
41446
+ if (filter2.agentId) {
41447
+ clauses.push("agentId = ?");
41448
+ params.push(filter2.agentId);
41449
+ }
41450
+ if (filter2.taskId) {
41451
+ clauses.push("taskId = ?");
41452
+ params.push(filter2.taskId);
41453
+ }
41454
+ if (filter2.surface) {
41455
+ clauses.push("surface = ?");
41456
+ params.push(filter2.surface);
41457
+ }
41458
+ if (filter2.startTime) {
41459
+ clauses.push("timestamp >= ?");
41460
+ params.push(filter2.startTime);
41461
+ }
41462
+ if (filter2.endTime) {
41463
+ clauses.push("timestamp <= ?");
41464
+ params.push(filter2.endTime);
41465
+ }
41466
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
41467
+ const limit = Math.max(1, Math.min(filter2.limit ?? 200, 1e3));
41468
+ const rows = this.db.prepare(
41469
+ `SELECT * FROM goal_citations ${where} ORDER BY timestamp DESC, id DESC LIMIT ?`
41470
+ ).all(...params, limit);
41471
+ return rows.map((row) => this.rowToGoalCitation(row));
41472
+ }
41473
+ scanAndRecordCitations(text, surface, sourceRef, agentId, taskId, timestamp) {
41474
+ const matches = extractGoalCitations(text);
41475
+ if (matches.length === 0) {
41476
+ return [];
41477
+ }
41478
+ return matches.map((match) => ({
41479
+ goalId: match.goalId,
41480
+ agentId,
41481
+ ...taskId ? { taskId } : {},
41482
+ surface,
41483
+ sourceRef,
41484
+ snippet: buildSnippet(text, match.index),
41485
+ ...timestamp ? { timestamp } : {}
41486
+ }));
41487
+ }
41030
41488
  getTaskSelectClause(slim, tableAlias) {
41031
41489
  if (!slim) {
41032
41490
  return tableAlias ? `${tableAlias}.*` : "*";
@@ -41051,6 +41509,7 @@ ${recentText}` : void 0
41051
41509
  "userPaused",
41052
41510
  "baseBranch",
41053
41511
  "branch",
41512
+ "autoMerge",
41054
41513
  "executionStartBranch",
41055
41514
  "baseCommitSha",
41056
41515
  "modelPresetId",
@@ -41063,6 +41522,9 @@ ${recentText}` : void 0
41063
41522
  "mergeRetries",
41064
41523
  "workflowStepRetries",
41065
41524
  "stuckKillCount",
41525
+ "resumeLimboCount",
41526
+ "resumeLimboTipSha",
41527
+ "resumeLimboStepSignature",
41066
41528
  "postReviewFixCount",
41067
41529
  "recoveryRetryCount",
41068
41530
  "taskDoneRetryCount",
@@ -41071,6 +41533,7 @@ ${recentText}` : void 0
41071
41533
  "verificationFailureCount",
41072
41534
  "mergeConflictBounceCount",
41073
41535
  "mergeAuditBounceCount",
41536
+ "mergeTransientRetryCount",
41074
41537
  "branchConflictRecoveryCount",
41075
41538
  "reviewerContextRetryCount",
41076
41539
  "reviewerFallbackRetryCount",
@@ -41195,6 +41658,7 @@ ${outcome}`;
41195
41658
  "userPaused",
41196
41659
  "baseBranch",
41197
41660
  "branch",
41661
+ "autoMerge",
41198
41662
  "executionStartBranch",
41199
41663
  "baseCommitSha",
41200
41664
  "modelPresetId",
@@ -41207,6 +41671,9 @@ ${outcome}`;
41207
41671
  "mergeRetries",
41208
41672
  "workflowStepRetries",
41209
41673
  "stuckKillCount",
41674
+ "resumeLimboCount",
41675
+ "resumeLimboTipSha",
41676
+ "resumeLimboStepSignature",
41210
41677
  "postReviewFixCount",
41211
41678
  "recoveryRetryCount",
41212
41679
  "taskDoneRetryCount",
@@ -41215,6 +41682,7 @@ ${outcome}`;
41215
41682
  "verificationFailureCount",
41216
41683
  "mergeConflictBounceCount",
41217
41684
  "mergeAuditBounceCount",
41685
+ "mergeTransientRetryCount",
41218
41686
  "branchConflictRecoveryCount",
41219
41687
  "reviewerContextRetryCount",
41220
41688
  "reviewerFallbackRetryCount",
@@ -41328,6 +41796,7 @@ ${outcome}`;
41328
41796
  task.userPaused ? 1 : 0,
41329
41797
  task.baseBranch ?? null,
41330
41798
  task.branch ?? null,
41799
+ task.autoMerge === void 0 ? null : task.autoMerge ? 1 : 0,
41331
41800
  task.executionStartBranch ?? null,
41332
41801
  task.baseCommitSha ?? null,
41333
41802
  task.modelPresetId ?? null,
@@ -41340,6 +41809,9 @@ ${outcome}`;
41340
41809
  task.mergeRetries ?? null,
41341
41810
  task.workflowStepRetries ?? null,
41342
41811
  task.stuckKillCount ?? 0,
41812
+ task.resumeLimboCount ?? 0,
41813
+ task.resumeLimboTipSha ?? null,
41814
+ task.resumeLimboStepSignature ?? null,
41343
41815
  task.postReviewFixCount ?? 0,
41344
41816
  task.recoveryRetryCount ?? null,
41345
41817
  task.taskDoneRetryCount ?? 0,
@@ -41348,6 +41820,7 @@ ${outcome}`;
41348
41820
  task.verificationFailureCount ?? 0,
41349
41821
  task.mergeConflictBounceCount ?? 0,
41350
41822
  task.mergeAuditBounceCount ?? 0,
41823
+ task.mergeTransientRetryCount ?? 0,
41351
41824
  task.branchConflictRecoveryCount ?? 0,
41352
41825
  task.reviewerContextRetryCount ?? 0,
41353
41826
  task.reviewerFallbackRetryCount ?? 0,
@@ -41394,6 +41867,7 @@ ${outcome}`;
41394
41867
  toJsonNullable(task.mergeDetails),
41395
41868
  task.breakIntoSubtasks ? 1 : 0,
41396
41869
  task.noCommitsExpected ? 1 : 0,
41870
+ task.autoMerge === void 0 ? null : task.autoMerge ? 1 : 0,
41397
41871
  toJson(task.enabledWorkflowSteps || []),
41398
41872
  toJson(task.modifiedFiles || []),
41399
41873
  task.missionId ?? null,
@@ -41434,16 +41908,16 @@ ${outcome}`;
41434
41908
  this.db.prepare(`
41435
41909
  INSERT INTO tasks (
41436
41910
  id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
41437
- worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41911
+ worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, autoMerge, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41438
41912
  modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
41439
- workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41913
+ workflowStepRetries, stuckKillCount, resumeLimboCount, resumeLimboTipSha, resumeLimboStepSignature, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, mergeTransientRetryCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41440
41914
  summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
41441
41915
  tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
41442
41916
  firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
41443
41917
  dependencies, steps, log, attachments, steeringComments,
41444
41918
  comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
41445
41919
  sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
41446
- mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, scopeAutoWiden, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt, allowResurrection
41920
+ mergeDetails, breakIntoSubtasks, noCommitsExpected, autoMerge, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, scopeAutoWiden, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt, allowResurrection
41447
41921
  ) VALUES (${placeholders})
41448
41922
  `).run(...values);
41449
41923
  this.db.bumpLastModified();
@@ -41460,16 +41934,16 @@ ${outcome}`;
41460
41934
  this.db.prepare(`
41461
41935
  INSERT INTO tasks (
41462
41936
  id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
41463
- worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41937
+ worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, autoMerge, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41464
41938
  modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
41465
- workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41939
+ workflowStepRetries, stuckKillCount, resumeLimboCount, resumeLimboTipSha, resumeLimboStepSignature, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, mergeTransientRetryCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41466
41940
  summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
41467
41941
  tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
41468
41942
  firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
41469
41943
  dependencies, steps, log, attachments, steeringComments,
41470
41944
  comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
41471
41945
  sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
41472
- mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, scopeAutoWiden, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt, allowResurrection
41946
+ mergeDetails, breakIntoSubtasks, noCommitsExpected, autoMerge, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, scopeAutoWiden, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt, allowResurrection
41473
41947
  ) VALUES (${placeholders})
41474
41948
  ON CONFLICT(id) DO UPDATE SET
41475
41949
  lineageId = excluded.lineageId,
@@ -41488,6 +41962,7 @@ ${outcome}`;
41488
41962
  userPaused = excluded.userPaused,
41489
41963
  baseBranch = excluded.baseBranch,
41490
41964
  branch = excluded.branch,
41965
+ autoMerge = excluded.autoMerge,
41491
41966
  executionStartBranch = excluded.executionStartBranch,
41492
41967
  baseCommitSha = excluded.baseCommitSha,
41493
41968
  modelPresetId = excluded.modelPresetId,
@@ -41500,6 +41975,9 @@ ${outcome}`;
41500
41975
  mergeRetries = excluded.mergeRetries,
41501
41976
  workflowStepRetries = excluded.workflowStepRetries,
41502
41977
  stuckKillCount = excluded.stuckKillCount,
41978
+ resumeLimboCount = excluded.resumeLimboCount,
41979
+ resumeLimboTipSha = excluded.resumeLimboTipSha,
41980
+ resumeLimboStepSignature = excluded.resumeLimboStepSignature,
41503
41981
  postReviewFixCount = excluded.postReviewFixCount,
41504
41982
  recoveryRetryCount = excluded.recoveryRetryCount,
41505
41983
  taskDoneRetryCount = excluded.taskDoneRetryCount,
@@ -41508,6 +41986,7 @@ ${outcome}`;
41508
41986
  verificationFailureCount = excluded.verificationFailureCount,
41509
41987
  mergeConflictBounceCount = excluded.mergeConflictBounceCount,
41510
41988
  mergeAuditBounceCount = excluded.mergeAuditBounceCount,
41989
+ mergeTransientRetryCount = excluded.mergeTransientRetryCount,
41511
41990
  branchConflictRecoveryCount = excluded.branchConflictRecoveryCount,
41512
41991
  reviewerContextRetryCount = excluded.reviewerContextRetryCount,
41513
41992
  reviewerFallbackRetryCount = excluded.reviewerFallbackRetryCount,
@@ -41554,6 +42033,7 @@ ${outcome}`;
41554
42033
  mergeDetails = excluded.mergeDetails,
41555
42034
  breakIntoSubtasks = excluded.breakIntoSubtasks,
41556
42035
  noCommitsExpected = excluded.noCommitsExpected,
42036
+ autoMerge = excluded.autoMerge,
41557
42037
  enabledWorkflowSteps = excluded.enabledWorkflowSteps,
41558
42038
  modifiedFiles = excluded.modifiedFiles,
41559
42039
  missionId = excluded.missionId,
@@ -42829,6 +43309,7 @@ ${outcome}`;
42829
43309
  sourceParentTaskId: input.source?.sourceParentTaskId,
42830
43310
  sourceMetadata: withTaskBranchContextInSourceMetadata(input.source?.sourceMetadata, input.branchContext),
42831
43311
  branchContext: input.branchContext,
43312
+ autoMerge: input.autoMerge,
42832
43313
  column: input.column || "triage",
42833
43314
  dependencies: input.dependencies || [],
42834
43315
  breakIntoSubtasks: input.breakIntoSubtasks === true ? true : void 0,
@@ -43171,6 +43652,99 @@ ${newTask.description}
43171
43652
  return { ...task, prompt };
43172
43653
  });
43173
43654
  }
43655
+ createBranchGroup(input) {
43656
+ const now = Date.now();
43657
+ const id = this.generateBranchGroupId();
43658
+ this.db.prepare(`
43659
+ INSERT INTO branch_groups (id, sourceType, sourceId, branchName, worktreePath, autoMerge, prState, prUrl, prNumber, status, createdAt, updatedAt, closedAt)
43660
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
43661
+ `).run(
43662
+ id,
43663
+ input.sourceType,
43664
+ input.sourceId,
43665
+ input.branchName,
43666
+ input.worktreePath ?? null,
43667
+ input.autoMerge ? 1 : 0,
43668
+ input.prState ?? "none",
43669
+ input.prUrl ?? null,
43670
+ input.prNumber ?? null,
43671
+ input.status ?? "open",
43672
+ now,
43673
+ now,
43674
+ input.closedAt ?? null
43675
+ );
43676
+ this.db.bumpLastModified();
43677
+ return this.getBranchGroup(id);
43678
+ }
43679
+ getBranchGroup(id) {
43680
+ const row = this.db.prepare(`SELECT * FROM branch_groups WHERE id = ?`).get(id);
43681
+ return row ? this.rowToBranchGroup(row) : null;
43682
+ }
43683
+ getBranchGroupBySource(sourceType, sourceId) {
43684
+ const row = this.db.prepare(`SELECT * FROM branch_groups WHERE sourceType = ? AND sourceId = ?`).get(sourceType, sourceId);
43685
+ return row ? this.rowToBranchGroup(row) : null;
43686
+ }
43687
+ listBranchGroups(options) {
43688
+ const rows = options?.status ? this.db.prepare(`SELECT * FROM branch_groups WHERE status = ? ORDER BY createdAt ASC`).all(options.status) : this.db.prepare(`SELECT * FROM branch_groups ORDER BY createdAt ASC`).all();
43689
+ return rows.map((row) => this.rowToBranchGroup(row));
43690
+ }
43691
+ updateBranchGroup(id, patch) {
43692
+ const current = this.getBranchGroup(id);
43693
+ if (!current) {
43694
+ throw new Error(`Branch group ${id} not found`);
43695
+ }
43696
+ const nextStatus = patch.status ?? current.status;
43697
+ const now = Date.now();
43698
+ const nextClosedAt = patch.closedAt === null ? null : patch.closedAt ?? (nextStatus !== "open" && current.status === "open" ? now : current.closedAt ?? null);
43699
+ this.db.prepare(`
43700
+ UPDATE branch_groups
43701
+ SET sourceId = ?, branchName = ?, worktreePath = ?, autoMerge = ?, prState = ?, prUrl = ?, prNumber = ?, status = ?, updatedAt = ?, closedAt = ?
43702
+ WHERE id = ?
43703
+ `).run(
43704
+ patch.sourceId ?? current.sourceId,
43705
+ patch.branchName ?? current.branchName,
43706
+ patch.worktreePath === null ? null : patch.worktreePath ?? current.worktreePath ?? null,
43707
+ patch.autoMerge === void 0 ? current.autoMerge ? 1 : 0 : patch.autoMerge ? 1 : 0,
43708
+ patch.prState ?? current.prState,
43709
+ patch.prUrl === null ? null : patch.prUrl ?? current.prUrl ?? null,
43710
+ patch.prNumber === null ? null : patch.prNumber ?? current.prNumber ?? null,
43711
+ nextStatus,
43712
+ now,
43713
+ nextClosedAt,
43714
+ id
43715
+ );
43716
+ this.db.bumpLastModified();
43717
+ return this.getBranchGroup(id);
43718
+ }
43719
+ async setTaskBranchGroup(taskId, branchGroupId) {
43720
+ await this.withTaskLock(taskId, async () => {
43721
+ const dir2 = this.taskDir(taskId);
43722
+ const task = await this.readTaskJson(dir2);
43723
+ let branchContext;
43724
+ if (branchGroupId) {
43725
+ const group = this.getBranchGroup(branchGroupId);
43726
+ if (!group) {
43727
+ throw new Error(`Branch group ${branchGroupId} not found`);
43728
+ }
43729
+ branchContext = {
43730
+ groupId: group.id,
43731
+ source: group.sourceType,
43732
+ assignmentMode: "shared"
43733
+ };
43734
+ }
43735
+ task.branchContext = branchContext;
43736
+ task.sourceMetadata = withTaskBranchContextInSourceMetadata(task.sourceMetadata, branchContext);
43737
+ if (!branchContext && task.sourceMetadata) {
43738
+ const nextSourceMetadata = { ...task.sourceMetadata };
43739
+ delete nextSourceMetadata[TASK_BRANCH_CONTEXT_METADATA_KEY];
43740
+ task.sourceMetadata = Object.keys(nextSourceMetadata).length > 0 ? nextSourceMetadata : void 0;
43741
+ }
43742
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
43743
+ await this.atomicWriteTaskJson(dir2, task);
43744
+ if (this.isWatching) this.taskCache.set(taskId, { ...task });
43745
+ this.emit("task:updated", task);
43746
+ });
43747
+ }
43174
43748
  async getTaskColumns(ids) {
43175
43749
  if (ids.length === 0) {
43176
43750
  return /* @__PURE__ */ new Map();
@@ -43895,6 +44469,7 @@ ${newTask.description}
43895
44469
  throw new Error(`Cannot move ${id} to done: ${mergeBlocker}`);
43896
44470
  }
43897
44471
  }
44472
+ const settingsForInReview = toColumn === "in-review" && task.autoMerge === void 0 ? await this.getSettingsFast() : void 0;
43898
44473
  const movedAt = internal.now ?? (/* @__PURE__ */ new Date()).toISOString();
43899
44474
  task.column = toColumn;
43900
44475
  task.columnMovedAt = movedAt;
@@ -43954,6 +44529,9 @@ ${newTask.description}
43954
44529
  }
43955
44530
  }
43956
44531
  if (toColumn === "in-review") {
44532
+ if (task.autoMerge === void 0 && settingsForInReview) {
44533
+ task.autoMerge = settingsForInReview.autoMerge;
44534
+ }
43957
44535
  task.recoveryRetryCount = void 0;
43958
44536
  task.nextRecoveryAt = void 0;
43959
44537
  if (task.status === "queued") {
@@ -44337,11 +44915,21 @@ ${newTask.description}
44337
44915
  } else if (updates.baseBranch !== void 0) {
44338
44916
  task.baseBranch = updates.baseBranch;
44339
44917
  }
44918
+ if (updates.autoMerge === null) {
44919
+ task.autoMerge = void 0;
44920
+ } else if (updates.autoMerge !== void 0) {
44921
+ task.autoMerge = updates.autoMerge;
44922
+ }
44340
44923
  if (updates.branch === null) {
44341
44924
  task.branch = void 0;
44342
44925
  } else if (updates.branch !== void 0) {
44343
44926
  task.branch = updates.branch;
44344
44927
  }
44928
+ if (updates.autoMerge === null) {
44929
+ task.autoMerge = void 0;
44930
+ } else if (updates.autoMerge !== void 0) {
44931
+ task.autoMerge = updates.autoMerge;
44932
+ }
44345
44933
  if (updates.executionStartBranch === null) {
44346
44934
  task.executionStartBranch = void 0;
44347
44935
  } else if (updates.executionStartBranch !== void 0) {
@@ -44361,6 +44949,21 @@ ${newTask.description}
44361
44949
  } else if (updates.stuckKillCount !== void 0) {
44362
44950
  task.stuckKillCount = updates.stuckKillCount;
44363
44951
  }
44952
+ if (updates.resumeLimboCount === null) {
44953
+ task.resumeLimboCount = void 0;
44954
+ } else if (updates.resumeLimboCount !== void 0) {
44955
+ task.resumeLimboCount = updates.resumeLimboCount;
44956
+ }
44957
+ if (updates.resumeLimboTipSha === null) {
44958
+ task.resumeLimboTipSha = void 0;
44959
+ } else if (updates.resumeLimboTipSha !== void 0) {
44960
+ task.resumeLimboTipSha = updates.resumeLimboTipSha;
44961
+ }
44962
+ if (updates.resumeLimboStepSignature === null) {
44963
+ task.resumeLimboStepSignature = void 0;
44964
+ } else if (updates.resumeLimboStepSignature !== void 0) {
44965
+ task.resumeLimboStepSignature = updates.resumeLimboStepSignature;
44966
+ }
44364
44967
  if (updates.postReviewFixCount === null) {
44365
44968
  task.postReviewFixCount = void 0;
44366
44969
  } else if (updates.postReviewFixCount !== void 0) {
@@ -44401,6 +45004,11 @@ ${newTask.description}
44401
45004
  } else if (updates.mergeAuditBounceCount !== void 0) {
44402
45005
  task.mergeAuditBounceCount = updates.mergeAuditBounceCount;
44403
45006
  }
45007
+ if (updates.mergeTransientRetryCount === null) {
45008
+ task.mergeTransientRetryCount = void 0;
45009
+ } else if (updates.mergeTransientRetryCount !== void 0) {
45010
+ task.mergeTransientRetryCount = updates.mergeTransientRetryCount;
45011
+ }
44404
45012
  if (updates.branchConflictRecoveryCount === null) {
44405
45013
  task.branchConflictRecoveryCount = void 0;
44406
45014
  } else if (updates.branchConflictRecoveryCount !== void 0) {
@@ -46452,8 +47060,42 @@ ${newTask.description}
46452
47060
  INSERT INTO agentLogEntries (taskId, timestamp, text, type, detail, agent)
46453
47061
  VALUES (?, ?, ?, ?, ?, ?)
46454
47062
  `);
47063
+ const citationInputs = [];
46455
47064
  for (const entry of validEntries) {
46456
- stmt.run(entry.taskId, entry.timestamp, entry.text, entry.type, entry.detail, entry.agent);
47065
+ const insertResult = stmt.run(
47066
+ entry.taskId,
47067
+ entry.timestamp,
47068
+ entry.text,
47069
+ entry.type,
47070
+ entry.detail,
47071
+ entry.agent
47072
+ );
47073
+ const insertedId = insertResult.lastInsertRowid;
47074
+ if (insertedId === void 0 || insertedId === null) {
47075
+ continue;
47076
+ }
47077
+ const sourceRef = `agentLog:${String(insertedId)}`;
47078
+ try {
47079
+ citationInputs.push(
47080
+ ...this.scanAndRecordCitations(
47081
+ entry.text,
47082
+ "agent_log",
47083
+ sourceRef,
47084
+ entry.agent ?? "unknown",
47085
+ entry.taskId,
47086
+ entry.timestamp
47087
+ )
47088
+ );
47089
+ } catch (err) {
47090
+ console.warn("[fusion] Failed to scan goal citations from agent_log:", err);
47091
+ }
47092
+ }
47093
+ if (citationInputs.length > 0) {
47094
+ try {
47095
+ this.recordGoalCitations(citationInputs);
47096
+ } catch (err) {
47097
+ console.warn("[fusion] Failed to record goal citations from agent_log batch:", err);
47098
+ }
46457
47099
  }
46458
47100
  this.db.bumpLastModified();
46459
47101
  }
@@ -46491,8 +47133,9 @@ ${newTask.description}
46491
47133
  VALUES (?, ?, ?, ?, ?, ?)
46492
47134
  `);
46493
47135
  this.db.transaction(() => {
47136
+ const citationInputs = [];
46494
47137
  for (const entry of normalizedEntries) {
46495
- stmt.run(
47138
+ const insertResult = stmt.run(
46496
47139
  entry.taskId,
46497
47140
  timestamp,
46498
47141
  entry.text,
@@ -46500,6 +47143,31 @@ ${newTask.description}
46500
47143
  entry.detail ?? null,
46501
47144
  entry.agent ?? null
46502
47145
  );
47146
+ const insertedId = insertResult.lastInsertRowid;
47147
+ if (insertedId === void 0 || insertedId === null) {
47148
+ continue;
47149
+ }
47150
+ try {
47151
+ citationInputs.push(
47152
+ ...this.scanAndRecordCitations(
47153
+ entry.text,
47154
+ "agent_log",
47155
+ `agentLog:${String(insertedId)}`,
47156
+ entry.agent ?? "unknown",
47157
+ entry.taskId,
47158
+ timestamp
47159
+ )
47160
+ );
47161
+ } catch (err) {
47162
+ console.warn("[fusion] Failed to scan goal citations from agent log batch:", err);
47163
+ }
47164
+ }
47165
+ if (citationInputs.length > 0) {
47166
+ try {
47167
+ this.recordGoalCitations(citationInputs);
47168
+ } catch (err) {
47169
+ console.warn("[fusion] Failed to record goal citations from appendAgentLogBatch:", err);
47170
+ }
46503
47171
  }
46504
47172
  this.db.bumpLastModified();
46505
47173
  });
@@ -46894,6 +47562,21 @@ ${newTask.description}
46894
47562
  this.db.bumpLastModified();
46895
47563
  const task = await this.getTask(taskId);
46896
47564
  this.emit("task:updated", task);
47565
+ try {
47566
+ const citationInputs = this.scanAndRecordCitations(
47567
+ input.content,
47568
+ "task_document",
47569
+ `document:${taskId}:${input.key}:rev${document2.revision}`,
47570
+ input.author ?? "user",
47571
+ taskId,
47572
+ document2.updatedAt
47573
+ );
47574
+ if (citationInputs.length > 0) {
47575
+ this.recordGoalCitations(citationInputs);
47576
+ }
47577
+ } catch (err) {
47578
+ console.warn("[fusion] Failed to scan/record goal citations from task document:", err);
47579
+ }
46897
47580
  return document2;
46898
47581
  }
46899
47582
  /**
@@ -48744,6 +49427,7 @@ var init_manual_retry_reset = __esm({
48744
49427
  "use strict";
48745
49428
  MANUAL_RETRY_RESET_COUNTER_KEYS = [
48746
49429
  "stuckKillCount",
49430
+ "resumeLimboCount",
48747
49431
  "recoveryRetryCount",
48748
49432
  "taskDoneRetryCount",
48749
49433
  "worktreeSessionRetryCount",
@@ -69678,6 +70362,8 @@ __export(src_exports, {
69678
70362
  FileMemoryBackend: () => FileMemoryBackend,
69679
70363
  FirstRunDetector: () => FirstRunDetector,
69680
70364
  GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
70365
+ GOAL_CITATION_SNIPPET_MAX: () => GOAL_CITATION_SNIPPET_MAX,
70366
+ GOAL_ID_PATTERN: () => GOAL_ID_PATTERN,
69681
70367
  GlobalSettingsStore: () => GlobalSettingsStore,
69682
70368
  GoalStore: () => GoalStore,
69683
70369
  HIGH_FANOUT_BLOCKER_TODO_THRESHOLD: () => HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
@@ -69848,6 +70534,7 @@ __export(src_exports, {
69848
70534
  buildQmdSearchArgs: () => buildQmdSearchArgs,
69849
70535
  buildResearchDocumentKey: () => buildResearchDocumentKey,
69850
70536
  buildReviewerMemoryInstructions: () => buildReviewerMemoryInstructions,
70537
+ buildSnippet: () => buildSnippet,
69851
70538
  buildTaskLineageTrailer: () => buildTaskLineageTrailer,
69852
70539
  buildTriageMemoryInstructions: () => buildTriageMemoryInstructions,
69853
70540
  buildUnblockWeightMap: () => buildUnblockWeightMap,
@@ -69929,6 +70616,7 @@ __export(src_exports, {
69929
70616
  extractClaudeCliStoredCredential: () => extractClaudeCliStoredCredential,
69930
70617
  extractCodexCliStoredCredential: () => extractCodexCliStoredCredential,
69931
70618
  extractDreamProcessorResult: () => extractDreamProcessorResult,
70619
+ extractGoalCitations: () => extractGoalCitations,
69932
70620
  extractIntentSignature: () => extractIntentSignature,
69933
70621
  extractSandboxProvisioningRequest: () => extractSandboxProvisioningRequest,
69934
70622
  extractTaskIdTokens: () => extractTaskIdTokens,
@@ -70122,6 +70810,7 @@ __export(src_exports, {
70122
70810
  resolveAgentProvisioningPolicy: () => resolveAgentProvisioningPolicy,
70123
70811
  resolveDependencyOrder: () => resolveDependencyOrder,
70124
70812
  resolveEffectiveAgentPermissionPolicy: () => resolveEffectiveAgentPermissionPolicy,
70813
+ resolveEffectiveAutoMerge: () => resolveEffectiveAutoMerge,
70125
70814
  resolveEvalSettings: () => resolveEvalSettings,
70126
70815
  resolveExecutionSettingsModel: () => resolveExecutionSettingsModel,
70127
70816
  resolveGlobalDir: () => resolveGlobalDir,
@@ -70322,6 +71011,7 @@ var init_src = __esm({
70322
71011
  init_mission_store();
70323
71012
  init_goal_types();
70324
71013
  init_goal_store();
71014
+ init_goal_citation_extractor();
70325
71015
  init_central_core();
70326
71016
  init_central_db();
70327
71017
  init_node_connection();
@@ -79796,12 +80486,16 @@ var init_agent_instructions = __esm({
79796
80486
 
79797
80487
  // ../engine/src/prompt-layers.ts
79798
80488
  function buildPromptLayers(input) {
79799
- const { basePrompt, agentInstructions, memorySection, pluginContributions, performanceFeedback } = input;
80489
+ const { basePrompt, agentInstructions, memorySection, goalContext, pluginContributions, performanceFeedback } = input;
79800
80490
  const dynamicParts = [];
79801
80491
  const trimmedMemory = memorySection?.trim() ?? "";
79802
80492
  if (trimmedMemory) {
79803
80493
  dynamicParts.push(trimmedMemory);
79804
80494
  }
80495
+ const trimmedGoalContext = goalContext?.trim() ?? "";
80496
+ if (trimmedGoalContext) {
80497
+ dynamicParts.push(trimmedGoalContext);
80498
+ }
79805
80499
  const trimmedInstructions = agentInstructions?.trim() ?? "";
79806
80500
  if (trimmedInstructions) {
79807
80501
  dynamicParts.push(`## Custom Instructions
@@ -85129,7 +85823,7 @@ function isInsideWorktreesDir(rootDir, worktreePath, settings) {
85129
85823
  return isInsideConfiguredWorktreesDir(rootDir, settings, worktreePath);
85130
85824
  }
85131
85825
  function deriveTaskIdFromBranch(branchName) {
85132
- const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?$/i);
85826
+ const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?(?:-[a-z0-9._-]+)*$/i);
85133
85827
  return match ? match[1].toUpperCase() : branchName.toUpperCase();
85134
85828
  }
85135
85829
  async function scanIdleWorktrees(rootDir, store, settings) {
@@ -101337,6 +102031,12 @@ async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTar
101337
102031
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
101338
102032
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
101339
102033
  }
102034
+ function buildResumeLimboStepSignature(task) {
102035
+ return JSON.stringify({
102036
+ currentStep: task.currentStep ?? null,
102037
+ steps: Array.isArray(task.steps) ? task.steps.map((step) => step.status) : []
102038
+ });
102039
+ }
101340
102040
  function formatRecoveryTimestamp(date = /* @__PURE__ */ new Date()) {
101341
102041
  const pad = (value) => String(value).padStart(2, "0");
101342
102042
  return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
@@ -101551,7 +102251,7 @@ function isNoTaskDoneFailure2(task) {
101551
102251
  function hasStepProgress2(task) {
101552
102252
  return task.steps.some((step) => step.status !== "pending");
101553
102253
  }
101554
- var log20, worktreeMetadataReconcileLog, execAsync27, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS, COMPLETION_HANDOFF_LIMBO_GRACE_MS, MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES2, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_WORKTREE_SESSION_RETRIES, MAX_AUTO_MERGE_RETRIES, MAX_TRANSIENT_MERGE_RECOVERIES, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
102254
+ var log20, worktreeMetadataReconcileLog, execAsync27, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS, COMPLETION_HANDOFF_LIMBO_GRACE_MS, MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES, MAX_NO_PROGRESS_RESUME_ATTEMPTS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES2, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_WORKTREE_SESSION_RETRIES, MAX_AUTO_MERGE_RETRIES, MAX_TRANSIENT_MERGE_RECOVERIES, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
101555
102255
  var init_self_healing = __esm({
101556
102256
  "../engine/src/self-healing.ts"() {
101557
102257
  "use strict";
@@ -101584,6 +102284,7 @@ var init_self_healing = __esm({
101584
102284
  STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS = 10 * 6e4;
101585
102285
  COMPLETION_HANDOFF_LIMBO_GRACE_MS = 5 * 6e4;
101586
102286
  MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES = 3;
102287
+ MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
101587
102288
  APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
101588
102289
  STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
101589
102290
  STARVED_PEER_PROGRESS_THRESHOLD = 3;
@@ -103100,13 +103801,71 @@ var init_self_healing = __esm({
103100
103801
  }
103101
103802
  }
103102
103803
  const preservedCommitCount = inspection.kind === "fully-subsumed" ? 0 : inspection.taskAttributedCommitCount;
103804
+ const stepSignature = buildResumeLimboStepSignature(task);
103805
+ const hasActiveSessionSignal = Boolean(task.checkedOutBy) || activeTaskIds.has(task.id.toUpperCase());
103806
+ const hasPriorSnapshot = typeof task.resumeLimboTipSha === "string" && typeof task.resumeLimboStepSignature === "string";
103807
+ const unchangedSincePriorResume = hasPriorSnapshot && task.resumeLimboTipSha === inspection.tipSha && task.resumeLimboStepSignature === stepSignature;
103808
+ const isNoProgressResume = task.column === "in-progress" && unchangedSincePriorResume && !hasActiveSessionSignal;
103809
+ const resumeAttemptCount = isNoProgressResume ? (task.resumeLimboCount ?? 0) + 1 : 0;
103810
+ if (task.column === "in-progress" && isNoProgressResume && resumeAttemptCount >= MAX_NO_PROGRESS_RESUME_ATTEMPTS) {
103811
+ const idleAnchor = task.executionStartedAt ?? task.columnMovedAt ?? task.updatedAt;
103812
+ const idleAnchorMs = Date.parse(idleAnchor ?? "");
103813
+ const idleMs = Number.isFinite(idleAnchorMs) ? Math.max(0, Date.now() - idleAnchorMs) : null;
103814
+ await this.store.moveTask(task.id, "todo", {
103815
+ moveSource: "engine",
103816
+ preserveWorktree: true,
103817
+ preserveProgress: true,
103818
+ preserveResumeState: true
103819
+ });
103820
+ await this.store.updateTask(task.id, {
103821
+ resumeLimboCount: 0,
103822
+ resumeLimboTipSha: inspection.tipSha,
103823
+ resumeLimboStepSignature: stepSignature
103824
+ });
103825
+ await this.store.logEntry(
103826
+ task.id,
103827
+ `[recovery] resume-limbo-escalated ${task.id} moved to todo after ${resumeAttemptCount} no-progress reclaim/resume attempts`,
103828
+ JSON.stringify({
103829
+ frozenTipSha: inspection.tipSha,
103830
+ idleMs,
103831
+ resumeAttemptCount,
103832
+ currentStep: task.currentStep ?? null
103833
+ })
103834
+ );
103835
+ try {
103836
+ await createRunAuditor(this.store, {
103837
+ runId: generateSyntheticRunId("self-heal", task.id),
103838
+ agentId: "self-healing",
103839
+ taskId: task.id,
103840
+ taskLineageId: task.lineageId,
103841
+ phase: "reclaim-self-owned-branch-conflicts"
103842
+ }).database({
103843
+ type: "task:resume-limbo-escalated",
103844
+ target: task.id,
103845
+ metadata: {
103846
+ taskId: task.id,
103847
+ frozenTipSha: inspection.tipSha,
103848
+ idleMs,
103849
+ resumeAttemptCount,
103850
+ currentStep: task.currentStep ?? null
103851
+ }
103852
+ });
103853
+ } catch (auditErr) {
103854
+ log20.warn(`Failed to write task:resume-limbo-escalated run-audit event for ${task.id}: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`);
103855
+ }
103856
+ recovered++;
103857
+ continue;
103858
+ }
103103
103859
  await this.store.updateTask(task.id, {
103104
103860
  worktree: inspection.livePath,
103105
103861
  branch: task.branch,
103106
103862
  paused: false,
103107
103863
  pausedReason: void 0,
103108
103864
  status: null,
103109
- error: null
103865
+ error: null,
103866
+ resumeLimboCount: resumeAttemptCount,
103867
+ resumeLimboTipSha: inspection.tipSha,
103868
+ resumeLimboStepSignature: stepSignature
103110
103869
  });
103111
103870
  await this.store.logEntry(
103112
103871
  task.id,
@@ -104338,7 +105097,7 @@ var init_self_healing = __esm({
104338
105097
  const blockerId = task.blockedBy;
104339
105098
  const unresolvedDeps = task.dependencies.filter((depId) => {
104340
105099
  const dep = taskById.get(depId);
104341
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
105100
+ return dep && !dep.deletedAt && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
104342
105101
  });
104343
105102
  const overlapBlocker = task.overlapBlockedBy ? taskById.get(task.overlapBlockedBy) : void 0;
104344
105103
  const hasActiveOverlapBlocker = Boolean(
@@ -104364,6 +105123,9 @@ var init_self_healing = __esm({
104364
105123
  reasonCode = "missing-blocker";
104365
105124
  reason = `blocker ${blockerId} missing`;
104366
105125
  }
105126
+ } else if (blocker.deletedAt) {
105127
+ reasonCode = "soft-deleted-blocker";
105128
+ reason = `blocker ${blockerId} soft-deleted at ${blocker.deletedAt}`;
104367
105129
  } else if (blocker.column === "done") {
104368
105130
  reasonCode = "blocker-done";
104369
105131
  reason = `blocker ${blockerId} is done`;
@@ -105893,6 +106655,12 @@ var init_self_healing = __esm({
105893
106655
  try {
105894
106656
  const hardBlocker = getTaskHardMergeBlocker({
105895
106657
  ...task,
106658
+ // Merge-confirmed tasks have already landed. Treat stale merge
106659
+ // in-flight statuses as soft state to clear during finalization,
106660
+ // not hard blockers that park an otherwise confirmed merge as failed.
106661
+ paused: false,
106662
+ status: task.status === "merging" || task.status === "merging-pr" ? void 0 : task.status,
106663
+ error: void 0,
105896
106664
  steps: task.steps ?? [],
105897
106665
  workflowStepResults: task.workflowStepResults
105898
106666
  });
@@ -110524,6 +111292,258 @@ var init_token_cap_detector = __esm({
110524
111292
  }
110525
111293
  });
110526
111294
 
111295
+ // ../engine/src/goal-context-injector.ts
111296
+ function normalizeTitle(title) {
111297
+ return title.replace(/\s*\n+\s*/g, " ").trim();
111298
+ }
111299
+ function renderGoalContext(goals) {
111300
+ const lines = goals.map((goal) => `- ${goal.id}: ${normalizeTitle(goal.title)}`);
111301
+ return `## Active Goals
111302
+
111303
+ ${lines.join("\n")}`;
111304
+ }
111305
+ function buildGoalContextSection(input) {
111306
+ const { activeGoals, onTruncated } = input;
111307
+ if (activeGoals.length === 0) {
111308
+ return { text: "", emittedGoalIds: [], truncated: null };
111309
+ }
111310
+ const sortedGoals = [...activeGoals].sort((a, b2) => {
111311
+ const byCreated = a.createdAt.localeCompare(b2.createdAt);
111312
+ if (byCreated !== 0) {
111313
+ return byCreated;
111314
+ }
111315
+ return a.id.localeCompare(b2.id);
111316
+ });
111317
+ const resolvedMaxGoals = Math.min(input.maxGoals ?? MAX_INJECTED_GOALS, MAX_INJECTED_GOALS);
111318
+ const charBudget = input.charBudget ?? DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
111319
+ const droppedGoalIds = [];
111320
+ let selectedGoals = sortedGoals.slice(0, resolvedMaxGoals);
111321
+ if (sortedGoals.length > resolvedMaxGoals) {
111322
+ droppedGoalIds.push(...sortedGoals.slice(resolvedMaxGoals).map((goal) => goal.id));
111323
+ }
111324
+ let text = renderGoalContext(selectedGoals);
111325
+ let usedBudgetDrop = false;
111326
+ while (selectedGoals.length > 1 && text.length > charBudget) {
111327
+ const dropped = selectedGoals[selectedGoals.length - 1];
111328
+ selectedGoals = selectedGoals.slice(0, -1);
111329
+ droppedGoalIds.push(dropped.id);
111330
+ text = renderGoalContext(selectedGoals);
111331
+ usedBudgetDrop = true;
111332
+ }
111333
+ let truncated = null;
111334
+ if (droppedGoalIds.length > 0) {
111335
+ truncated = {
111336
+ reason: usedBudgetDrop ? "budget" : "cap",
111337
+ requested: activeGoals.length,
111338
+ emitted: selectedGoals.length,
111339
+ droppedGoalIds,
111340
+ charBudget,
111341
+ producedChars: text.length
111342
+ };
111343
+ onTruncated?.(truncated);
111344
+ }
111345
+ return {
111346
+ text,
111347
+ emittedGoalIds: selectedGoals.map((goal) => goal.id),
111348
+ truncated
111349
+ };
111350
+ }
111351
+ var MAX_INJECTED_GOALS, resolvedActiveGoalLimit, DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
111352
+ var init_goal_context_injector = __esm({
111353
+ "../engine/src/goal-context-injector.ts"() {
111354
+ "use strict";
111355
+ init_src();
111356
+ MAX_INJECTED_GOALS = 5;
111357
+ resolvedActiveGoalLimit = "ACTIVE_GOAL_LIMIT" in src_exports && typeof ACTIVE_GOAL_LIMIT === "number" ? ACTIVE_GOAL_LIMIT : MAX_INJECTED_GOALS;
111358
+ if (MAX_INJECTED_GOALS > resolvedActiveGoalLimit) {
111359
+ throw new Error(
111360
+ `MAX_INJECTED_GOALS (${MAX_INJECTED_GOALS}) cannot exceed ACTIVE_GOAL_LIMIT (${resolvedActiveGoalLimit})`
111361
+ );
111362
+ }
111363
+ DEFAULT_GOAL_INJECTION_CHAR_BUDGET = 600;
111364
+ }
111365
+ });
111366
+
111367
+ // ../engine/src/goal-injection-diagnostics.ts
111368
+ function classifyGoalInjectionResult(result) {
111369
+ if (result.emittedGoalIds.length === 0) {
111370
+ return {
111371
+ outcome: "no-goals",
111372
+ goalCount: 0,
111373
+ goalIds: [],
111374
+ truncated: false
111375
+ };
111376
+ }
111377
+ return {
111378
+ outcome: "applied",
111379
+ goalCount: result.emittedGoalIds.length,
111380
+ goalIds: result.emittedGoalIds,
111381
+ truncated: result.truncated !== null
111382
+ };
111383
+ }
111384
+ function classifyGoalInjectionFailure(reason, error) {
111385
+ const resolvedErrorClass = error && typeof error === "object" && "constructor" in error && typeof error.constructor?.name === "string" ? error.constructor.name : void 0;
111386
+ return {
111387
+ outcome: "disabled-or-failed",
111388
+ goalCount: 0,
111389
+ goalIds: [],
111390
+ truncated: false,
111391
+ reason,
111392
+ ...resolvedErrorClass ? { errorClass: resolvedErrorClass } : {}
111393
+ };
111394
+ }
111395
+ function resolveGoalContextForDiagnostics(input) {
111396
+ if (!input.listActiveGoals) {
111397
+ return {
111398
+ goalContext: "",
111399
+ classification: classifyGoalInjectionFailure(input.disabledReason ?? "store-unavailable")
111400
+ };
111401
+ }
111402
+ try {
111403
+ const activeGoals = input.listActiveGoals();
111404
+ const injector = input.injector ?? ((goals) => buildGoalContextSection({ activeGoals: goals }));
111405
+ try {
111406
+ const injectionResult = injector(activeGoals);
111407
+ return {
111408
+ goalContext: injectionResult.text,
111409
+ classification: classifyGoalInjectionResult(injectionResult)
111410
+ };
111411
+ } catch (injectorError) {
111412
+ return {
111413
+ goalContext: "",
111414
+ classification: classifyGoalInjectionFailure("injector-threw", injectorError)
111415
+ };
111416
+ }
111417
+ } catch (listError) {
111418
+ return {
111419
+ goalContext: "",
111420
+ classification: classifyGoalInjectionFailure("list-failed", listError)
111421
+ };
111422
+ }
111423
+ }
111424
+ function formatAgentLogLine(input) {
111425
+ const ids = JSON.stringify(input.goalIds);
111426
+ const reason = input.reason ? ` reason=${input.reason}` : "";
111427
+ const errorClass = input.errorClass ? ` err=${input.errorClass}` : "";
111428
+ return `[goal-injection] ${input.outcome} count=${input.goalCount} ids=${ids} truncated=${String(input.truncated)}${reason}${errorClass}`;
111429
+ }
111430
+ async function emitGoalInjectionDiagnostic(input) {
111431
+ const record = {
111432
+ lane: input.lane,
111433
+ outcome: input.outcome,
111434
+ goalCount: input.goalCount,
111435
+ goalIds: [...input.goalIds],
111436
+ truncated: input.truncated,
111437
+ ...input.reason ? { reason: input.reason } : {},
111438
+ ...input.errorClass ? { errorClass: input.errorClass } : {},
111439
+ ...input.runId ? { runId: input.runId } : {},
111440
+ ...input.agentId ? { agentId: input.agentId } : {},
111441
+ ...input.taskId ? { taskId: input.taskId } : {},
111442
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
111443
+ };
111444
+ if (input.store && record.taskId) {
111445
+ try {
111446
+ await input.store.logEntry(record.taskId, formatAgentLogLine(record), void 0, input.runContext ?? void 0);
111447
+ } catch (error) {
111448
+ diagnosticsLog.warn(
111449
+ `failed to append goal-injection task log for ${record.taskId}: ${error instanceof Error ? error.message : String(error)}`
111450
+ );
111451
+ }
111452
+ }
111453
+ const auditStore = input.store;
111454
+ const hasAuditWriter = Boolean(auditStore && typeof auditStore.recordRunAuditEvent === "function");
111455
+ if (!input.runContext || !hasAuditWriter || !auditStore) {
111456
+ diagnosticsLog.warn(
111457
+ `goal-injection diagnostic emitted without run-audit side effect (lane=${record.lane}, hasContext=${Boolean(input.runContext)}, hasWriter=${Boolean(hasAuditWriter)})`
111458
+ );
111459
+ return record;
111460
+ }
111461
+ try {
111462
+ await auditStore.recordRunAuditEvent({
111463
+ taskId: input.runContext.taskId,
111464
+ agentId: input.runContext.agentId,
111465
+ runId: input.runContext.runId,
111466
+ domain: "database",
111467
+ mutationType: "prompt:goal-injection",
111468
+ target: record.lane,
111469
+ metadata: {
111470
+ lane: record.lane,
111471
+ outcome: record.outcome,
111472
+ goalCount: record.goalCount,
111473
+ goalIds: record.goalIds,
111474
+ truncated: record.truncated,
111475
+ ...record.reason ? { reason: record.reason } : {},
111476
+ ...record.errorClass ? { errorClass: record.errorClass } : {},
111477
+ ...record.runId ? { runId: record.runId } : {},
111478
+ ...record.agentId ? { agentId: record.agentId } : {},
111479
+ ...record.taskId ? { taskId: record.taskId } : {}
111480
+ }
111481
+ });
111482
+ } catch (error) {
111483
+ diagnosticsLog.warn(
111484
+ `failed to append goal-injection run-audit event for lane=${record.lane}: ${error instanceof Error ? error.message : String(error)}`
111485
+ );
111486
+ }
111487
+ return record;
111488
+ }
111489
+ var diagnosticsLog;
111490
+ var init_goal_injection_diagnostics = __esm({
111491
+ "../engine/src/goal-injection-diagnostics.ts"() {
111492
+ "use strict";
111493
+ init_goal_context_injector();
111494
+ init_logger2();
111495
+ diagnosticsLog = createLogger2("goal-injection-diagnostics");
111496
+ }
111497
+ });
111498
+
111499
+ // ../engine/src/goal-anchoring-audit.ts
111500
+ async function emitGoalAnchoringAudit(auditor, input) {
111501
+ const isApplied = input.goalsInjected > 0;
111502
+ await auditor.database({
111503
+ type: isApplied ? GOAL_INJECTION_APPLIED : GOAL_INJECTION_SKIPPED,
111504
+ target: input.taskId ?? "goals",
111505
+ metadata: {
111506
+ lane: input.lane,
111507
+ count: input.goalsInjected,
111508
+ ...typeof input.truncated === "boolean" ? { truncated: input.truncated } : {},
111509
+ ...input.reason ? { reason: input.reason } : {}
111510
+ }
111511
+ });
111512
+ }
111513
+ function emitGoalRetrievalAudit(store, ctx, input) {
111514
+ if (!ctx.runId || !ctx.agentId) return;
111515
+ try {
111516
+ store.recordRunAuditEvent({
111517
+ runId: ctx.runId,
111518
+ agentId: ctx.agentId,
111519
+ taskId: ctx.taskId,
111520
+ domain: "database",
111521
+ mutationType: GOAL_RETRIEVAL_INVOKED,
111522
+ target: input.goalId ?? "goals",
111523
+ metadata: {
111524
+ toolName: input.toolName,
111525
+ count: input.resultCount,
111526
+ notFound: input.notFound ?? false
111527
+ }
111528
+ });
111529
+ } catch (error) {
111530
+ console.warn("[fusion-extension] goal retrieval audit emission skipped", error);
111531
+ }
111532
+ }
111533
+ var GOAL_INJECTION_APPLIED, GOAL_INJECTION_SKIPPED, GOAL_RETRIEVAL_INVOKED, GOAL_AUDIT_TYPES;
111534
+ var init_goal_anchoring_audit = __esm({
111535
+ "../engine/src/goal-anchoring-audit.ts"() {
111536
+ "use strict";
111537
+ GOAL_INJECTION_APPLIED = "goal:injection-applied";
111538
+ GOAL_INJECTION_SKIPPED = "goal:injection-skipped";
111539
+ GOAL_RETRIEVAL_INVOKED = "goal:retrieval-invoked";
111540
+ GOAL_AUDIT_TYPES = [GOAL_INJECTION_APPLIED, GOAL_INJECTION_SKIPPED, GOAL_RETRIEVAL_INVOKED];
111541
+ if (new Set(GOAL_AUDIT_TYPES).size !== GOAL_AUDIT_TYPES.length || GOAL_AUDIT_TYPES.some((value) => typeof value !== "string" || !value.startsWith("goal:"))) {
111542
+ throw new Error("Goal anchoring audit mutation types must be unique goal:* strings.");
111543
+ }
111544
+ }
111545
+ });
111546
+
110527
111547
  // ../engine/src/auto-recovery-handlers/branch-worktree.ts
110528
111548
  import { exec as exec31 } from "node:child_process";
110529
111549
  import { existsSync as existsSync39 } from "node:fs";
@@ -111388,7 +112408,7 @@ function isTransientMissingTaskJsonError(error, task) {
111388
112408
  const normalizedTaskJsonPath = resolvePath(filePath);
111389
112409
  return normalizedTaskJsonPath.startsWith(`${normalizedWorktree}/`);
111390
112410
  }
111391
- function detectPendingReviewBlock(task, codeReviewVerdicts) {
112411
+ function detectPendingReviewBlock(task, _codeReviewVerdicts) {
111392
112412
  const inProgressStepIndices = [];
111393
112413
  for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
111394
112414
  if (task.steps[stepIndex]?.status === "in-progress") {
@@ -111400,9 +112420,6 @@ function detectPendingReviewBlock(task, codeReviewVerdicts) {
111400
112420
  }
111401
112421
  const recentActions = (task.log ?? []).slice(-30).map((entry) => entry.action?.trim()).filter((action) => Boolean(action));
111402
112422
  for (const stepIndex of inProgressStepIndices) {
111403
- if (codeReviewVerdicts.get(stepIndex) === "REVISE") {
111404
- return { blocked: true, reason: "code-review-revise-outstanding", stepIndex };
111405
- }
111406
112423
  const stepDisplay = stepIndex + 1;
111407
112424
  const codeRequest = `code review requested for Step ${stepDisplay}`;
111408
112425
  const planRequest = `plan review requested for Step ${stepDisplay}`;
@@ -111963,6 +112980,8 @@ var init_executor = __esm({
111963
112980
  init_worktree_hooks();
111964
112981
  init_agent_instructions();
111965
112982
  init_prompt_layers();
112983
+ init_goal_injection_diagnostics();
112984
+ init_goal_anchoring_audit();
111966
112985
  init_run_audit();
111967
112986
  init_auto_recovery();
111968
112987
  init_restart_recovery_coordinator();
@@ -112349,6 +113368,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
112349
113368
  executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
112350
113369
  try {
112351
113370
  await this.clearResumeFailureState(task);
113371
+ await this.store.updateTask(task.id, {
113372
+ resumeLimboCount: 0,
113373
+ resumeLimboTipSha: null,
113374
+ resumeLimboStepSignature: null
113375
+ });
112352
113376
  await this.store.logEntry(task.id, "Resuming execution after unpause", void 0, this.getRunContextFor(task.id));
112353
113377
  await this.recoverApprovedStepsOnResume(task.id);
112354
113378
  } catch (clearErr) {
@@ -114724,8 +115748,30 @@ ${summary}`,
114724
115748
  if (executorPluginContributions) {
114725
115749
  executorLog.log(`${task.id}: applied plugin prompt contributions for executor-system surface`);
114726
115750
  }
115751
+ const executorGoalResolution = resolveGoalContextForDiagnostics({
115752
+ listActiveGoals: typeof this.store.getGoalStore === "function" ? () => this.store.getGoalStore().listGoals({ status: "active" }) : void 0
115753
+ });
115754
+ const executorGoalContext = executorGoalResolution.goalContext;
115755
+ const executorGoalClassification = executorGoalResolution.classification;
115756
+ await emitGoalAnchoringAudit(audit, {
115757
+ lane: "executor",
115758
+ taskId: task.id,
115759
+ goalsInjected: executorGoalClassification.goalCount,
115760
+ truncated: executorGoalClassification.truncated,
115761
+ reason: executorGoalClassification.outcome === "no-goals" ? "no-active-goals" : void 0
115762
+ });
115763
+ await emitGoalInjectionDiagnostic({
115764
+ lane: "executor",
115765
+ ...executorGoalClassification,
115766
+ runId: engineRunContext.runId,
115767
+ agentId: engineRunContext.agentId,
115768
+ taskId: task.id,
115769
+ store: this.store,
115770
+ runContext: engineRunContext
115771
+ });
114727
115772
  const executorLayers = buildPromptLayers({
114728
115773
  basePrompt: getExecutorSystemPrompt(settings),
115774
+ goalContext: executorGoalContext,
114729
115775
  agentInstructions: executorInstructions,
114730
115776
  pluginContributions: executorPluginContributions
114731
115777
  });
@@ -120195,7 +121241,8 @@ async function collectTaskEvaluationEvidence(params) {
120195
121241
  taskDoneRetryCount: task.taskDoneRetryCount ?? 0,
120196
121242
  verificationFailureCount: task.verificationFailureCount ?? 0,
120197
121243
  mergeConflictBounceCount: task.mergeConflictBounceCount ?? 0,
120198
- mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0
121244
+ mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0,
121245
+ mergeTransientRetryCount: task.mergeTransientRetryCount ?? 0
120199
121246
  }
120200
121247
  }],
120201
121248
  commits: commitEvidence,
@@ -120654,6 +121701,25 @@ function filterPathsByIgnoreList(paths, ignorePaths) {
120654
121701
  }
120655
121702
  return paths.filter((path10) => !normalizedIgnorePaths.some((ignore) => isIgnoredOverlapPath(path10, ignore)));
120656
121703
  }
121704
+ function getUnmetSchedulingDependencies(task, tasks) {
121705
+ return task.dependencies.filter((depId) => {
121706
+ const dep = tasks.find((candidate) => candidate.id === depId);
121707
+ return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
121708
+ });
121709
+ }
121710
+ function isRunnableQueuedOverlapCandidate(task, tasks, now = Date.now(), activeScopes, scope = []) {
121711
+ if (task.column !== "todo" || task.status !== "queued") return false;
121712
+ if (task.paused || task.userPaused) return false;
121713
+ if (task.nextRecoveryAt && new Date(task.nextRecoveryAt).getTime() > now) return false;
121714
+ if (getUnmetSchedulingDependencies(task, tasks).length > 0) return false;
121715
+ if (!activeScopes || activeScopes.size === 0) return true;
121716
+ if (scope.length === 0) return true;
121717
+ for (const activeScope of activeScopes.values()) {
121718
+ if (!activeScope.length) continue;
121719
+ if (pathsOverlap2(scope, activeScope)) return false;
121720
+ }
121721
+ return true;
121722
+ }
120657
121723
  function findHigherPriorityQueuedOverlap(candidate, queuedScopes, overlap) {
120658
121724
  let higher = null;
120659
121725
  for (const queued of queuedScopes) {
@@ -121328,34 +122394,47 @@ var init_scheduler = __esm({
121328
122394
  );
121329
122395
  }
121330
122396
  const activeScopes = /* @__PURE__ */ new Map();
122397
+ const activeScopeColumns = /* @__PURE__ */ new Map();
122398
+ const setActiveScopeLease = (taskId, scope, column) => {
122399
+ activeScopes.set(taskId, scope);
122400
+ activeScopeColumns.set(taskId, column);
122401
+ };
121331
122402
  const inversionEmitted = /* @__PURE__ */ new Set();
121332
122403
  const queuedHigherPriorityScopes = [];
122404
+ const queuedHigherPriorityTaskById = /* @__PURE__ */ new Map();
122405
+ const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
122406
+ const filteredScopeByTaskId = /* @__PURE__ */ new Map();
122407
+ const getFilteredFileScope = async (taskId) => {
122408
+ const cached = filteredScopeByTaskId.get(taskId);
122409
+ if (cached) return cached;
122410
+ const scope = await this.store.parseFileScopeFromPrompt(taskId);
122411
+ const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
122412
+ filteredScopeByTaskId.set(taskId, filteredScope);
122413
+ return filteredScope;
122414
+ };
121333
122415
  if (settings.groupOverlappingFiles) {
121334
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
121335
122416
  for (const t of inProgress) {
121336
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121337
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121338
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
122417
+ const filteredScope = await getFilteredFileScope(t.id);
122418
+ if (filteredScope.length > 0) setActiveScopeLease(t.id, filteredScope, "in-progress");
122419
+ }
122420
+ const inReviewWithWorktree = tasks.filter(
122421
+ (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
122422
+ );
122423
+ for (const t of inReviewWithWorktree) {
122424
+ const filteredScope = await getFilteredFileScope(t.id);
122425
+ if (filteredScope.length > 0) setActiveScopeLease(t.id, filteredScope, "in-review");
121339
122426
  }
121340
122427
  for (const t of todo) {
121341
- if (t.status !== "queued" || t.paused || t.userPaused) continue;
121342
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121343
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
122428
+ const filteredScope = await getFilteredFileScope(t.id);
121344
122429
  if (filteredScope.length === 0) continue;
122430
+ if (!isRunnableQueuedOverlapCandidate(t, tasks, now, activeScopes, filteredScope)) continue;
121345
122431
  queuedHigherPriorityScopes.push({
121346
122432
  id: t.id,
121347
122433
  priority: t.priority,
121348
122434
  createdAt: t.createdAt,
121349
122435
  scope: filteredScope
121350
122436
  });
121351
- }
121352
- const inReviewWithWorktree = tasks.filter(
121353
- (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
121354
- );
121355
- for (const t of inReviewWithWorktree) {
121356
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121357
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121358
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
122437
+ queuedHigherPriorityTaskById.set(t.id, t);
121359
122438
  }
121360
122439
  }
121361
122440
  const ordered = resolveDependencyOrder(todo);
@@ -121376,10 +122455,7 @@ var init_scheduler = __esm({
121376
122455
  continue;
121377
122456
  }
121378
122457
  }
121379
- const unmetDeps = task.dependencies.filter((depId) => {
121380
- const dep = tasks.find((t) => t.id === depId);
121381
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
121382
- });
122458
+ const unmetDeps = getUnmetSchedulingDependencies(task, tasks);
121383
122459
  if (unmetDeps.length > 0) {
121384
122460
  await this.store.updateTask(task.id, {
121385
122461
  status: "queued",
@@ -121413,17 +122489,18 @@ var init_scheduler = __esm({
121413
122489
  continue;
121414
122490
  }
121415
122491
  if (settings.groupOverlappingFiles) {
121416
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
121417
- const taskScope = filterPathsByIgnoreList(
121418
- await this.store.parseFileScopeFromPrompt(task.id),
121419
- overlapIgnorePaths
121420
- );
122492
+ const taskScope = await getFilteredFileScope(task.id);
121421
122493
  if (taskScope.length > 0) {
121422
122494
  const activeScopeEntries = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId));
121423
122495
  const overlapBlockerId = task.overlapBlockedBy || task.blockedBy;
121424
122496
  const currentBlockerScope = overlapBlockerId ? activeScopes.get(overlapBlockerId) : void 0;
121425
122497
  const hasValidCurrentBlocker = Boolean(overlapBlockerId) && Boolean(currentBlockerScope) && this.pathsOverlap(taskScope, currentBlockerScope);
121426
122498
  const overlappingTaskId = hasValidCurrentBlocker ? overlapBlockerId : activeScopeEntries.find(([, ipScope]) => this.pathsOverlap(taskScope, ipScope))?.[0] ?? null;
122499
+ const runnableQueuedHigherPriorityScopes = queuedHigherPriorityScopes.filter((queuedCandidate) => {
122500
+ const queuedTask = queuedHigherPriorityTaskById.get(queuedCandidate.id);
122501
+ if (!queuedTask) return false;
122502
+ return isRunnableQueuedOverlapCandidate(queuedTask, tasks, now, activeScopes, queuedCandidate.scope);
122503
+ });
121427
122504
  const higherPriorityQueuedOverlap = findHigherPriorityQueuedOverlap(
121428
122505
  {
121429
122506
  id: task.id,
@@ -121431,7 +122508,7 @@ var init_scheduler = __esm({
121431
122508
  createdAt: task.createdAt,
121432
122509
  scope: taskScope
121433
122510
  },
121434
- queuedHigherPriorityScopes,
122511
+ runnableQueuedHigherPriorityScopes,
121435
122512
  this.pathsOverlap.bind(this)
121436
122513
  );
121437
122514
  if (higherPriorityQueuedOverlap) {
@@ -121446,7 +122523,7 @@ var init_scheduler = __esm({
121446
122523
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
121447
122524
  await this.logDispatchQueuedReason(
121448
122525
  task.id,
121449
- `queued \u2014 deferred for higher-priority queued task ${higherPriorityQueuedOverlap.id} (overlap)`
122526
+ `queued \u2014 deferred for higher-priority runnable queued task ${higherPriorityQueuedOverlap.id} (overlap)`
121450
122527
  );
121451
122528
  continue;
121452
122529
  }
@@ -121478,7 +122555,7 @@ var init_scheduler = __esm({
121478
122555
  blockerId: overlapBlockerTask.id,
121479
122556
  blockerPriority: overlapBlockerTask.priority ?? null,
121480
122557
  blockerCreatedAt: overlapBlockerTask.createdAt ?? null,
121481
- blockerColumn: overlapBlockerTask.column,
122558
+ blockerColumn: activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask.column,
121482
122559
  source: "scheduler.overlap-priority-inversion"
121483
122560
  }
121484
122561
  });
@@ -121489,7 +122566,11 @@ var init_scheduler = __esm({
121489
122566
  }
121490
122567
  }
121491
122568
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
121492
- await this.logDispatchQueuedReason(task.id, `queued \u2014 file scope overlap with ${overlappingTaskId}`);
122569
+ const activeLeaseColumn = activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask?.column ?? "unknown";
122570
+ await this.logDispatchQueuedReason(
122571
+ task.id,
122572
+ `queued \u2014 blocked by active file-scope lease ${overlappingTaskId} (column=${activeLeaseColumn})`
122573
+ );
121493
122574
  continue;
121494
122575
  }
121495
122576
  if (task.overlapBlockedBy) {
@@ -121708,11 +122789,8 @@ var init_scheduler = __esm({
121708
122789
  this.options.onSchedule?.(task);
121709
122790
  started++;
121710
122791
  if (settings.groupOverlappingFiles) {
121711
- const scope = filterPathsByIgnoreList(
121712
- await this.store.parseFileScopeFromPrompt(task.id),
121713
- settings.overlapIgnorePaths
121714
- );
121715
- if (scope.length > 0) activeScopes.set(task.id, scope);
122792
+ const scope = await getFilteredFileScope(task.id);
122793
+ if (scope.length > 0) setActiveScopeLease(task.id, scope, "in-progress");
121716
122794
  }
121717
122795
  }
121718
122796
  await this.emitHighOverlapFanoutWarnings(tasks);
@@ -124742,6 +125820,8 @@ var init_agent_heartbeat = __esm({
124742
125820
  init_agent_instructions();
124743
125821
  init_heartbeat_procedure_resolver();
124744
125822
  init_prompt_layers();
125823
+ init_goal_injection_diagnostics();
125824
+ init_goal_anchoring_audit();
124745
125825
  init_logger2();
124746
125826
  init_worktree_acquisition();
124747
125827
  init_run_audit();
@@ -126133,12 +127213,16 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126133
127213
  engineRunContext.taskId = taskId;
126134
127214
  }
126135
127215
  let autoClaimCandidates = [];
127216
+ let autoClaimSnapshotCandidateCount = 0;
127217
+ let autoClaimRoleFilteredCount = 0;
126136
127218
  const autoClaimEnabled = isAutoClaimRelevantTasksEnabled(agent);
126137
127219
  if (!taskId && canRunNoTaskHeartbeat && autoClaimEnabled && this.snapshotManager) {
126138
127220
  try {
126139
127221
  const snapshot = await this.snapshotManager.getSnapshot();
127222
+ autoClaimSnapshotCandidateCount = snapshot.tasks.length;
126140
127223
  const roleCompatibleCandidates = snapshot.tasks.filter((candidate) => canAgentTakeImplementationTask(agent, candidate));
126141
127224
  const skippedIncompatibleCount = snapshot.tasks.length - roleCompatibleCandidates.length;
127225
+ autoClaimRoleFilteredCount = skippedIncompatibleCount;
126142
127226
  if (skippedIncompatibleCount > 0) {
126143
127227
  heartbeatLog.log(
126144
127228
  `Agent ${agentId} (role=${agent.role}) skipped auto-claim of ${skippedIncompatibleCount} implementation task(s) \u2014 only executor agents may claim implementation work`
@@ -126394,8 +127478,29 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126394
127478
  if (heartbeatPluginContributions) {
126395
127479
  heartbeatLog.log(`applied plugin prompt contributions for heartbeat surface`);
126396
127480
  }
127481
+ const heartbeatGoalResolution = resolveGoalContextForDiagnostics({
127482
+ listActiveGoals: typeof taskStore.getGoalStore === "function" ? () => taskStore.getGoalStore().listGoals({ status: "active" }) : void 0
127483
+ });
127484
+ const heartbeatGoalContext = heartbeatGoalResolution.goalContext;
127485
+ const heartbeatGoalClassification = heartbeatGoalResolution.classification;
127486
+ await emitGoalAnchoringAudit(audit, {
127487
+ lane: "heartbeat",
127488
+ taskId,
127489
+ goalsInjected: heartbeatGoalClassification.goalCount,
127490
+ truncated: heartbeatGoalClassification.truncated,
127491
+ reason: heartbeatGoalClassification.outcome === "no-goals" ? "no-active-goals" : void 0
127492
+ });
127493
+ await emitGoalInjectionDiagnostic({
127494
+ lane: "heartbeat",
127495
+ ...heartbeatGoalClassification,
127496
+ runId: run2.id,
127497
+ agentId,
127498
+ store: taskStore,
127499
+ runContext: engineRunContext
127500
+ });
126397
127501
  const heartbeatLayers = buildPromptLayers({
126398
127502
  basePrompt: baseHeartbeatSystemPrompt,
127503
+ goalContext: heartbeatGoalContext,
126399
127504
  agentInstructions: [resolvedInstructionsText, memoryInstructions, selfImprovePrompt].filter((part) => part.trim()).join("\n\n"),
126400
127505
  pluginContributions: heartbeatPluginContributions
126401
127506
  });
@@ -126670,10 +127775,15 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126670
127775
  );
126671
127776
  }
126672
127777
  const promptCandidateLimit = resolveAutoClaimCandidatesInPromptLimit(agent, heartbeatModelSettings);
127778
+ const autoClaimStatus = autoClaimEnabled ? promptCandidateLimit === 0 ? "disabled (prompt-suppressed)" : autoClaimCandidates.length === 0 && autoClaimSnapshotCandidateCount > 0 && autoClaimRoleFilteredCount > 0 ? "enabled (no role-compatible candidates; executor role required)" : "enabled" : "disabled";
127779
+ const noRoleCompatibleCandidateLines = autoClaimCandidates.length === 0 && autoClaimSnapshotCandidateCount > 0 && autoClaimRoleFilteredCount > 0 ? [
127780
+ `- Snapshot found ${autoClaimSnapshotCandidateCount} eligible Todo task(s), but this agent role cannot auto-claim implementation work.`,
127781
+ "- Backlog auto-claim is restricted to executor-role agents; use delegation or create coordination follow-up instead of assuming the board is empty."
127782
+ ] : [];
126673
127783
  const candidateLines = promptCandidateLimit > 0 ? [
126674
127784
  "",
126675
127785
  "Open Task Candidates (auto-claim scan):",
126676
- ...autoClaimCandidates.slice(0, promptCandidateLimit).map((candidate) => `- ${candidate.id}: ${candidate.title ?? candidate.descriptionFirstLine}`)
127786
+ ...autoClaimCandidates.length > 0 ? autoClaimCandidates.slice(0, promptCandidateLimit).map((candidate) => `- ${candidate.id}: ${candidate.title ?? candidate.descriptionFirstLine}`) : noRoleCompatibleCandidateLines
126677
127787
  ] : [];
126678
127788
  executionPrompt = [
126679
127789
  `Heartbeat execution for agent "${agent.name}" (ID: ${agent.id})`,
@@ -126689,7 +127799,7 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126689
127799
  ...wakeTriggerSourceLine ? [wakeTriggerSourceLine] : [],
126690
127800
  `- pending messages: ${pendingMessages.length}`,
126691
127801
  `- pending room messages: ${pendingRoomMessages.total}`,
126692
- `- auto-claim relevant tasks: ${autoClaimEnabled ? promptCandidateLimit === 0 ? "disabled (prompt-suppressed)" : "enabled" : "disabled"}`,
127802
+ `- auto-claim relevant tasks: ${autoClaimStatus}`,
126693
127803
  "",
126694
127804
  "Treat this wake delta as the highest-priority change for this heartbeat.",
126695
127805
  "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
@@ -127922,78 +129032,6 @@ var init_error_classifier = __esm({
127922
129032
  }
127923
129033
  });
127924
129034
 
127925
- // ../engine/src/goal-context-injector.ts
127926
- function normalizeTitle(title) {
127927
- return title.replace(/\s*\n+\s*/g, " ").trim();
127928
- }
127929
- function renderGoalContext(goals) {
127930
- const lines = goals.map((goal) => `- ${goal.id}: ${normalizeTitle(goal.title)}`);
127931
- return `## Active Goals
127932
-
127933
- ${lines.join("\n")}`;
127934
- }
127935
- function buildGoalContextSection(input) {
127936
- const { activeGoals, onTruncated } = input;
127937
- if (activeGoals.length === 0) {
127938
- return { text: "", emittedGoalIds: [], truncated: null };
127939
- }
127940
- const sortedGoals = [...activeGoals].sort((a, b2) => {
127941
- const byCreated = a.createdAt.localeCompare(b2.createdAt);
127942
- if (byCreated !== 0) {
127943
- return byCreated;
127944
- }
127945
- return a.id.localeCompare(b2.id);
127946
- });
127947
- const resolvedMaxGoals = Math.min(input.maxGoals ?? MAX_INJECTED_GOALS, MAX_INJECTED_GOALS);
127948
- const charBudget = input.charBudget ?? DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
127949
- const droppedGoalIds = [];
127950
- let selectedGoals = sortedGoals.slice(0, resolvedMaxGoals);
127951
- if (sortedGoals.length > resolvedMaxGoals) {
127952
- droppedGoalIds.push(...sortedGoals.slice(resolvedMaxGoals).map((goal) => goal.id));
127953
- }
127954
- let text = renderGoalContext(selectedGoals);
127955
- let usedBudgetDrop = false;
127956
- while (selectedGoals.length > 1 && text.length > charBudget) {
127957
- const dropped = selectedGoals[selectedGoals.length - 1];
127958
- selectedGoals = selectedGoals.slice(0, -1);
127959
- droppedGoalIds.push(dropped.id);
127960
- text = renderGoalContext(selectedGoals);
127961
- usedBudgetDrop = true;
127962
- }
127963
- let truncated = null;
127964
- if (droppedGoalIds.length > 0) {
127965
- truncated = {
127966
- reason: usedBudgetDrop ? "budget" : "cap",
127967
- requested: activeGoals.length,
127968
- emitted: selectedGoals.length,
127969
- droppedGoalIds,
127970
- charBudget,
127971
- producedChars: text.length
127972
- };
127973
- onTruncated?.(truncated);
127974
- }
127975
- return {
127976
- text,
127977
- emittedGoalIds: selectedGoals.map((goal) => goal.id),
127978
- truncated
127979
- };
127980
- }
127981
- var MAX_INJECTED_GOALS, resolvedActiveGoalLimit, DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
127982
- var init_goal_context_injector = __esm({
127983
- "../engine/src/goal-context-injector.ts"() {
127984
- "use strict";
127985
- init_src();
127986
- MAX_INJECTED_GOALS = 5;
127987
- resolvedActiveGoalLimit = "ACTIVE_GOAL_LIMIT" in src_exports && typeof ACTIVE_GOAL_LIMIT === "number" ? ACTIVE_GOAL_LIMIT : MAX_INJECTED_GOALS;
127988
- if (MAX_INJECTED_GOALS > resolvedActiveGoalLimit) {
127989
- throw new Error(
127990
- `MAX_INJECTED_GOALS (${MAX_INJECTED_GOALS}) cannot exceed ACTIVE_GOAL_LIMIT (${resolvedActiveGoalLimit})`
127991
- );
127992
- }
127993
- DEFAULT_GOAL_INJECTION_CHAR_BUDGET = 600;
127994
- }
127995
- });
127996
-
127997
129035
  // ../engine/src/experiment/benchmark-runner.ts
127998
129036
  import { spawn as spawn7 } from "node:child_process";
127999
129037
  import { mkdtemp, writeFile as writeFile17 } from "node:fs/promises";
@@ -133912,7 +134950,8 @@ var init_in_process_runtime = __esm({
133912
134950
  this.usageLimitPauser = pauser;
133913
134951
  }
133914
134952
  /**
133915
- * Set up event forwarding from TaskStore to runtime listeners.
134953
+ * Set up event forwarding from TaskStore to runtime listeners
134954
+ * for task:created, task:moved, task:updated, and task:deleted.
133916
134955
  */
133917
134956
  setupEventForwarding() {
133918
134957
  this.taskStore.on("task:created", (task) => {
@@ -133927,6 +134966,10 @@ var init_in_process_runtime = __esm({
133927
134966
  this.recordActivity();
133928
134967
  this.emit("task:updated", task);
133929
134968
  });
134969
+ this.taskStore.on("task:deleted", (task, meta) => {
134970
+ this.recordActivity();
134971
+ this.emit("task:deleted", task, meta);
134972
+ });
133930
134973
  runtimeLog.log("Event forwarding setup complete");
133931
134974
  }
133932
134975
  /**
@@ -133977,7 +135020,7 @@ var init_in_process_runtime = __esm({
133977
135020
  function generateCorrelationId() {
133978
135021
  return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
133979
135022
  }
133980
- var START_RUNTIME, STOP_RUNTIME, GET_METRICS, OK, ERROR, PONG, TASK_CREATED, TASK_MOVED, TASK_UPDATED, ERROR_EVENT, HEALTH_CHANGED;
135023
+ var START_RUNTIME, STOP_RUNTIME, GET_METRICS, OK, ERROR, PONG, TASK_CREATED, TASK_MOVED, TASK_UPDATED, TASK_DELETED, ERROR_EVENT, HEALTH_CHANGED;
133981
135024
  var init_ipc_protocol = __esm({
133982
135025
  "../engine/src/ipc/ipc-protocol.ts"() {
133983
135026
  "use strict";
@@ -133990,6 +135033,7 @@ var init_ipc_protocol = __esm({
133990
135033
  TASK_CREATED = "TASK_CREATED";
133991
135034
  TASK_MOVED = "TASK_MOVED";
133992
135035
  TASK_UPDATED = "TASK_UPDATED";
135036
+ TASK_DELETED = "TASK_DELETED";
133993
135037
  ERROR_EVENT = "ERROR_EVENT";
133994
135038
  HEALTH_CHANGED = "HEALTH_CHANGED";
133995
135039
  }
@@ -134374,6 +135418,9 @@ var init_child_process_runtime = __esm({
134374
135418
  this.ipcHost.on(TASK_UPDATED, (payload) => {
134375
135419
  this.emit("task:updated", payload.task);
134376
135420
  });
135421
+ this.ipcHost.on(TASK_DELETED, (payload) => {
135422
+ this.emit("task:deleted", payload.task, payload.meta);
135423
+ });
134377
135424
  this.ipcHost.on(ERROR_EVENT, (payload) => {
134378
135425
  const error = new Error(payload.message);
134379
135426
  if (payload.code) {
@@ -135496,6 +136543,9 @@ var init_project_manager = __esm({
135496
136543
  runtime.on("task:updated", (task) => {
135497
136544
  this.emit("task:updated", { projectId, projectName, task });
135498
136545
  });
136546
+ runtime.on("task:deleted", (task, meta) => {
136547
+ this.emit("task:deleted", { projectId, projectName, task, meta });
136548
+ });
135499
136549
  runtime.on("error", (error) => {
135500
136550
  this.emit("error", { projectId, projectName, error });
135501
136551
  this.logActivity(
@@ -137174,6 +138224,7 @@ var init_project_engine = __esm({
137174
138224
  init_research_step_runner();
137175
138225
  init_run_audit();
137176
138226
  init_verification_followup_dedup();
138227
+ init_transient_error_detector();
137177
138228
  init_tunnel_process_manager();
137178
138229
  execFileAsync12 = promisify45(execFile17);
137179
138230
  MERGE_HANDOFF_GRACE_MS = 300;
@@ -137244,6 +138295,10 @@ var init_project_engine = __esm({
137244
138295
  manualMergeResolvers = /* @__PURE__ */ new Map();
137245
138296
  shuttingDown = false;
137246
138297
  static MAX_AUTO_MERGE_RETRIES = 3;
138298
+ /** FN-5697/FN-5674: cap transient provider/network abort retries in auto-merge.
138299
+ * Examples: "This operation was aborted", "socket hang up", `server_error`.
138300
+ * After this cap, the task is parked failed for human visibility. */
138301
+ static MAX_AUTO_MERGE_TRANSIENT_RETRIES = 3;
137247
138302
  /** Cap on outer in-review→in-progress bounces caused by deterministic
137248
138303
  * verification failures during auto-merge. After this many failed merges
137249
138304
  * for the same task, we stop bouncing it back, mark it failed, and create
@@ -138226,7 +139281,15 @@ ${detail}`
138226
139281
  this.internalEnqueueMerge(taskId);
138227
139282
  continue;
138228
139283
  }
138229
- const blockerReason = getTaskHardMergeBlocker(task);
139284
+ const blockerReason = getTaskHardMergeBlocker({
139285
+ ...task,
139286
+ // Merge-confirmed tasks have already landed. Treat stale merge
139287
+ // in-flight statuses as soft state to clear during finalization,
139288
+ // not hard blockers that park an otherwise confirmed merge as failed.
139289
+ paused: false,
139290
+ status: task.status === "merging" || task.status === "merging-pr" ? void 0 : task.status,
139291
+ error: void 0
139292
+ });
138230
139293
  if (blockerReason) {
138231
139294
  await store.updateTask(taskId, {
138232
139295
  status: "failed",
@@ -138745,6 +139808,16 @@ ${detail}`
138745
139808
  }
138746
139809
  } else {
138747
139810
  try {
139811
+ if (await this.maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg)) {
139812
+ continue;
139813
+ }
139814
+ if (this.isTransientMergeRetryExhausted(taskOnErr, errorMsg)) {
139815
+ await store.logEntry(
139816
+ taskId,
139817
+ `Auto-merge transient retries exhausted (${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}); parking task as failed: ${errorMsg}`,
139818
+ "MergeTransientRetryExhausted"
139819
+ );
139820
+ }
138748
139821
  await store.updateTask(taskId, {
138749
139822
  status: "failed",
138750
139823
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -138763,6 +139836,16 @@ ${detail}`
138763
139836
  }
138764
139837
  } else {
138765
139838
  try {
139839
+ if (await this.maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg)) {
139840
+ continue;
139841
+ }
139842
+ if (this.isTransientMergeRetryExhausted(taskOnErr, errorMsg)) {
139843
+ await store.logEntry(
139844
+ taskId,
139845
+ `Auto-merge transient retries exhausted (${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}); parking task as failed: ${errorMsg}`,
139846
+ "MergeTransientRetryExhausted"
139847
+ );
139848
+ }
138766
139849
  await store.updateTask(taskId, {
138767
139850
  status: "failed",
138768
139851
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -138798,6 +139881,40 @@ ${detail}`
138798
139881
  this.mergeRunning = false;
138799
139882
  }
138800
139883
  }
139884
+ isTransientMergeRetryExhausted(task, errorMsg) {
139885
+ if (!task || !isTransientError(errorMsg)) {
139886
+ return false;
139887
+ }
139888
+ const current = task.mergeTransientRetryCount ?? 0;
139889
+ return current >= _ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES;
139890
+ }
139891
+ async maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg) {
139892
+ if (!taskOnErr || !isTransientError(errorMsg)) {
139893
+ return false;
139894
+ }
139895
+ const currentRetries = taskOnErr.mergeTransientRetryCount ?? 0;
139896
+ if (currentRetries >= _ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES) {
139897
+ return false;
139898
+ }
139899
+ const nextRetryCount = currentRetries + 1;
139900
+ const delayMs = 5e3 * Math.pow(2, currentRetries);
139901
+ await store.updateTask(taskId, {
139902
+ mergeTransientRetryCount: nextRetryCount,
139903
+ status: null
139904
+ });
139905
+ await store.logEntry(
139906
+ taskId,
139907
+ `Auto-merge transient retry ${nextRetryCount}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES} scheduled in ${delayMs / 1e3}s: ${errorMsg}`,
139908
+ "MergeTransientRetry"
139909
+ );
139910
+ runtimeLog.log(
139911
+ `Auto-merge transient retry ${nextRetryCount}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES} for ${taskId} in ${delayMs / 1e3}s`
139912
+ );
139913
+ setTimeout(() => {
139914
+ if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
139915
+ }, delayMs);
139916
+ return true;
139917
+ }
138801
139918
  wireAutoMerge(store, _cwd) {
138802
139919
  this.taskMovedHandler = async ({ task, to: to2 }) => {
138803
139920
  if (to2 !== "in-review") return;
@@ -142362,6 +143479,9 @@ __export(src_exports2, {
142362
143479
  ExperimentGitNotConfiguredError: () => ExperimentGitNotConfiguredError,
142363
143480
  ExperimentMaxIterationsError: () => ExperimentMaxIterationsError,
142364
143481
  ExperimentRevertConflictError: () => ExperimentRevertConflictError,
143482
+ GOAL_INJECTION_APPLIED: () => GOAL_INJECTION_APPLIED,
143483
+ GOAL_INJECTION_SKIPPED: () => GOAL_INJECTION_SKIPPED,
143484
+ GOAL_RETRIEVAL_INVOKED: () => GOAL_RETRIEVAL_INVOKED,
142365
143485
  GitHubProvider: () => GitHubProvider,
142366
143486
  HEARTBEAT_NO_TASK_SYSTEM_PROMPT: () => HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
142367
143487
  HEARTBEAT_PROCEDURE: () => HEARTBEAT_PROCEDURE,
@@ -142453,6 +143573,8 @@ __export(src_exports2, {
142453
143573
  buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
142454
143574
  buildSystemPromptWithInstructions: () => buildSystemPromptWithInstructions,
142455
143575
  classifyBootstrapMisbinding: () => classifyBootstrapMisbinding,
143576
+ classifyGoalInjectionFailure: () => classifyGoalInjectionFailure,
143577
+ classifyGoalInjectionResult: () => classifyGoalInjectionResult,
142456
143578
  classifyTaskError: () => classifyTaskError,
142457
143579
  cleanupOrphanedWorktrees: () => cleanupOrphanedWorktrees,
142458
143580
  cleanupSecretsEnvFile: () => cleanupSecretsEnvFile,
@@ -142477,6 +143599,9 @@ __export(src_exports2, {
142477
143599
  describeModel: () => describeModel,
142478
143600
  dropAutostashBySha: () => dropAutostashBySha,
142479
143601
  dropAutostashHandle: () => dropAutostashHandle,
143602
+ emitGoalAnchoringAudit: () => emitGoalAnchoringAudit,
143603
+ emitGoalInjectionDiagnostic: () => emitGoalInjectionDiagnostic,
143604
+ emitGoalRetrievalAudit: () => emitGoalRetrievalAudit,
142480
143605
  ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
142481
143606
  executeApprovedAgentProvisioning: () => executeApprovedAgentProvisioning,
142482
143607
  executeApprovedWorktrunkInstall: () => executeApprovedWorktrunkInstall,
@@ -142592,6 +143717,8 @@ var init_src2 = __esm({
142592
143717
  init_web_fetch();
142593
143718
  init_error_classifier();
142594
143719
  init_goal_context_injector();
143720
+ init_goal_injection_diagnostics();
143721
+ init_goal_anchoring_audit();
142595
143722
  init_worktrunk_installer();
142596
143723
  init_worktrunk_failure_handler();
142597
143724
  init_usage_limit_detector();
@@ -157632,6 +158759,26 @@ var init_register_git_github = __esm({
157632
158759
  });
157633
158760
 
157634
158761
  // ../dashboard/src/routes/branch-selection.ts
158762
+ function getBranchSelectionMode(selectionInput) {
158763
+ if (selectionInput === void 0 || selectionInput === null) return void 0;
158764
+ if (typeof selectionInput !== "object" || Array.isArray(selectionInput)) {
158765
+ throw badRequest("branchSelection must be an object");
158766
+ }
158767
+ const selection = selectionInput;
158768
+ const mode = typeof selection.mode === "string" ? selection.mode : void 0;
158769
+ if (!mode) {
158770
+ throw badRequest("branchSelection.mode is required");
158771
+ }
158772
+ if (![
158773
+ "project-default",
158774
+ "auto-new",
158775
+ "existing",
158776
+ "custom-new"
158777
+ ].includes(mode)) {
158778
+ throw badRequest("branchSelection.mode must be one of: project-default, auto-new, existing, custom-new");
158779
+ }
158780
+ return mode;
158781
+ }
157635
158782
  function normalizeOptionalBranch(value, fieldName) {
157636
158783
  if (value === void 0 || value === null) return void 0;
157637
158784
  if (typeof value !== "string") {
@@ -157648,22 +158795,8 @@ function resolveBranchSelection(selectionInput, fallbackBranch, fallbackBaseBran
157648
158795
  if (selectionInput === void 0 || selectionInput === null) {
157649
158796
  return fallback2;
157650
158797
  }
157651
- if (typeof selectionInput !== "object" || Array.isArray(selectionInput)) {
157652
- throw badRequest("branchSelection must be an object");
157653
- }
158798
+ const mode = getBranchSelectionMode(selectionInput);
157654
158799
  const selection = selectionInput;
157655
- const mode = typeof selection.mode === "string" ? selection.mode : void 0;
157656
- if (!mode) {
157657
- throw badRequest("branchSelection.mode is required");
157658
- }
157659
- if (![
157660
- "project-default",
157661
- "auto-new",
157662
- "existing",
157663
- "custom-new"
157664
- ].includes(mode)) {
157665
- throw badRequest("branchSelection.mode must be one of: project-default, auto-new, existing, custom-new");
157666
- }
157667
158800
  const baseBranch = normalizeOptionalBranch(selection.baseBranch, "branchSelection.baseBranch");
157668
158801
  if (mode === "project-default") {
157669
158802
  return { branch: void 0, baseBranch };
@@ -157699,6 +158832,11 @@ function resolveBranchAssignmentContext(input) {
157699
158832
  function sanitizeSegment(input) {
157700
158833
  return input.trim().toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-/.]+|[-/.]+$/g, "").slice(0, 48);
157701
158834
  }
158835
+ function deriveAutoTaskBranch(taskId, shortName) {
158836
+ const base = `fusion/${taskId.toLowerCase()}`;
158837
+ const segment = sanitizeSegment(shortName ?? "");
158838
+ return segment ? `${base}-${segment}` : base;
158839
+ }
157702
158840
  function derivePerTaskBranch(sharedBranch, taskSegment) {
157703
158841
  const base = normalizeOptionalBranch(sharedBranch, "sharedBranch");
157704
158842
  if (!base) return void 0;
@@ -158338,6 +159476,7 @@ function registerTaskWorkflowRoutes(ctx, deps) {
158338
159476
  }
158339
159477
  } : void 0;
158340
159478
  const normalizedSource = source && typeof source === "object" && "sourceType" in source && typeof source.sourceType === "string" ? source : { sourceType: "api" };
159479
+ const requestedBranchMode = getBranchSelectionMode(branchSelection);
158341
159480
  const { branch: normalizedBranch, baseBranch: normalizedBaseBranch } = resolveBranchSelection(branchSelection, branch, baseBranch);
158342
159481
  let validatedGithubTracking;
158343
159482
  if (githubTracking !== void 0 && githubTracking !== null) {
@@ -158594,8 +159733,14 @@ ${normalizedDescription}`;
158594
159733
  createInput,
158595
159734
  { onSummarize, settings: { autoSummarizeTitles: settings.autoSummarizeTitles } }
158596
159735
  );
159736
+ const taskWithAutoBranch = requestedBranchMode === "auto-new" ? await scopedStore.updateTask(task.id, {
159737
+ branch: deriveAutoTaskBranch(
159738
+ task.id,
159739
+ ((task.title ?? "").trim() || task.description).slice(0, 60)
159740
+ )
159741
+ }) : task;
158597
159742
  const deterministicReconcile = await reconcileDeterministicDuplicate(scopedStore, {
158598
- createdTask: task,
159743
+ createdTask: taskWithAutoBranch,
158599
159744
  fingerprint: bypassDuplicateCheck === true ? null : contentFingerprint,
158600
159745
  windowMs: 6e4,
158601
159746
  logger: runtimeLogger
@@ -158608,8 +159753,8 @@ ${normalizedDescription}`;
158608
159753
  try {
158609
159754
  await scopedStore.recordActivity({
158610
159755
  type: "task:duplicate-warning-overridden",
158611
- taskId: task.id,
158612
- taskTitle: task.title,
159756
+ taskId: taskWithAutoBranch.id,
159757
+ taskTitle: taskWithAutoBranch.title,
158613
159758
  details: `Created despite ${acknowledgedDuplicateIds.length} possible duplicate(s): ${acknowledgedDuplicateIds.join(", ")}`,
158614
159759
  metadata: {
158615
159760
  acknowledgedDuplicateIds,
@@ -158623,7 +159768,7 @@ ${normalizedDescription}`;
158623
159768
  });
158624
159769
  }
158625
159770
  }
158626
- res.status(201).json(task);
159771
+ res.status(201).json(taskWithAutoBranch);
158627
159772
  return;
158628
159773
  } finally {
158629
159774
  deterministicGuard.releaseLock();
@@ -159751,7 +160896,7 @@ ${normalizedDescription}`;
159751
160896
  router.patch("/tasks/:id", async (req, res) => {
159752
160897
  try {
159753
160898
  const { store: scopedStore } = await getProjectContext3(req);
159754
- const { title, description, prompt, priority, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode, sourceIssue, nodeId, branch, baseBranch, githubTracking, noCommitsExpected, overlapBlockedBy, status } = req.body;
160899
+ const { title, description, prompt, priority, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode, sourceIssue, nodeId, branch, baseBranch, githubTracking, noCommitsExpected, autoMerge, overlapBlockedBy, status } = req.body;
159755
160900
  const hasBodyField = (field) => Object.prototype.hasOwnProperty.call(req.body, field);
159756
160901
  const validateModelField = (value, name) => {
159757
160902
  if (value === void 0) return void 0;
@@ -159792,6 +160937,9 @@ ${normalizedDescription}`;
159792
160937
  if (hasBodyField("noCommitsExpected") && noCommitsExpected !== void 0 && typeof noCommitsExpected !== "boolean") {
159793
160938
  throw new Error("noCommitsExpected must be a boolean");
159794
160939
  }
160940
+ if (hasBodyField("autoMerge") && autoMerge !== void 0 && autoMerge !== null && typeof autoMerge !== "boolean") {
160941
+ throw new Error("autoMerge must be a boolean");
160942
+ }
159795
160943
  let validatedSourceIssue;
159796
160944
  if (hasBodyField("sourceIssue")) {
159797
160945
  if (sourceIssue === null) {
@@ -159913,6 +161061,7 @@ ${normalizedDescription}`;
159913
161061
  if (dependencies !== void 0) updates.dependencies = dependencies;
159914
161062
  if (enabledWorkflowSteps !== void 0) updates.enabledWorkflowSteps = enabledWorkflowSteps;
159915
161063
  if (hasBodyField("noCommitsExpected")) updates.noCommitsExpected = noCommitsExpected;
161064
+ if (hasBodyField("autoMerge")) updates.autoMerge = autoMerge === null ? void 0 : autoMerge;
159916
161065
  if (hasBodyField("modelProvider")) updates.modelProvider = validatedModelProvider;
159917
161066
  if (hasBodyField("modelId")) updates.modelId = validatedModelId;
159918
161067
  if (hasBodyField("validatorModelProvider")) updates.validatorModelProvider = validatedValidatorModelProvider;
@@ -198181,15 +199330,35 @@ async function resolveAuditCommitSha(taskId, scopedStore) {
198181
199330
  }
198182
199331
  return void 0;
198183
199332
  }
198184
- async function resolveDoneTaskMergeSha(task, scopedStore) {
199333
+ async function isAtOrBelowTaskBase(sha, baseCommitSha, rootDir) {
199334
+ if (sha === baseCommitSha) return true;
199335
+ try {
199336
+ await runGitCommand(["merge-base", "--is-ancestor", sha, baseCommitSha], rootDir, 5e3);
199337
+ return true;
199338
+ } catch {
199339
+ return false;
199340
+ }
199341
+ }
199342
+ async function resolveDoneTaskMergeSha(task, scopedStore, options) {
199343
+ const rootDir = scopedStore.getRootDir();
199344
+ const baseCommitSha = task.baseCommitSha?.trim();
199345
+ const includeBaseCommitSha = options?.includeBaseCommitSha === true;
198185
199346
  const existing = task.mergeDetails?.commitSha?.trim();
198186
- if (existing) return existing;
199347
+ if (existing) {
199348
+ if (!includeBaseCommitSha && baseCommitSha && existing === baseCommitSha) return void 0;
199349
+ return existing;
199350
+ }
198187
199351
  const auditSha = await resolveAuditCommitSha(task.id, scopedStore);
198188
- if (auditSha) return auditSha;
199352
+ if (auditSha) {
199353
+ if (!includeBaseCommitSha && baseCommitSha && auditSha === baseCommitSha) return void 0;
199354
+ return auditSha;
199355
+ }
198189
199356
  if (!task.lineageId) return void 0;
198190
199357
  const associations = await scopedStore.getTaskCommitAssociationsByLineageId(task.lineageId);
198191
199358
  for (const association of associations) {
198192
- if (association.commitSha && await isReachableFromHead(association.commitSha, scopedStore.getRootDir())) {
199359
+ if (!association.commitSha) continue;
199360
+ if (!includeBaseCommitSha && baseCommitSha && association.commitSha === baseCommitSha) continue;
199361
+ if (await isReachableFromHead(association.commitSha, rootDir)) {
198193
199362
  return association.commitSha;
198194
199363
  }
198195
199364
  }
@@ -198408,7 +199577,15 @@ function registerSessionDiffRoutes(router, deps) {
198408
199577
  return;
198409
199578
  }
198410
199579
  if (task.column === "done") {
199580
+ const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
198411
199581
  const resolvedMergeSha = await resolveDoneTaskMergeSha(task, scopedStore);
199582
+ if (mergeShaForBaseBoundary && task.baseCommitSha) {
199583
+ const atOrBelowBase = await isAtOrBelowTaskBase(mergeShaForBaseBoundary, task.baseCommitSha, scopedStore.getRootDir());
199584
+ if (atOrBelowBase) {
199585
+ res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
199586
+ return;
199587
+ }
199588
+ }
198412
199589
  const doneTaskForDiff = resolvedMergeSha ? {
198413
199590
  ...task,
198414
199591
  mergeDetails: {
@@ -198619,7 +199796,15 @@ function registerSessionDiffRoutes(router, deps) {
198619
199796
  return;
198620
199797
  }
198621
199798
  if (task.column === "done") {
199799
+ const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
198622
199800
  const resolvedMergeSha = await resolveDoneTaskMergeSha(task, scopedStore);
199801
+ if (mergeShaForBaseBoundary && task.baseCommitSha) {
199802
+ const atOrBelowBase = await isAtOrBelowTaskBase(mergeShaForBaseBoundary, task.baseCommitSha, scopedStore.getRootDir());
199803
+ if (atOrBelowBase) {
199804
+ res.json([]);
199805
+ return;
199806
+ }
199807
+ }
198623
199808
  const doneTaskForDiff = resolvedMergeSha ? {
198624
199809
  ...task,
198625
199810
  mergeDetails: {
@@ -215093,21 +216278,48 @@ var init_register_auth_routes = __esm({
215093
216278
  let autoPromptConsumed = false;
215094
216279
  let authResolve;
215095
216280
  let authReject;
216281
+ let authSettled = false;
215096
216282
  const authUrlPromise = new Promise((resolve64, reject2) => {
215097
216283
  authResolve = resolve64;
215098
216284
  authReject = reject2;
215099
216285
  });
216286
+ const resolveAuthInfo = (info) => {
216287
+ if (authSettled) return;
216288
+ authSettled = true;
216289
+ authResolve(info);
216290
+ };
216291
+ const rejectAuthInfo = (err) => {
216292
+ if (authSettled) return;
216293
+ authSettled = true;
216294
+ authReject(err);
216295
+ };
216296
+ let resolvedDeviceCode;
215100
216297
  const loginPromise = storage.login(provider, {
215101
216298
  onAuth: (info) => {
215102
- const parsedUserCode = provider === "github-copilot" && info.instructions ? parseGitHubCopilotDeviceCode(info.instructions) : void 0;
215103
- const deviceCode = parsedUserCode ? {
215104
- userCode: parsedUserCode,
215105
- verificationUri: info.url
215106
- } : void 0;
215107
- authResolve({
216299
+ if (!resolvedDeviceCode) {
216300
+ const parsedUserCode = provider === "github-copilot" && info.instructions ? parseGitHubCopilotDeviceCode(info.instructions) : void 0;
216301
+ if (parsedUserCode) {
216302
+ resolvedDeviceCode = {
216303
+ userCode: parsedUserCode,
216304
+ verificationUri: info.url
216305
+ };
216306
+ }
216307
+ }
216308
+ resolveAuthInfo({
215108
216309
  url: info.url,
215109
216310
  instructions: appendManualCodeHint(info.instructions, provider, origin),
215110
- deviceCode
216311
+ deviceCode: resolvedDeviceCode
216312
+ });
216313
+ },
216314
+ onDeviceCode: (info) => {
216315
+ resolvedDeviceCode = {
216316
+ userCode: info.userCode,
216317
+ verificationUri: info.verificationUri
216318
+ };
216319
+ resolveAuthInfo({
216320
+ url: info.verificationUri,
216321
+ instructions: appendManualCodeHint(void 0, provider, origin),
216322
+ deviceCode: resolvedDeviceCode
215111
216323
  });
215112
216324
  },
215113
216325
  onPrompt: async (_prompt) => {
@@ -215124,14 +216336,20 @@ var init_register_auth_routes = __esm({
215124
216336
  onProgress: () => {
215125
216337
  },
215126
216338
  // no-op for web UI
216339
+ onSelect: async (prompt) => {
216340
+ if (prompt.options.length === 1) {
216341
+ return prompt.options[0]?.id;
216342
+ }
216343
+ return void 0;
216344
+ },
215127
216345
  signal: abortController.signal
215128
216346
  });
215129
216347
  const timeout2 = setTimeout(() => {
215130
- authReject(new Error("Login initiation timed out"));
216348
+ rejectAuthInfo(new Error("Login initiation timed out"));
215131
216349
  }, 3e4);
215132
216350
  loginPromise.then(() => {
215133
216351
  }).catch((err) => {
215134
- authReject(err);
216352
+ rejectAuthInfo(err instanceof Error ? err : new Error(String(err)));
215135
216353
  }).finally(() => {
215136
216354
  clearTimeout(timeout2);
215137
216355
  loginInProgress.delete(provider);
@@ -216048,6 +217266,32 @@ function validateBoolean(value, fieldName) {
216048
217266
  }
216049
217267
  return value;
216050
217268
  }
217269
+ function validateMissionBranchStrategy(value) {
217270
+ if (value === void 0 || value === null) return void 0;
217271
+ if (typeof value !== "object") {
217272
+ throw new Error("branchStrategy must be an object");
217273
+ }
217274
+ const input = value;
217275
+ const mode = input.mode;
217276
+ if (mode !== "project-default" && mode !== "existing" && mode !== "custom-new" && mode !== "auto-per-task") {
217277
+ throw new Error("branchStrategy.mode must be one of: project-default, existing, custom-new, auto-per-task");
217278
+ }
217279
+ const branchName = input.branchName;
217280
+ if (branchName !== void 0 && typeof branchName !== "string") {
217281
+ throw new Error("branchStrategy.branchName must be a string when provided");
217282
+ }
217283
+ const trimmedBranchName = branchName?.trim();
217284
+ if ((mode === "existing" || mode === "custom-new") && !trimmedBranchName) {
217285
+ throw new Error("branchStrategy.branchName is required for existing/custom-new");
217286
+ }
217287
+ if (mode === "project-default" || mode === "auto-per-task") {
217288
+ return { mode };
217289
+ }
217290
+ return {
217291
+ mode,
217292
+ branchName: trimmedBranchName
217293
+ };
217294
+ }
216051
217295
  function validateOrderedIds(body) {
216052
217296
  if (!body || typeof body !== "object") {
216053
217297
  throw new Error("Request body must contain orderedIds array");
@@ -216147,27 +217391,22 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216147
217391
  router.post(
216148
217392
  "/",
216149
217393
  catchTypedHandler(async (req, res) => {
216150
- const { title, description, autoAdvance, autopilotEnabled, baseBranch } = req.body;
217394
+ const { title, description, autoAdvance, baseBranch, branchStrategy } = req.body;
216151
217395
  const validatedTitle = validateTitle(title);
216152
217396
  const validatedDescription = validateDescription2(description);
216153
217397
  const input = {
216154
217398
  title: validatedTitle,
216155
217399
  description: validatedDescription,
216156
- baseBranch: validateDescription2(baseBranch)
217400
+ baseBranch: validateDescription2(baseBranch),
217401
+ branchStrategy: validateMissionBranchStrategy(branchStrategy)
216157
217402
  };
216158
217403
  const mission = missionStore.createMission(input);
216159
217404
  const updates = {};
216160
217405
  if (autoAdvance !== void 0) {
216161
217406
  updates.autoAdvance = validateBoolean(autoAdvance, "autoAdvance");
216162
217407
  }
216163
- if (autopilotEnabled !== void 0) {
216164
- updates.autopilotEnabled = validateBoolean(autopilotEnabled, "autopilotEnabled");
216165
- }
216166
217408
  if (Object.keys(updates).length > 0) {
216167
217409
  const updatedMission = missionStore.updateMission(mission.id, updates);
216168
- if (missionAutopilot && updatedMission.autopilotEnabled) {
216169
- missionAutopilot.watchMission(updatedMission.id);
216170
- }
216171
217410
  res.status(201).json(updatedMission);
216172
217411
  return;
216173
217412
  }
@@ -216512,18 +217751,11 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216512
217751
  status: "pending"
216513
217752
  });
216514
217753
  for (const featureData of sliceData.features ?? []) {
216515
- const feature = missionStore.addFeature(slice.id, {
217754
+ missionStore.addFeature(slice.id, {
216516
217755
  title: featureData.title,
216517
217756
  description: featureData.description,
216518
217757
  acceptanceCriteria: featureData.acceptanceCriteria
216519
217758
  });
216520
- const assertionText = featureData.acceptanceCriteria || featureData.description || `Verify implementation of: ${featureData.title}`;
216521
- const assertion = missionStore.addContractAssertion(milestone.id, {
216522
- title: featureData.title,
216523
- assertion: assertionText,
216524
- status: "pending"
216525
- });
216526
- missionStore.linkFeatureToAssertion(feature.id, assertion.id);
216527
217759
  }
216528
217760
  }
216529
217761
  missionStore.applyDerivedMilestoneAcceptanceCriteria(milestone.id);
@@ -216563,7 +217795,7 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216563
217795
  "/:missionId",
216564
217796
  catchTypedHandler(async (req, res) => {
216565
217797
  const { missionId } = req.params;
216566
- const { title, description, status, autoAdvance, autopilotEnabled, baseBranch } = req.body;
217798
+ const { title, description, status, autoAdvance, autopilotEnabled, baseBranch, branchStrategy } = req.body;
216567
217799
  if (!validateMissionId(missionId)) {
216568
217800
  throw badRequest("Invalid mission ID format");
216569
217801
  }
@@ -216586,6 +217818,9 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216586
217818
  if (baseBranch !== void 0) {
216587
217819
  updates.baseBranch = validateDescription2(baseBranch);
216588
217820
  }
217821
+ if (branchStrategy !== void 0) {
217822
+ updates.branchStrategy = validateMissionBranchStrategy(branchStrategy);
217823
+ }
216589
217824
  if (Object.keys(updates).length === 0) {
216590
217825
  throw badRequest("No valid fields to update");
216591
217826
  }
@@ -248278,6 +249513,7 @@ var init_mission = __esm({
248278
249513
  var goals_exports = {};
248279
249514
  __export(goals_exports, {
248280
249515
  runGoalsArchive: () => runGoalsArchive,
249516
+ runGoalsCitations: () => runGoalsCitations,
248281
249517
  runGoalsCreate: () => runGoalsCreate,
248282
249518
  runGoalsList: () => runGoalsList
248283
249519
  });
@@ -248356,6 +249592,31 @@ async function runGoalsCreate(titleArg, descriptionArg, projectName) {
248356
249592
  throw error;
248357
249593
  }
248358
249594
  }
249595
+ async function runGoalsCitations(projectName, opts) {
249596
+ const store = await getStore2({ project: projectName });
249597
+ const rows = store.listGoalCitations({
249598
+ goalId: opts.goalId,
249599
+ agentId: opts.agentId,
249600
+ surface: opts.surface,
249601
+ startTime: opts.since,
249602
+ endTime: opts.until,
249603
+ limit: opts.limit ?? 50
249604
+ });
249605
+ if (opts.json) {
249606
+ console.log(JSON.stringify(rows, null, 2));
249607
+ return;
249608
+ }
249609
+ if (rows.length === 0) {
249610
+ console.log("No goal citations match the filter.");
249611
+ return;
249612
+ }
249613
+ for (const row of rows) {
249614
+ console.log(`${row.timestamp} ${row.goalId} ${row.agentId} ${row.surface} ${row.sourceRef}`);
249615
+ console.log(` ${row.snippet}`);
249616
+ }
249617
+ console.log(`
249618
+ ${rows.length} citation(s).`);
249619
+ }
248359
249620
  async function runGoalsArchive(idArg, projectName) {
248360
249621
  if (!idArg) {
248361
249622
  console.error("Usage: fn goals archive <id>");
@@ -251586,7 +252847,7 @@ async function loadCommandHandlers() {
251586
252847
  const { runBackupCreate: runBackupCreate2, runBackupList: runBackupList2, runBackupRestore: runBackupRestore2, runBackupCleanup: runBackupCleanup2 } = await Promise.resolve().then(() => (init_backup2(), backup_exports));
251587
252848
  const { runMemoryBackupCreate: runMemoryBackupCreate2, runMemoryBackupList: runMemoryBackupList2, runMemoryBackupRestore: runMemoryBackupRestore2 } = await Promise.resolve().then(() => (init_memory_backup2(), memory_backup_exports));
251588
252849
  const { runMissionCreate: runMissionCreate2, runMissionList: runMissionList2, runMissionShow: runMissionShow2, runMissionDelete: runMissionDelete2, runMissionActivateSlice: runMissionActivateSlice2 } = await Promise.resolve().then(() => (init_mission(), mission_exports));
251589
- const { runGoalsList: runGoalsList2, runGoalsCreate: runGoalsCreate2, runGoalsArchive: runGoalsArchive2 } = await Promise.resolve().then(() => (init_goals(), goals_exports));
252850
+ const { runGoalsList: runGoalsList2, runGoalsCreate: runGoalsCreate2, runGoalsArchive: runGoalsArchive2, runGoalsCitations: runGoalsCitations2 } = await Promise.resolve().then(() => (init_goals(), goals_exports));
251590
252851
  const { runProjectList: runProjectList2, runProjectAdd: runProjectAdd2, runProjectRemove: runProjectRemove2, runProjectShow: runProjectShow2, runProjectInfo: runProjectInfo2, runProjectSetDefault: runProjectSetDefault2, runProjectDetect: runProjectDetect2 } = await Promise.resolve().then(() => (init_project(), project_exports));
251591
252852
  const { runNodeList: runNodeList2, runNodeConnect: runNodeConnect2, runNodeDisconnect: runNodeDisconnect2, runNodeShow: runNodeShow2, runNodeHealth: runNodeHealth2, runMeshStatus: runMeshStatus2 } = await Promise.resolve().then(() => (init_node(), node_exports));
251592
252853
  const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -251654,6 +252915,7 @@ async function loadCommandHandlers() {
251654
252915
  runGoalsList: runGoalsList2,
251655
252916
  runGoalsCreate: runGoalsCreate2,
251656
252917
  runGoalsArchive: runGoalsArchive2,
252918
+ runGoalsCitations: runGoalsCitations2,
251657
252919
  runProjectList: runProjectList2,
251658
252920
  runProjectAdd: runProjectAdd2,
251659
252921
  runProjectRemove: runProjectRemove2,
@@ -251773,6 +253035,7 @@ PR:
251773
253035
  fn goals list [--status STATE] List goals (default: active)
251774
253036
  fn goals create [title] [desc] Create a new goal
251775
253037
  fn goals archive <id> Archive a goal
253038
+ fn goals citations [flags] List recorded goal-ID citations across agent logs and task documents (Slice 2 success signal)
251776
253039
  fn project list | ls [--json] List all registered projects
251777
253040
  fn project add [name] [path] [opts] Register a new project
251778
253041
  fn project remove | rm <name> [--force]
@@ -252025,6 +253288,7 @@ async function main() {
252025
253288
  runGoalsList: runGoalsList2,
252026
253289
  runGoalsCreate: runGoalsCreate2,
252027
253290
  runGoalsArchive: runGoalsArchive2,
253291
+ runGoalsCitations: runGoalsCitations2,
252028
253292
  runProjectList: runProjectList2,
252029
253293
  runProjectAdd: runProjectAdd2,
252030
253294
  runProjectRemove: runProjectRemove2,
@@ -252790,9 +254054,21 @@ async function main() {
252790
254054
  await runGoalsArchive2(id, projectName);
252791
254055
  break;
252792
254056
  }
254057
+ case "citations": {
254058
+ const goalId = getFlagValue(args, "--goal");
254059
+ const agentId = getFlagValue(args, "--agent");
254060
+ const surface = getFlagValue(args, "--surface");
254061
+ const since = getFlagValue(args, "--since");
254062
+ const until2 = getFlagValue(args, "--until");
254063
+ const limitValue = getFlagValue(args, "--limit");
254064
+ const limit = limitValue ? Number(limitValue) : void 0;
254065
+ const json = args.includes("--json");
254066
+ await runGoalsCitations2(projectName, { goalId, agentId, surface, since, until: until2, limit, json });
254067
+ break;
254068
+ }
252793
254069
  default:
252794
254070
  console.error(`Unknown subcommand: goals ${subcommand || ""}`);
252795
- console.log("Try: fn goals list | create | archive");
254071
+ console.log("Try: fn goals list | create | archive | citations");
252796
254072
  process.exit(1);
252797
254073
  }
252798
254074
  break;