@runfusion/fusion 0.36.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 (61) hide show
  1. package/dist/bin.js +1599 -174
  2. package/dist/client/.vite/manifest.json +66 -66
  3. package/dist/client/assets/AgentDetailView-CquLtIia.js +18 -0
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-BoD6come.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-Dd613aZ5.js → DevServerView-BRA00XoT.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-H-fUl2dF.js → DirectoryPicker-D496NTjZ.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-Dshc9e5K.js → DocumentsView-DIRlgdeB.js} +1 -1
  10. package/dist/client/assets/{EvalsView-50W0K5h7.js → EvalsView-By5mtr80.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-BXBljPFj.js → ExperimentalAgentOnboardingModal-Br57oNdX.js} +1 -1
  12. package/dist/client/assets/GoalsView-B65urz9k.css +1 -0
  13. package/dist/client/assets/GoalsView-COI14CWJ.js +1 -0
  14. package/dist/client/assets/{InsightsView-sVbGRKTl.js → InsightsView-Hm7vX2lr.js} +2 -2
  15. package/dist/client/assets/{MemoryView-006a_sB0.js → MemoryView-BLozyYur.js} +1 -1
  16. package/dist/client/assets/{NodesView-CiRPqgVx.js → NodesView-bXC6WoE2.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-Y1ETuTxf.js → PiExtensionsManager-DEiqcMkx.js} +2 -2
  18. package/dist/client/assets/{PluginManager-Df_KHiXn.js → PluginManager-l4224zxk.js} +1 -1
  19. package/dist/client/assets/{ResearchView-DU_QuS_-.js → ResearchView-CpsR1Q56.js} +1 -1
  20. package/dist/client/assets/{SecretsView-D61Jvysn.js → SecretsView-Bwme1zZI.js} +1 -1
  21. package/dist/client/assets/{SettingsModal-BarzmqNb.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-BF3iRNq9.js → SetupWizardModal-BFD9E0H_.js} +1 -1
  25. package/dist/client/assets/{SkillsView-tCKOFw3j.js → SkillsView-CZMLY7F_.js} +1 -1
  26. package/dist/client/assets/{StashRecoveryView-nGtypd10.js → StashRecoveryView-jgZeWbZo.js} +1 -1
  27. package/dist/client/assets/{TodoView-IyGPV6AO.js → TodoView-DXdnOymj.js} +2 -2
  28. package/dist/client/assets/{dashboard-view-DiA5-hnb.js → dashboard-view-H8SnaCPP.js} +2 -2
  29. package/dist/client/assets/{folder-open-Byqfi_N-.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-D6_BjqVi.js → upload-ntU2YHPl.js} +1 -1
  33. package/dist/client/assets/{users-CERYgInO.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 +1449 -125
  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/skill/fusion/SKILL.md +1 -1
  51. package/skill/fusion/references/extension-tools.md +16 -0
  52. package/skill/fusion/references/fusion-capabilities.md +1 -0
  53. package/dist/client/assets/AgentDetailView-CGycokdR.js +0 -18
  54. package/dist/client/assets/AgentsView-B_afRRfc.js +0 -29
  55. package/dist/client/assets/ChatView-IjNrSZSf.js +0 -6
  56. package/dist/client/assets/GoalsView-Cg136Jcn.css +0 -1
  57. package/dist/client/assets/GoalsView-Dh508Z2s.js +0 -1
  58. package/dist/client/assets/SettingsModal-0gevFhXV.css +0 -1
  59. package/dist/client/assets/SettingsModal-Cpur3WpT.js +0 -31
  60. package/dist/client/assets/index-B9Ro_NwC.js +0 -742
  61. package/dist/client/assets/index-DVFBXjGT.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();
@@ -71754,6 +72444,8 @@ var init_gating_classifications = __esm({
71754
72444
  "fn_insight_show",
71755
72445
  "fn_insight_run_list",
71756
72446
  "fn_insight_run_show",
72447
+ "fn_goal_list",
72448
+ "fn_goal_show",
71757
72449
  "fn_mission_list",
71758
72450
  "fn_mission_show",
71759
72451
  "fn_list_agents",
@@ -79794,12 +80486,16 @@ var init_agent_instructions = __esm({
79794
80486
 
79795
80487
  // ../engine/src/prompt-layers.ts
79796
80488
  function buildPromptLayers(input) {
79797
- const { basePrompt, agentInstructions, memorySection, pluginContributions, performanceFeedback } = input;
80489
+ const { basePrompt, agentInstructions, memorySection, goalContext, pluginContributions, performanceFeedback } = input;
79798
80490
  const dynamicParts = [];
79799
80491
  const trimmedMemory = memorySection?.trim() ?? "";
79800
80492
  if (trimmedMemory) {
79801
80493
  dynamicParts.push(trimmedMemory);
79802
80494
  }
80495
+ const trimmedGoalContext = goalContext?.trim() ?? "";
80496
+ if (trimmedGoalContext) {
80497
+ dynamicParts.push(trimmedGoalContext);
80498
+ }
79803
80499
  const trimmedInstructions = agentInstructions?.trim() ?? "";
79804
80500
  if (trimmedInstructions) {
79805
80501
  dynamicParts.push(`## Custom Instructions
@@ -85127,7 +85823,7 @@ function isInsideWorktreesDir(rootDir, worktreePath, settings) {
85127
85823
  return isInsideConfiguredWorktreesDir(rootDir, settings, worktreePath);
85128
85824
  }
85129
85825
  function deriveTaskIdFromBranch(branchName) {
85130
- const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?$/i);
85826
+ const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?(?:-[a-z0-9._-]+)*$/i);
85131
85827
  return match ? match[1].toUpperCase() : branchName.toUpperCase();
85132
85828
  }
85133
85829
  async function scanIdleWorktrees(rootDir, store, settings) {
@@ -92596,6 +93292,7 @@ __export(merger_exports, {
92596
93292
  VERIFICATION_LOG_MAX_CHARS: () => VERIFICATION_LOG_MAX_CHARS,
92597
93293
  VerificationError: () => VerificationError,
92598
93294
  __executePostMergeScriptStepForTests: () => __executePostMergeScriptStepForTests,
93295
+ __testOnlyBuildDeterministicMergeMessage: () => buildDeterministicMergeMessage,
92599
93296
  __test__: () => __test__,
92600
93297
  aiMergeTask: () => aiMergeTask,
92601
93298
  applyAutostashBySha: () => applyAutostashBySha,
@@ -95331,7 +96028,7 @@ async function filterStagedGitignoredPaths(rootDir, taskId) {
95331
96028
  return { unstaged: [], remainingStaged };
95332
96029
  }
95333
96030
  }
95334
- async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary, aiSubject, fixModifiedFiles = /* @__PURE__ */ new Set(), store, auditor) {
96031
+ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary, aiBody, aiSubject, fixModifiedFiles = /* @__PURE__ */ new Set(), store, auditor) {
95335
96032
  try {
95336
96033
  const { stdout: squashStagedOut } = await execAsync26("git diff --cached --name-only", {
95337
96034
  cwd: rootDir,
@@ -95631,7 +96328,7 @@ ${msg}`;
95631
96328
  diffStat: messageDiffStat,
95632
96329
  includeTaskId,
95633
96330
  aiSummary,
95634
- aiBody: void 0,
96331
+ aiBody,
95635
96332
  aiSubject
95636
96333
  });
95637
96334
  let lineageId;
@@ -99063,6 +99760,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
99063
99760
  settings,
99064
99761
  options.signal,
99065
99762
  aiMergeSummary,
99763
+ aiMergeBody,
99066
99764
  aiMergeSubject,
99067
99765
  verificationFixModifiedFiles,
99068
99766
  store,
@@ -99194,6 +99892,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
99194
99892
  settings,
99195
99893
  options.signal,
99196
99894
  aiMergeSummary,
99895
+ aiMergeBody,
99197
99896
  aiMergeSubject,
99198
99897
  buildFixModifiedFiles,
99199
99898
  store,
@@ -100108,6 +100807,7 @@ async function executeMergeAttempt(params, aiTracker) {
100108
100807
  diffStat,
100109
100808
  includeTaskId,
100110
100809
  aiSummary: safeBody,
100810
+ aiBody: aiBody?.trim().length ? aiBody : safeBody,
100111
100811
  aiSubject
100112
100812
  });
100113
100813
  await enforceSquashFileScopeInvariant({
@@ -100302,6 +101002,7 @@ async function executeMergeAttempt(params, aiTracker) {
100302
101002
  diffStat: actualContext.diffStat || diffStat,
100303
101003
  includeTaskId,
100304
101004
  aiSummary,
101005
+ aiBody,
100305
101006
  aiSubject
100306
101007
  });
100307
101008
  const trailerArg = buildTaskTrailerArgs(taskId);
@@ -101330,6 +102031,12 @@ async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTar
101330
102031
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
101331
102032
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
101332
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
+ }
101333
102040
  function formatRecoveryTimestamp(date = /* @__PURE__ */ new Date()) {
101334
102041
  const pad = (value) => String(value).padStart(2, "0");
101335
102042
  return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
@@ -101544,7 +102251,7 @@ function isNoTaskDoneFailure2(task) {
101544
102251
  function hasStepProgress2(task) {
101545
102252
  return task.steps.some((step) => step.status !== "pending");
101546
102253
  }
101547
- 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;
101548
102255
  var init_self_healing = __esm({
101549
102256
  "../engine/src/self-healing.ts"() {
101550
102257
  "use strict";
@@ -101577,6 +102284,7 @@ var init_self_healing = __esm({
101577
102284
  STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS = 10 * 6e4;
101578
102285
  COMPLETION_HANDOFF_LIMBO_GRACE_MS = 5 * 6e4;
101579
102286
  MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES = 3;
102287
+ MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
101580
102288
  APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
101581
102289
  STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
101582
102290
  STARVED_PEER_PROGRESS_THRESHOLD = 3;
@@ -103093,13 +103801,71 @@ var init_self_healing = __esm({
103093
103801
  }
103094
103802
  }
103095
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
+ }
103096
103859
  await this.store.updateTask(task.id, {
103097
103860
  worktree: inspection.livePath,
103098
103861
  branch: task.branch,
103099
103862
  paused: false,
103100
103863
  pausedReason: void 0,
103101
103864
  status: null,
103102
- error: null
103865
+ error: null,
103866
+ resumeLimboCount: resumeAttemptCount,
103867
+ resumeLimboTipSha: inspection.tipSha,
103868
+ resumeLimboStepSignature: stepSignature
103103
103869
  });
103104
103870
  await this.store.logEntry(
103105
103871
  task.id,
@@ -104331,7 +105097,7 @@ var init_self_healing = __esm({
104331
105097
  const blockerId = task.blockedBy;
104332
105098
  const unresolvedDeps = task.dependencies.filter((depId) => {
104333
105099
  const dep = taskById.get(depId);
104334
- 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";
104335
105101
  });
104336
105102
  const overlapBlocker = task.overlapBlockedBy ? taskById.get(task.overlapBlockedBy) : void 0;
104337
105103
  const hasActiveOverlapBlocker = Boolean(
@@ -104357,6 +105123,9 @@ var init_self_healing = __esm({
104357
105123
  reasonCode = "missing-blocker";
104358
105124
  reason = `blocker ${blockerId} missing`;
104359
105125
  }
105126
+ } else if (blocker.deletedAt) {
105127
+ reasonCode = "soft-deleted-blocker";
105128
+ reason = `blocker ${blockerId} soft-deleted at ${blocker.deletedAt}`;
104360
105129
  } else if (blocker.column === "done") {
104361
105130
  reasonCode = "blocker-done";
104362
105131
  reason = `blocker ${blockerId} is done`;
@@ -105886,6 +106655,12 @@ var init_self_healing = __esm({
105886
106655
  try {
105887
106656
  const hardBlocker = getTaskHardMergeBlocker({
105888
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,
105889
106664
  steps: task.steps ?? [],
105890
106665
  workflowStepResults: task.workflowStepResults
105891
106666
  });
@@ -110517,6 +111292,258 @@ var init_token_cap_detector = __esm({
110517
111292
  }
110518
111293
  });
110519
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
+
110520
111547
  // ../engine/src/auto-recovery-handlers/branch-worktree.ts
110521
111548
  import { exec as exec31 } from "node:child_process";
110522
111549
  import { existsSync as existsSync39 } from "node:fs";
@@ -111381,7 +112408,7 @@ function isTransientMissingTaskJsonError(error, task) {
111381
112408
  const normalizedTaskJsonPath = resolvePath(filePath);
111382
112409
  return normalizedTaskJsonPath.startsWith(`${normalizedWorktree}/`);
111383
112410
  }
111384
- function detectPendingReviewBlock(task, codeReviewVerdicts) {
112411
+ function detectPendingReviewBlock(task, _codeReviewVerdicts) {
111385
112412
  const inProgressStepIndices = [];
111386
112413
  for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
111387
112414
  if (task.steps[stepIndex]?.status === "in-progress") {
@@ -111393,9 +112420,6 @@ function detectPendingReviewBlock(task, codeReviewVerdicts) {
111393
112420
  }
111394
112421
  const recentActions = (task.log ?? []).slice(-30).map((entry) => entry.action?.trim()).filter((action) => Boolean(action));
111395
112422
  for (const stepIndex of inProgressStepIndices) {
111396
- if (codeReviewVerdicts.get(stepIndex) === "REVISE") {
111397
- return { blocked: true, reason: "code-review-revise-outstanding", stepIndex };
111398
- }
111399
112423
  const stepDisplay = stepIndex + 1;
111400
112424
  const codeRequest = `code review requested for Step ${stepDisplay}`;
111401
112425
  const planRequest = `plan review requested for Step ${stepDisplay}`;
@@ -111956,6 +112980,8 @@ var init_executor = __esm({
111956
112980
  init_worktree_hooks();
111957
112981
  init_agent_instructions();
111958
112982
  init_prompt_layers();
112983
+ init_goal_injection_diagnostics();
112984
+ init_goal_anchoring_audit();
111959
112985
  init_run_audit();
111960
112986
  init_auto_recovery();
111961
112987
  init_restart_recovery_coordinator();
@@ -112342,6 +113368,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
112342
113368
  executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
112343
113369
  try {
112344
113370
  await this.clearResumeFailureState(task);
113371
+ await this.store.updateTask(task.id, {
113372
+ resumeLimboCount: 0,
113373
+ resumeLimboTipSha: null,
113374
+ resumeLimboStepSignature: null
113375
+ });
112345
113376
  await this.store.logEntry(task.id, "Resuming execution after unpause", void 0, this.getRunContextFor(task.id));
112346
113377
  await this.recoverApprovedStepsOnResume(task.id);
112347
113378
  } catch (clearErr) {
@@ -114717,8 +115748,30 @@ ${summary}`,
114717
115748
  if (executorPluginContributions) {
114718
115749
  executorLog.log(`${task.id}: applied plugin prompt contributions for executor-system surface`);
114719
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
+ });
114720
115772
  const executorLayers = buildPromptLayers({
114721
115773
  basePrompt: getExecutorSystemPrompt(settings),
115774
+ goalContext: executorGoalContext,
114722
115775
  agentInstructions: executorInstructions,
114723
115776
  pluginContributions: executorPluginContributions
114724
115777
  });
@@ -115035,12 +116088,7 @@ ${summary}`,
115035
116088
  this.tokenUsageBaselines.delete(task.id);
115036
116089
  session.dispose();
115037
116090
  await this.persistTokenUsage(task.id);
115038
- await this.store.updateTask(task.id, {
115039
- status: "failed",
115040
- error: "executor-exit-while-review-pending"
115041
- });
115042
116091
  await this.handoffTaskToReview(task, "executor-exit-while-review-pending");
115043
- this.options.onError?.(task, new Error("executor-exit-while-review-pending"));
115044
116092
  pendingReviewParked = true;
115045
116093
  break;
115046
116094
  }
@@ -120193,7 +121241,8 @@ async function collectTaskEvaluationEvidence(params) {
120193
121241
  taskDoneRetryCount: task.taskDoneRetryCount ?? 0,
120194
121242
  verificationFailureCount: task.verificationFailureCount ?? 0,
120195
121243
  mergeConflictBounceCount: task.mergeConflictBounceCount ?? 0,
120196
- mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0
121244
+ mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0,
121245
+ mergeTransientRetryCount: task.mergeTransientRetryCount ?? 0
120197
121246
  }
120198
121247
  }],
120199
121248
  commits: commitEvidence,
@@ -120652,6 +121701,25 @@ function filterPathsByIgnoreList(paths, ignorePaths) {
120652
121701
  }
120653
121702
  return paths.filter((path10) => !normalizedIgnorePaths.some((ignore) => isIgnoredOverlapPath(path10, ignore)));
120654
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
+ }
120655
121723
  function findHigherPriorityQueuedOverlap(candidate, queuedScopes, overlap) {
120656
121724
  let higher = null;
120657
121725
  for (const queued of queuedScopes) {
@@ -121326,34 +122394,47 @@ var init_scheduler = __esm({
121326
122394
  );
121327
122395
  }
121328
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
+ };
121329
122402
  const inversionEmitted = /* @__PURE__ */ new Set();
121330
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
+ };
121331
122415
  if (settings.groupOverlappingFiles) {
121332
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
121333
122416
  for (const t of inProgress) {
121334
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121335
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121336
- 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");
121337
122426
  }
121338
122427
  for (const t of todo) {
121339
- if (t.status !== "queued" || t.paused || t.userPaused) continue;
121340
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121341
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
122428
+ const filteredScope = await getFilteredFileScope(t.id);
121342
122429
  if (filteredScope.length === 0) continue;
122430
+ if (!isRunnableQueuedOverlapCandidate(t, tasks, now, activeScopes, filteredScope)) continue;
121343
122431
  queuedHigherPriorityScopes.push({
121344
122432
  id: t.id,
121345
122433
  priority: t.priority,
121346
122434
  createdAt: t.createdAt,
121347
122435
  scope: filteredScope
121348
122436
  });
121349
- }
121350
- const inReviewWithWorktree = tasks.filter(
121351
- (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
121352
- );
121353
- for (const t of inReviewWithWorktree) {
121354
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
121355
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121356
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
122437
+ queuedHigherPriorityTaskById.set(t.id, t);
121357
122438
  }
121358
122439
  }
121359
122440
  const ordered = resolveDependencyOrder(todo);
@@ -121374,10 +122455,7 @@ var init_scheduler = __esm({
121374
122455
  continue;
121375
122456
  }
121376
122457
  }
121377
- const unmetDeps = task.dependencies.filter((depId) => {
121378
- const dep = tasks.find((t) => t.id === depId);
121379
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
121380
- });
122458
+ const unmetDeps = getUnmetSchedulingDependencies(task, tasks);
121381
122459
  if (unmetDeps.length > 0) {
121382
122460
  await this.store.updateTask(task.id, {
121383
122461
  status: "queued",
@@ -121411,17 +122489,18 @@ var init_scheduler = __esm({
121411
122489
  continue;
121412
122490
  }
121413
122491
  if (settings.groupOverlappingFiles) {
121414
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
121415
- const taskScope = filterPathsByIgnoreList(
121416
- await this.store.parseFileScopeFromPrompt(task.id),
121417
- overlapIgnorePaths
121418
- );
122492
+ const taskScope = await getFilteredFileScope(task.id);
121419
122493
  if (taskScope.length > 0) {
121420
122494
  const activeScopeEntries = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId));
121421
122495
  const overlapBlockerId = task.overlapBlockedBy || task.blockedBy;
121422
122496
  const currentBlockerScope = overlapBlockerId ? activeScopes.get(overlapBlockerId) : void 0;
121423
122497
  const hasValidCurrentBlocker = Boolean(overlapBlockerId) && Boolean(currentBlockerScope) && this.pathsOverlap(taskScope, currentBlockerScope);
121424
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
+ });
121425
122504
  const higherPriorityQueuedOverlap = findHigherPriorityQueuedOverlap(
121426
122505
  {
121427
122506
  id: task.id,
@@ -121429,7 +122508,7 @@ var init_scheduler = __esm({
121429
122508
  createdAt: task.createdAt,
121430
122509
  scope: taskScope
121431
122510
  },
121432
- queuedHigherPriorityScopes,
122511
+ runnableQueuedHigherPriorityScopes,
121433
122512
  this.pathsOverlap.bind(this)
121434
122513
  );
121435
122514
  if (higherPriorityQueuedOverlap) {
@@ -121444,7 +122523,7 @@ var init_scheduler = __esm({
121444
122523
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
121445
122524
  await this.logDispatchQueuedReason(
121446
122525
  task.id,
121447
- `queued \u2014 deferred for higher-priority queued task ${higherPriorityQueuedOverlap.id} (overlap)`
122526
+ `queued \u2014 deferred for higher-priority runnable queued task ${higherPriorityQueuedOverlap.id} (overlap)`
121448
122527
  );
121449
122528
  continue;
121450
122529
  }
@@ -121476,7 +122555,7 @@ var init_scheduler = __esm({
121476
122555
  blockerId: overlapBlockerTask.id,
121477
122556
  blockerPriority: overlapBlockerTask.priority ?? null,
121478
122557
  blockerCreatedAt: overlapBlockerTask.createdAt ?? null,
121479
- blockerColumn: overlapBlockerTask.column,
122558
+ blockerColumn: activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask.column,
121480
122559
  source: "scheduler.overlap-priority-inversion"
121481
122560
  }
121482
122561
  });
@@ -121487,7 +122566,11 @@ var init_scheduler = __esm({
121487
122566
  }
121488
122567
  }
121489
122568
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
121490
- 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
+ );
121491
122574
  continue;
121492
122575
  }
121493
122576
  if (task.overlapBlockedBy) {
@@ -121706,11 +122789,8 @@ var init_scheduler = __esm({
121706
122789
  this.options.onSchedule?.(task);
121707
122790
  started++;
121708
122791
  if (settings.groupOverlappingFiles) {
121709
- const scope = filterPathsByIgnoreList(
121710
- await this.store.parseFileScopeFromPrompt(task.id),
121711
- settings.overlapIgnorePaths
121712
- );
121713
- 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");
121714
122794
  }
121715
122795
  }
121716
122796
  await this.emitHighOverlapFanoutWarnings(tasks);
@@ -122863,7 +123943,10 @@ var init_mission_execution_loop = __esm({
122863
123943
  try {
122864
123944
  await this.missionStore.transitionLoopState(feature.id, "implementing");
122865
123945
  if (feature.taskId) {
122866
- await this.processTaskOutcome(feature.taskId);
123946
+ const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
123947
+ if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
123948
+ await this.processTaskOutcome(feature.taskId);
123949
+ }
122867
123950
  }
122868
123951
  recoveredCount++;
122869
123952
  } catch (err) {
@@ -122874,7 +123957,10 @@ var init_mission_execution_loop = __esm({
122874
123957
  loopLog.log(`Recovery: feature ${feature.id} awaiting fix implementation`);
122875
123958
  if (feature.taskId) {
122876
123959
  try {
122877
- await this.processTaskOutcome(feature.taskId);
123960
+ const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
123961
+ if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
123962
+ await this.processTaskOutcome(feature.taskId);
123963
+ }
122878
123964
  recoveredCount++;
122879
123965
  } catch (err) {
122880
123966
  loopLog.error(`Recovery failed for needs_fix feature ${feature.id}:`, err);
@@ -122916,10 +124002,17 @@ var init_mission_execution_loop = __esm({
122916
124002
  }
122917
124003
  if (feature.loopState !== "implementing") {
122918
124004
  loopLog.log(`Feature ${feature.id} loopState is "${feature.loopState}"; skipping validation`);
124005
+ this.logFeatureWarningEvent(feature.id, "validation_skipped_loop_state", `Validation skipped: feature ${feature.id} is in loopState "${feature.loopState}" (expected "implementing").`, {
124006
+ taskId,
124007
+ loopState: feature.loopState
124008
+ });
122919
124009
  return;
122920
124010
  }
122921
124011
  if (this.activeValidations.has(feature.id)) {
122922
124012
  loopLog.log(`Feature ${feature.id} already has an active validation; skipping duplicate trigger`);
124013
+ this.logFeatureWarningEvent(feature.id, "validation_deduplicated", `Validation already running for feature ${feature.id}; duplicate trigger ignored.`, {
124014
+ taskId
124015
+ });
122923
124016
  return;
122924
124017
  }
122925
124018
  const assertions = this.missionStore.listAssertionsForFeature(feature.id);
@@ -123387,6 +124480,10 @@ ${taskContext}` : ""}`;
123387
124480
  this.missionStore.completeValidatorRun(runId, "blocked", blockedReason);
123388
124481
  }
123389
124482
  loopLog.log(`Feature ${featureId} blocked: ${blockedReason}`);
124483
+ this.logFeatureErrorEvent(featureId, "validation_blocked", `Validation blocked for feature ${featureId}: ${blockedReason ?? "no reason provided"}`, {
124484
+ runId,
124485
+ blockedReason: blockedReason ?? null
124486
+ });
123390
124487
  if (this.missionAutopilot?.notifyValidationComplete) {
123391
124488
  await this.missionAutopilot.notifyValidationComplete(featureId, "blocked");
123392
124489
  }
@@ -123404,6 +124501,10 @@ ${taskContext}` : ""}`;
123404
124501
  this.missionStore.completeValidatorRun(runId, "error", error);
123405
124502
  }
123406
124503
  loopLog.error(`Feature ${featureId} validation error: ${error}`);
124504
+ this.logFeatureErrorEvent(featureId, "validation_error", `Validation error for feature ${featureId}: ${error}`, {
124505
+ runId,
124506
+ error
124507
+ });
123407
124508
  if (this.missionAutopilot?.notifyValidationComplete) {
123408
124509
  await this.missionAutopilot.notifyValidationComplete(featureId, "error");
123409
124510
  }
@@ -123412,6 +124513,31 @@ ${taskContext}` : ""}`;
123412
124513
  loopLog.error(`Error handling validation error for ${featureId}:`, err);
123413
124514
  }
123414
124515
  }
124516
+ logFeatureWarningEvent(featureId, code, description, metadata) {
124517
+ this.logFeatureMissionEvent(featureId, "warning", code, description, metadata);
124518
+ }
124519
+ logFeatureErrorEvent(featureId, code, description, metadata) {
124520
+ this.logFeatureMissionEvent(featureId, "error", code, description, metadata);
124521
+ }
124522
+ logFeatureMissionEvent(featureId, eventType, code, description, metadata) {
124523
+ const feature = this.missionStore.getFeature(featureId);
124524
+ if (!feature) return;
124525
+ const slice = this.missionStore.getSlice(feature.sliceId);
124526
+ if (!slice) return;
124527
+ const milestone = this.missionStore.getMilestone(slice.milestoneId);
124528
+ if (!milestone) return;
124529
+ try {
124530
+ this.missionStore.logMissionEvent?.(milestone.missionId, eventType, description, {
124531
+ code,
124532
+ featureId,
124533
+ sliceId: slice.id,
124534
+ milestoneId: milestone.id,
124535
+ ...metadata
124536
+ });
124537
+ } catch (err) {
124538
+ loopLog.warn(`Failed to log mission ${eventType} event for feature ${featureId}:`, err);
124539
+ }
124540
+ }
123415
124541
  };
123416
124542
  }
123417
124543
  });
@@ -124694,6 +125820,8 @@ var init_agent_heartbeat = __esm({
124694
125820
  init_agent_instructions();
124695
125821
  init_heartbeat_procedure_resolver();
124696
125822
  init_prompt_layers();
125823
+ init_goal_injection_diagnostics();
125824
+ init_goal_anchoring_audit();
124697
125825
  init_logger2();
124698
125826
  init_worktree_acquisition();
124699
125827
  init_run_audit();
@@ -126085,12 +127213,16 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126085
127213
  engineRunContext.taskId = taskId;
126086
127214
  }
126087
127215
  let autoClaimCandidates = [];
127216
+ let autoClaimSnapshotCandidateCount = 0;
127217
+ let autoClaimRoleFilteredCount = 0;
126088
127218
  const autoClaimEnabled = isAutoClaimRelevantTasksEnabled(agent);
126089
127219
  if (!taskId && canRunNoTaskHeartbeat && autoClaimEnabled && this.snapshotManager) {
126090
127220
  try {
126091
127221
  const snapshot = await this.snapshotManager.getSnapshot();
127222
+ autoClaimSnapshotCandidateCount = snapshot.tasks.length;
126092
127223
  const roleCompatibleCandidates = snapshot.tasks.filter((candidate) => canAgentTakeImplementationTask(agent, candidate));
126093
127224
  const skippedIncompatibleCount = snapshot.tasks.length - roleCompatibleCandidates.length;
127225
+ autoClaimRoleFilteredCount = skippedIncompatibleCount;
126094
127226
  if (skippedIncompatibleCount > 0) {
126095
127227
  heartbeatLog.log(
126096
127228
  `Agent ${agentId} (role=${agent.role}) skipped auto-claim of ${skippedIncompatibleCount} implementation task(s) \u2014 only executor agents may claim implementation work`
@@ -126346,8 +127478,29 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126346
127478
  if (heartbeatPluginContributions) {
126347
127479
  heartbeatLog.log(`applied plugin prompt contributions for heartbeat surface`);
126348
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
+ });
126349
127501
  const heartbeatLayers = buildPromptLayers({
126350
127502
  basePrompt: baseHeartbeatSystemPrompt,
127503
+ goalContext: heartbeatGoalContext,
126351
127504
  agentInstructions: [resolvedInstructionsText, memoryInstructions, selfImprovePrompt].filter((part) => part.trim()).join("\n\n"),
126352
127505
  pluginContributions: heartbeatPluginContributions
126353
127506
  });
@@ -126622,10 +127775,15 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126622
127775
  );
126623
127776
  }
126624
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
+ ] : [];
126625
127783
  const candidateLines = promptCandidateLimit > 0 ? [
126626
127784
  "",
126627
127785
  "Open Task Candidates (auto-claim scan):",
126628
- ...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
126629
127787
  ] : [];
126630
127788
  executionPrompt = [
126631
127789
  `Heartbeat execution for agent "${agent.name}" (ID: ${agent.id})`,
@@ -126641,7 +127799,7 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
126641
127799
  ...wakeTriggerSourceLine ? [wakeTriggerSourceLine] : [],
126642
127800
  `- pending messages: ${pendingMessages.length}`,
126643
127801
  `- pending room messages: ${pendingRoomMessages.total}`,
126644
- `- auto-claim relevant tasks: ${autoClaimEnabled ? promptCandidateLimit === 0 ? "disabled (prompt-suppressed)" : "enabled" : "disabled"}`,
127802
+ `- auto-claim relevant tasks: ${autoClaimStatus}`,
126645
127803
  "",
126646
127804
  "Treat this wake delta as the highest-priority change for this heartbeat.",
126647
127805
  "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
@@ -133007,9 +134165,19 @@ var init_in_process_runtime = __esm({
133007
134165
  missionAutopilot: missionAutopilot ? {
133008
134166
  notifyValidationComplete: async (featureId) => {
133009
134167
  const feature = missionStore.getFeature(featureId);
133010
- if (feature?.taskId) {
133011
- await missionAutopilot.handleTaskCompletion(feature.taskId);
134168
+ if (!feature?.taskId) {
134169
+ return;
134170
+ }
134171
+ const slice = missionStore.getSlice(feature.sliceId);
134172
+ const milestone = slice ? missionStore.getMilestone(slice.milestoneId) : void 0;
134173
+ const missionId = milestone?.missionId;
134174
+ if (missionId) {
134175
+ const mission = missionStore.getMission(missionId);
134176
+ if (mission?.autopilotEnabled && !missionAutopilot.isWatching(missionId)) {
134177
+ missionAutopilot.watchMission(missionId);
134178
+ }
133012
134179
  }
134180
+ await missionAutopilot.handleTaskCompletion(feature.taskId);
133013
134181
  }
133014
134182
  } : void 0,
133015
134183
  rootDir: this.config.workingDirectory,
@@ -133782,7 +134950,8 @@ var init_in_process_runtime = __esm({
133782
134950
  this.usageLimitPauser = pauser;
133783
134951
  }
133784
134952
  /**
133785
- * 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.
133786
134955
  */
133787
134956
  setupEventForwarding() {
133788
134957
  this.taskStore.on("task:created", (task) => {
@@ -133797,6 +134966,10 @@ var init_in_process_runtime = __esm({
133797
134966
  this.recordActivity();
133798
134967
  this.emit("task:updated", task);
133799
134968
  });
134969
+ this.taskStore.on("task:deleted", (task, meta) => {
134970
+ this.recordActivity();
134971
+ this.emit("task:deleted", task, meta);
134972
+ });
133800
134973
  runtimeLog.log("Event forwarding setup complete");
133801
134974
  }
133802
134975
  /**
@@ -133847,7 +135020,7 @@ var init_in_process_runtime = __esm({
133847
135020
  function generateCorrelationId() {
133848
135021
  return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
133849
135022
  }
133850
- 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;
133851
135024
  var init_ipc_protocol = __esm({
133852
135025
  "../engine/src/ipc/ipc-protocol.ts"() {
133853
135026
  "use strict";
@@ -133860,6 +135033,7 @@ var init_ipc_protocol = __esm({
133860
135033
  TASK_CREATED = "TASK_CREATED";
133861
135034
  TASK_MOVED = "TASK_MOVED";
133862
135035
  TASK_UPDATED = "TASK_UPDATED";
135036
+ TASK_DELETED = "TASK_DELETED";
133863
135037
  ERROR_EVENT = "ERROR_EVENT";
133864
135038
  HEALTH_CHANGED = "HEALTH_CHANGED";
133865
135039
  }
@@ -134244,6 +135418,9 @@ var init_child_process_runtime = __esm({
134244
135418
  this.ipcHost.on(TASK_UPDATED, (payload) => {
134245
135419
  this.emit("task:updated", payload.task);
134246
135420
  });
135421
+ this.ipcHost.on(TASK_DELETED, (payload) => {
135422
+ this.emit("task:deleted", payload.task, payload.meta);
135423
+ });
134247
135424
  this.ipcHost.on(ERROR_EVENT, (payload) => {
134248
135425
  const error = new Error(payload.message);
134249
135426
  if (payload.code) {
@@ -135366,6 +136543,9 @@ var init_project_manager = __esm({
135366
136543
  runtime.on("task:updated", (task) => {
135367
136544
  this.emit("task:updated", { projectId, projectName, task });
135368
136545
  });
136546
+ runtime.on("task:deleted", (task, meta) => {
136547
+ this.emit("task:deleted", { projectId, projectName, task, meta });
136548
+ });
135369
136549
  runtime.on("error", (error) => {
135370
136550
  this.emit("error", { projectId, projectName, error });
135371
136551
  this.logActivity(
@@ -135728,6 +136908,10 @@ function buildMergeSystemPrompt2(agentPrompts) {
135728
136908
  " - The subject line must CONCISELY SUMMARIZE the squashed changes in",
135729
136909
  ' imperative mood (e.g. "add X", "fix Y") based on the actual diff \u2014 do',
135730
136910
  " not just restate the task title.",
136911
+ " - The commit BODY must include:",
136912
+ " 1) one short narrative summary line,",
136913
+ " 2) a bullet list of key changes, and",
136914
+ " 3) a `Files changed:` section populated from `git diff --stat`.",
135731
136915
  " - Include the task-id prefix and the trailer lines EXACTLY as given in the",
135732
136916
  " task instructions (they associate the commit with the board task)."
135733
136917
  ].filter((l) => l !== void 0).join("\n");
@@ -135741,11 +136925,15 @@ function buildMergePrompt2(input) {
135741
136925
  "Steps:",
135742
136926
  ` 1. Run: git merge --squash ${input.branch}`,
135743
136927
  " 2. If there are conflicts, resolve them (favor the task's intent), then `git add` the resolved files.",
135744
- " 3. Commit the staged result as a SINGLE commit whose subject summarizes the",
135745
- ` actual changes${input.taskTitle ? ` (task title hint: ${JSON.stringify(input.taskTitle)})` : ""}, including the required trailers:`,
135746
- ` git commit -m ${subjectShape}${trailerArgs}`,
136928
+ " 3. Build a merge body from the staged squash diff:",
136929
+ " - one short narrative summary line",
136930
+ " - bullet list of key changes",
136931
+ " - `Files changed:` + the output of `git diff --stat`",
136932
+ " 4. Commit the staged result as a SINGLE commit whose subject summarizes the",
136933
+ ` actual changes${input.taskTitle ? ` (task title hint: ${JSON.stringify(input.taskTitle)})` : ""}, including the body above and required trailers:`,
136934
+ ` git commit -m ${subjectShape} -m "<narrative + bullet list + Files changed: ...>"${trailerArgs}`,
135747
136935
  " Keep the trailer line(s) verbatim \u2014 they link the commit to the board task.",
135748
- " 4. Verify `git log --oneline ${tip}..HEAD` shows exactly one new commit and `git status` is clean.".replace("${tip}", short(input.tipSha)),
136936
+ " 5. Verify `git log --oneline ${tip}..HEAD` shows exactly one new commit and `git status` is clean.".replace("${tip}", short(input.tipSha)),
135749
136937
  "",
135750
136938
  "If `git merge --squash` reports the branch is already up to date (nothing to",
135751
136939
  "merge), do nothing and leave HEAD unchanged."
@@ -137036,6 +138224,7 @@ var init_project_engine = __esm({
137036
138224
  init_research_step_runner();
137037
138225
  init_run_audit();
137038
138226
  init_verification_followup_dedup();
138227
+ init_transient_error_detector();
137039
138228
  init_tunnel_process_manager();
137040
138229
  execFileAsync12 = promisify45(execFile17);
137041
138230
  MERGE_HANDOFF_GRACE_MS = 300;
@@ -137106,6 +138295,10 @@ var init_project_engine = __esm({
137106
138295
  manualMergeResolvers = /* @__PURE__ */ new Map();
137107
138296
  shuttingDown = false;
137108
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;
137109
138302
  /** Cap on outer in-review→in-progress bounces caused by deterministic
137110
138303
  * verification failures during auto-merge. After this many failed merges
137111
138304
  * for the same task, we stop bouncing it back, mark it failed, and create
@@ -138088,7 +139281,15 @@ ${detail}`
138088
139281
  this.internalEnqueueMerge(taskId);
138089
139282
  continue;
138090
139283
  }
138091
- 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
+ });
138092
139293
  if (blockerReason) {
138093
139294
  await store.updateTask(taskId, {
138094
139295
  status: "failed",
@@ -138607,6 +139808,16 @@ ${detail}`
138607
139808
  }
138608
139809
  } else {
138609
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
+ }
138610
139821
  await store.updateTask(taskId, {
138611
139822
  status: "failed",
138612
139823
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -138625,6 +139836,16 @@ ${detail}`
138625
139836
  }
138626
139837
  } else {
138627
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
+ }
138628
139849
  await store.updateTask(taskId, {
138629
139850
  status: "failed",
138630
139851
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -138660,6 +139881,40 @@ ${detail}`
138660
139881
  this.mergeRunning = false;
138661
139882
  }
138662
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
+ }
138663
139918
  wireAutoMerge(store, _cwd) {
138664
139919
  this.taskMovedHandler = async ({ task, to: to2 }) => {
138665
139920
  if (to2 !== "in-review") return;
@@ -142209,6 +143464,7 @@ __export(src_exports2, {
142209
143464
  BranchConflictError: () => BranchConflictError,
142210
143465
  BranchCrossContaminationError: () => BranchCrossContaminationError,
142211
143466
  CronRunner: () => CronRunner,
143467
+ DEFAULT_GOAL_INJECTION_CHAR_BUDGET: () => DEFAULT_GOAL_INJECTION_CHAR_BUDGET,
142212
143468
  DEFAULT_NTFY_EVENTS: () => DEFAULT_NTFY_EVENTS,
142213
143469
  DiffVolumeRegressionError: () => DiffVolumeRegressionError,
142214
143470
  EngineAlreadyRunningError: () => EngineAlreadyRunningError,
@@ -142223,6 +143479,9 @@ __export(src_exports2, {
142223
143479
  ExperimentGitNotConfiguredError: () => ExperimentGitNotConfiguredError,
142224
143480
  ExperimentMaxIterationsError: () => ExperimentMaxIterationsError,
142225
143481
  ExperimentRevertConflictError: () => ExperimentRevertConflictError,
143482
+ GOAL_INJECTION_APPLIED: () => GOAL_INJECTION_APPLIED,
143483
+ GOAL_INJECTION_SKIPPED: () => GOAL_INJECTION_SKIPPED,
143484
+ GOAL_RETRIEVAL_INVOKED: () => GOAL_RETRIEVAL_INVOKED,
142226
143485
  GitHubProvider: () => GitHubProvider,
142227
143486
  HEARTBEAT_NO_TASK_SYSTEM_PROMPT: () => HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
142228
143487
  HEARTBEAT_PROCEDURE: () => HEARTBEAT_PROCEDURE,
@@ -142233,6 +143492,7 @@ __export(src_exports2, {
142233
143492
  KNOWN_EXTERNAL_INTEGRATIONS: () => KNOWN_EXTERNAL_INTEGRATIONS,
142234
143493
  LLMSynthesisProvider: () => LLMSynthesisProvider,
142235
143494
  LocalDocsProvider: () => LocalDocsProvider,
143495
+ MAX_INJECTED_GOALS: () => MAX_INJECTED_GOALS,
142236
143496
  MOCK_PROVIDER_ID: () => MOCK_PROVIDER_ID3,
142237
143497
  MOCK_SYNTHETIC_TOKEN_USAGE: () => MOCK_SYNTHETIC_TOKEN_USAGE,
142238
143498
  MergeAbortedError: () => MergeAbortedError,
@@ -142308,10 +143568,13 @@ __export(src_exports2, {
142308
143568
  assertSafeUrl: () => assertSafeUrl,
142309
143569
  auditSquashMerge: () => auditSquashMerge,
142310
143570
  buildAgentChatPrompt: () => buildAgentChatPrompt,
143571
+ buildGoalContextSection: () => buildGoalContextSection,
142311
143572
  buildNtfyClickUrl: () => buildNtfyClickUrl,
142312
143573
  buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
142313
143574
  buildSystemPromptWithInstructions: () => buildSystemPromptWithInstructions,
142314
143575
  classifyBootstrapMisbinding: () => classifyBootstrapMisbinding,
143576
+ classifyGoalInjectionFailure: () => classifyGoalInjectionFailure,
143577
+ classifyGoalInjectionResult: () => classifyGoalInjectionResult,
142315
143578
  classifyTaskError: () => classifyTaskError,
142316
143579
  cleanupOrphanedWorktrees: () => cleanupOrphanedWorktrees,
142317
143580
  cleanupSecretsEnvFile: () => cleanupSecretsEnvFile,
@@ -142336,6 +143599,9 @@ __export(src_exports2, {
142336
143599
  describeModel: () => describeModel,
142337
143600
  dropAutostashBySha: () => dropAutostashBySha,
142338
143601
  dropAutostashHandle: () => dropAutostashHandle,
143602
+ emitGoalAnchoringAudit: () => emitGoalAnchoringAudit,
143603
+ emitGoalInjectionDiagnostic: () => emitGoalInjectionDiagnostic,
143604
+ emitGoalRetrievalAudit: () => emitGoalRetrievalAudit,
142339
143605
  ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
142340
143606
  executeApprovedAgentProvisioning: () => executeApprovedAgentProvisioning,
142341
143607
  executeApprovedWorktrunkInstall: () => executeApprovedWorktrunkInstall,
@@ -142450,6 +143716,9 @@ var init_src2 = __esm({
142450
143716
  init_external_integrations();
142451
143717
  init_web_fetch();
142452
143718
  init_error_classifier();
143719
+ init_goal_context_injector();
143720
+ init_goal_injection_diagnostics();
143721
+ init_goal_anchoring_audit();
142453
143722
  init_worktrunk_installer();
142454
143723
  init_worktrunk_failure_handler();
142455
143724
  init_usage_limit_detector();
@@ -157490,6 +158759,26 @@ var init_register_git_github = __esm({
157490
158759
  });
157491
158760
 
157492
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
+ }
157493
158782
  function normalizeOptionalBranch(value, fieldName) {
157494
158783
  if (value === void 0 || value === null) return void 0;
157495
158784
  if (typeof value !== "string") {
@@ -157506,22 +158795,8 @@ function resolveBranchSelection(selectionInput, fallbackBranch, fallbackBaseBran
157506
158795
  if (selectionInput === void 0 || selectionInput === null) {
157507
158796
  return fallback2;
157508
158797
  }
157509
- if (typeof selectionInput !== "object" || Array.isArray(selectionInput)) {
157510
- throw badRequest("branchSelection must be an object");
157511
- }
158798
+ const mode = getBranchSelectionMode(selectionInput);
157512
158799
  const selection = selectionInput;
157513
- const mode = typeof selection.mode === "string" ? selection.mode : void 0;
157514
- if (!mode) {
157515
- throw badRequest("branchSelection.mode is required");
157516
- }
157517
- if (![
157518
- "project-default",
157519
- "auto-new",
157520
- "existing",
157521
- "custom-new"
157522
- ].includes(mode)) {
157523
- throw badRequest("branchSelection.mode must be one of: project-default, auto-new, existing, custom-new");
157524
- }
157525
158800
  const baseBranch = normalizeOptionalBranch(selection.baseBranch, "branchSelection.baseBranch");
157526
158801
  if (mode === "project-default") {
157527
158802
  return { branch: void 0, baseBranch };
@@ -157557,6 +158832,11 @@ function resolveBranchAssignmentContext(input) {
157557
158832
  function sanitizeSegment(input) {
157558
158833
  return input.trim().toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-/.]+|[-/.]+$/g, "").slice(0, 48);
157559
158834
  }
158835
+ function deriveAutoTaskBranch(taskId, shortName) {
158836
+ const base = `fusion/${taskId.toLowerCase()}`;
158837
+ const segment = sanitizeSegment(shortName ?? "");
158838
+ return segment ? `${base}-${segment}` : base;
158839
+ }
157560
158840
  function derivePerTaskBranch(sharedBranch, taskSegment) {
157561
158841
  const base = normalizeOptionalBranch(sharedBranch, "sharedBranch");
157562
158842
  if (!base) return void 0;
@@ -158196,6 +159476,7 @@ function registerTaskWorkflowRoutes(ctx, deps) {
158196
159476
  }
158197
159477
  } : void 0;
158198
159478
  const normalizedSource = source && typeof source === "object" && "sourceType" in source && typeof source.sourceType === "string" ? source : { sourceType: "api" };
159479
+ const requestedBranchMode = getBranchSelectionMode(branchSelection);
158199
159480
  const { branch: normalizedBranch, baseBranch: normalizedBaseBranch } = resolveBranchSelection(branchSelection, branch, baseBranch);
158200
159481
  let validatedGithubTracking;
158201
159482
  if (githubTracking !== void 0 && githubTracking !== null) {
@@ -158452,8 +159733,14 @@ ${normalizedDescription}`;
158452
159733
  createInput,
158453
159734
  { onSummarize, settings: { autoSummarizeTitles: settings.autoSummarizeTitles } }
158454
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;
158455
159742
  const deterministicReconcile = await reconcileDeterministicDuplicate(scopedStore, {
158456
- createdTask: task,
159743
+ createdTask: taskWithAutoBranch,
158457
159744
  fingerprint: bypassDuplicateCheck === true ? null : contentFingerprint,
158458
159745
  windowMs: 6e4,
158459
159746
  logger: runtimeLogger
@@ -158466,8 +159753,8 @@ ${normalizedDescription}`;
158466
159753
  try {
158467
159754
  await scopedStore.recordActivity({
158468
159755
  type: "task:duplicate-warning-overridden",
158469
- taskId: task.id,
158470
- taskTitle: task.title,
159756
+ taskId: taskWithAutoBranch.id,
159757
+ taskTitle: taskWithAutoBranch.title,
158471
159758
  details: `Created despite ${acknowledgedDuplicateIds.length} possible duplicate(s): ${acknowledgedDuplicateIds.join(", ")}`,
158472
159759
  metadata: {
158473
159760
  acknowledgedDuplicateIds,
@@ -158481,7 +159768,7 @@ ${normalizedDescription}`;
158481
159768
  });
158482
159769
  }
158483
159770
  }
158484
- res.status(201).json(task);
159771
+ res.status(201).json(taskWithAutoBranch);
158485
159772
  return;
158486
159773
  } finally {
158487
159774
  deterministicGuard.releaseLock();
@@ -159609,7 +160896,7 @@ ${normalizedDescription}`;
159609
160896
  router.patch("/tasks/:id", async (req, res) => {
159610
160897
  try {
159611
160898
  const { store: scopedStore } = await getProjectContext3(req);
159612
- 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;
159613
160900
  const hasBodyField = (field) => Object.prototype.hasOwnProperty.call(req.body, field);
159614
160901
  const validateModelField = (value, name) => {
159615
160902
  if (value === void 0) return void 0;
@@ -159650,6 +160937,9 @@ ${normalizedDescription}`;
159650
160937
  if (hasBodyField("noCommitsExpected") && noCommitsExpected !== void 0 && typeof noCommitsExpected !== "boolean") {
159651
160938
  throw new Error("noCommitsExpected must be a boolean");
159652
160939
  }
160940
+ if (hasBodyField("autoMerge") && autoMerge !== void 0 && autoMerge !== null && typeof autoMerge !== "boolean") {
160941
+ throw new Error("autoMerge must be a boolean");
160942
+ }
159653
160943
  let validatedSourceIssue;
159654
160944
  if (hasBodyField("sourceIssue")) {
159655
160945
  if (sourceIssue === null) {
@@ -159771,6 +161061,7 @@ ${normalizedDescription}`;
159771
161061
  if (dependencies !== void 0) updates.dependencies = dependencies;
159772
161062
  if (enabledWorkflowSteps !== void 0) updates.enabledWorkflowSteps = enabledWorkflowSteps;
159773
161063
  if (hasBodyField("noCommitsExpected")) updates.noCommitsExpected = noCommitsExpected;
161064
+ if (hasBodyField("autoMerge")) updates.autoMerge = autoMerge === null ? void 0 : autoMerge;
159774
161065
  if (hasBodyField("modelProvider")) updates.modelProvider = validatedModelProvider;
159775
161066
  if (hasBodyField("modelId")) updates.modelId = validatedModelId;
159776
161067
  if (hasBodyField("validatorModelProvider")) updates.validatorModelProvider = validatedValidatorModelProvider;
@@ -198039,15 +199330,35 @@ async function resolveAuditCommitSha(taskId, scopedStore) {
198039
199330
  }
198040
199331
  return void 0;
198041
199332
  }
198042
- 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;
198043
199346
  const existing = task.mergeDetails?.commitSha?.trim();
198044
- if (existing) return existing;
199347
+ if (existing) {
199348
+ if (!includeBaseCommitSha && baseCommitSha && existing === baseCommitSha) return void 0;
199349
+ return existing;
199350
+ }
198045
199351
  const auditSha = await resolveAuditCommitSha(task.id, scopedStore);
198046
- if (auditSha) return auditSha;
199352
+ if (auditSha) {
199353
+ if (!includeBaseCommitSha && baseCommitSha && auditSha === baseCommitSha) return void 0;
199354
+ return auditSha;
199355
+ }
198047
199356
  if (!task.lineageId) return void 0;
198048
199357
  const associations = await scopedStore.getTaskCommitAssociationsByLineageId(task.lineageId);
198049
199358
  for (const association of associations) {
198050
- 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)) {
198051
199362
  return association.commitSha;
198052
199363
  }
198053
199364
  }
@@ -198266,7 +199577,15 @@ function registerSessionDiffRoutes(router, deps) {
198266
199577
  return;
198267
199578
  }
198268
199579
  if (task.column === "done") {
199580
+ const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
198269
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
+ }
198270
199589
  const doneTaskForDiff = resolvedMergeSha ? {
198271
199590
  ...task,
198272
199591
  mergeDetails: {
@@ -198477,7 +199796,15 @@ function registerSessionDiffRoutes(router, deps) {
198477
199796
  return;
198478
199797
  }
198479
199798
  if (task.column === "done") {
199799
+ const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
198480
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
+ }
198481
199808
  const doneTaskForDiff = resolvedMergeSha ? {
198482
199809
  ...task,
198483
199810
  mergeDetails: {
@@ -214951,21 +216278,48 @@ var init_register_auth_routes = __esm({
214951
216278
  let autoPromptConsumed = false;
214952
216279
  let authResolve;
214953
216280
  let authReject;
216281
+ let authSettled = false;
214954
216282
  const authUrlPromise = new Promise((resolve64, reject2) => {
214955
216283
  authResolve = resolve64;
214956
216284
  authReject = reject2;
214957
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;
214958
216297
  const loginPromise = storage.login(provider, {
214959
216298
  onAuth: (info) => {
214960
- const parsedUserCode = provider === "github-copilot" && info.instructions ? parseGitHubCopilotDeviceCode(info.instructions) : void 0;
214961
- const deviceCode = parsedUserCode ? {
214962
- userCode: parsedUserCode,
214963
- verificationUri: info.url
214964
- } : void 0;
214965
- 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({
214966
216309
  url: info.url,
214967
216310
  instructions: appendManualCodeHint(info.instructions, provider, origin),
214968
- 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
214969
216323
  });
214970
216324
  },
214971
216325
  onPrompt: async (_prompt) => {
@@ -214982,14 +216336,20 @@ var init_register_auth_routes = __esm({
214982
216336
  onProgress: () => {
214983
216337
  },
214984
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
+ },
214985
216345
  signal: abortController.signal
214986
216346
  });
214987
216347
  const timeout2 = setTimeout(() => {
214988
- authReject(new Error("Login initiation timed out"));
216348
+ rejectAuthInfo(new Error("Login initiation timed out"));
214989
216349
  }, 3e4);
214990
216350
  loginPromise.then(() => {
214991
216351
  }).catch((err) => {
214992
- authReject(err);
216352
+ rejectAuthInfo(err instanceof Error ? err : new Error(String(err)));
214993
216353
  }).finally(() => {
214994
216354
  clearTimeout(timeout2);
214995
216355
  loginInProgress.delete(provider);
@@ -215906,6 +217266,32 @@ function validateBoolean(value, fieldName) {
215906
217266
  }
215907
217267
  return value;
215908
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
+ }
215909
217295
  function validateOrderedIds(body) {
215910
217296
  if (!body || typeof body !== "object") {
215911
217297
  throw new Error("Request body must contain orderedIds array");
@@ -216005,22 +217391,20 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216005
217391
  router.post(
216006
217392
  "/",
216007
217393
  catchTypedHandler(async (req, res) => {
216008
- const { title, description, autoAdvance, autopilotEnabled, baseBranch } = req.body;
217394
+ const { title, description, autoAdvance, baseBranch, branchStrategy } = req.body;
216009
217395
  const validatedTitle = validateTitle(title);
216010
217396
  const validatedDescription = validateDescription2(description);
216011
217397
  const input = {
216012
217398
  title: validatedTitle,
216013
217399
  description: validatedDescription,
216014
- baseBranch: validateDescription2(baseBranch)
217400
+ baseBranch: validateDescription2(baseBranch),
217401
+ branchStrategy: validateMissionBranchStrategy(branchStrategy)
216015
217402
  };
216016
217403
  const mission = missionStore.createMission(input);
216017
217404
  const updates = {};
216018
217405
  if (autoAdvance !== void 0) {
216019
217406
  updates.autoAdvance = validateBoolean(autoAdvance, "autoAdvance");
216020
217407
  }
216021
- if (autopilotEnabled !== void 0) {
216022
- updates.autopilotEnabled = validateBoolean(autopilotEnabled, "autopilotEnabled");
216023
- }
216024
217408
  if (Object.keys(updates).length > 0) {
216025
217409
  const updatedMission = missionStore.updateMission(mission.id, updates);
216026
217410
  res.status(201).json(updatedMission);
@@ -216367,18 +217751,11 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216367
217751
  status: "pending"
216368
217752
  });
216369
217753
  for (const featureData of sliceData.features ?? []) {
216370
- const feature = missionStore.addFeature(slice.id, {
217754
+ missionStore.addFeature(slice.id, {
216371
217755
  title: featureData.title,
216372
217756
  description: featureData.description,
216373
217757
  acceptanceCriteria: featureData.acceptanceCriteria
216374
217758
  });
216375
- const assertionText = featureData.acceptanceCriteria || featureData.description || `Verify implementation of: ${featureData.title}`;
216376
- const assertion = missionStore.addContractAssertion(milestone.id, {
216377
- title: featureData.title,
216378
- assertion: assertionText,
216379
- status: "pending"
216380
- });
216381
- missionStore.linkFeatureToAssertion(feature.id, assertion.id);
216382
217759
  }
216383
217760
  }
216384
217761
  missionStore.applyDerivedMilestoneAcceptanceCriteria(milestone.id);
@@ -216418,7 +217795,7 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216418
217795
  "/:missionId",
216419
217796
  catchTypedHandler(async (req, res) => {
216420
217797
  const { missionId } = req.params;
216421
- const { title, description, status, autoAdvance, autopilotEnabled, baseBranch } = req.body;
217798
+ const { title, description, status, autoAdvance, autopilotEnabled, baseBranch, branchStrategy } = req.body;
216422
217799
  if (!validateMissionId(missionId)) {
216423
217800
  throw badRequest("Invalid mission ID format");
216424
217801
  }
@@ -216441,11 +217818,18 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
216441
217818
  if (baseBranch !== void 0) {
216442
217819
  updates.baseBranch = validateDescription2(baseBranch);
216443
217820
  }
217821
+ if (branchStrategy !== void 0) {
217822
+ updates.branchStrategy = validateMissionBranchStrategy(branchStrategy);
217823
+ }
216444
217824
  if (Object.keys(updates).length === 0) {
216445
217825
  throw badRequest("No valid fields to update");
216446
217826
  }
216447
217827
  try {
217828
+ const existingMission = missionStore.getMission(missionId);
216448
217829
  const mission = missionStore.updateMission(missionId, updates);
217830
+ if (missionAutopilot && updates.autopilotEnabled === true && existingMission?.autopilotEnabled !== true) {
217831
+ missionAutopilot.watchMission(missionId);
217832
+ }
216449
217833
  res.json(mission);
216450
217834
  } catch (err) {
216451
217835
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -248129,6 +249513,7 @@ var init_mission = __esm({
248129
249513
  var goals_exports = {};
248130
249514
  __export(goals_exports, {
248131
249515
  runGoalsArchive: () => runGoalsArchive,
249516
+ runGoalsCitations: () => runGoalsCitations,
248132
249517
  runGoalsCreate: () => runGoalsCreate,
248133
249518
  runGoalsList: () => runGoalsList
248134
249519
  });
@@ -248207,6 +249592,31 @@ async function runGoalsCreate(titleArg, descriptionArg, projectName) {
248207
249592
  throw error;
248208
249593
  }
248209
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
+ }
248210
249620
  async function runGoalsArchive(idArg, projectName) {
248211
249621
  if (!idArg) {
248212
249622
  console.error("Usage: fn goals archive <id>");
@@ -251437,7 +252847,7 @@ async function loadCommandHandlers() {
251437
252847
  const { runBackupCreate: runBackupCreate2, runBackupList: runBackupList2, runBackupRestore: runBackupRestore2, runBackupCleanup: runBackupCleanup2 } = await Promise.resolve().then(() => (init_backup2(), backup_exports));
251438
252848
  const { runMemoryBackupCreate: runMemoryBackupCreate2, runMemoryBackupList: runMemoryBackupList2, runMemoryBackupRestore: runMemoryBackupRestore2 } = await Promise.resolve().then(() => (init_memory_backup2(), memory_backup_exports));
251439
252849
  const { runMissionCreate: runMissionCreate2, runMissionList: runMissionList2, runMissionShow: runMissionShow2, runMissionDelete: runMissionDelete2, runMissionActivateSlice: runMissionActivateSlice2 } = await Promise.resolve().then(() => (init_mission(), mission_exports));
251440
- 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));
251441
252851
  const { runProjectList: runProjectList2, runProjectAdd: runProjectAdd2, runProjectRemove: runProjectRemove2, runProjectShow: runProjectShow2, runProjectInfo: runProjectInfo2, runProjectSetDefault: runProjectSetDefault2, runProjectDetect: runProjectDetect2 } = await Promise.resolve().then(() => (init_project(), project_exports));
251442
252852
  const { runNodeList: runNodeList2, runNodeConnect: runNodeConnect2, runNodeDisconnect: runNodeDisconnect2, runNodeShow: runNodeShow2, runNodeHealth: runNodeHealth2, runMeshStatus: runMeshStatus2 } = await Promise.resolve().then(() => (init_node(), node_exports));
251443
252853
  const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -251505,6 +252915,7 @@ async function loadCommandHandlers() {
251505
252915
  runGoalsList: runGoalsList2,
251506
252916
  runGoalsCreate: runGoalsCreate2,
251507
252917
  runGoalsArchive: runGoalsArchive2,
252918
+ runGoalsCitations: runGoalsCitations2,
251508
252919
  runProjectList: runProjectList2,
251509
252920
  runProjectAdd: runProjectAdd2,
251510
252921
  runProjectRemove: runProjectRemove2,
@@ -251624,6 +253035,7 @@ PR:
251624
253035
  fn goals list [--status STATE] List goals (default: active)
251625
253036
  fn goals create [title] [desc] Create a new goal
251626
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)
251627
253039
  fn project list | ls [--json] List all registered projects
251628
253040
  fn project add [name] [path] [opts] Register a new project
251629
253041
  fn project remove | rm <name> [--force]
@@ -251876,6 +253288,7 @@ async function main() {
251876
253288
  runGoalsList: runGoalsList2,
251877
253289
  runGoalsCreate: runGoalsCreate2,
251878
253290
  runGoalsArchive: runGoalsArchive2,
253291
+ runGoalsCitations: runGoalsCitations2,
251879
253292
  runProjectList: runProjectList2,
251880
253293
  runProjectAdd: runProjectAdd2,
251881
253294
  runProjectRemove: runProjectRemove2,
@@ -252641,9 +254054,21 @@ async function main() {
252641
254054
  await runGoalsArchive2(id, projectName);
252642
254055
  break;
252643
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
+ }
252644
254069
  default:
252645
254070
  console.error(`Unknown subcommand: goals ${subcommand || ""}`);
252646
- console.log("Try: fn goals list | create | archive");
254071
+ console.log("Try: fn goals list | create | archive | citations");
252647
254072
  process.exit(1);
252648
254073
  }
252649
254074
  break;