@runfusion/fusion 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/bin.js +1510 -234
  2. package/dist/client/.vite/manifest.json +58 -58
  3. package/dist/client/assets/AgentDetailView-CquLtIia.js +18 -0
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-niLSqGAE.js → AgentPermissionPolicyEditor-Dk-oEs1X.js} +1 -1
  5. package/dist/client/assets/AgentsView-BZVOpQ05.js +29 -0
  6. package/dist/client/assets/ChatView-CErtz86Y.js +6 -0
  7. package/dist/client/assets/{DevServerView-0Gb44WOb.js → DevServerView-BRA00XoT.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-BSQA0XM9.js → DirectoryPicker-D496NTjZ.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-XZbaqEFF.js → DocumentsView-DIRlgdeB.js} +1 -1
  10. package/dist/client/assets/{EvalsView-CJNtXxX9.js → EvalsView-By5mtr80.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DR8E3i36.js → ExperimentalAgentOnboardingModal-Br57oNdX.js} +1 -1
  12. package/dist/client/assets/GoalsView-B65urz9k.css +1 -0
  13. package/dist/client/assets/{GoalsView-CN1zQ_Ax.js → GoalsView-COI14CWJ.js} +1 -1
  14. package/dist/client/assets/{InsightsView-D_jRaMyn.js → InsightsView-Hm7vX2lr.js} +2 -2
  15. package/dist/client/assets/{MemoryView-D90vdPnp.js → MemoryView-BLozyYur.js} +1 -1
  16. package/dist/client/assets/{NodesView-C1zuedAe.js → NodesView-bXC6WoE2.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-DBJDDVfM.js → PiExtensionsManager-DEiqcMkx.js} +2 -2
  18. package/dist/client/assets/{PluginManager-Ceg64P2Z.js → PluginManager-l4224zxk.js} +1 -1
  19. package/dist/client/assets/{ResearchView-XRwWdczc.js → ResearchView-CpsR1Q56.js} +1 -1
  20. package/dist/client/assets/{SecretsView-TCrKyCme.js → SecretsView-Bwme1zZI.js} +1 -1
  21. package/dist/client/assets/{SettingsModal-C1TczsEr.js → SettingsModal-32vUNWRe.js} +1 -1
  22. package/dist/client/assets/SettingsModal-D_uIvf6n.css +1 -0
  23. package/dist/client/assets/SettingsModal-DdrXxMPW.js +31 -0
  24. package/dist/client/assets/{SetupWizardModal-D6moSTT3.js → SetupWizardModal-BFD9E0H_.js} +1 -1
  25. package/dist/client/assets/{SkillsView-BDHLPVBi.js → SkillsView-CZMLY7F_.js} +1 -1
  26. package/dist/client/assets/{StashRecoveryView-DZGa591R.js → StashRecoveryView-jgZeWbZo.js} +1 -1
  27. package/dist/client/assets/{TodoView-nr_SnCJv.js → TodoView-DXdnOymj.js} +2 -2
  28. package/dist/client/assets/{dashboard-view-0XeZ1FTR.js → dashboard-view-H8SnaCPP.js} +2 -2
  29. package/dist/client/assets/{folder-open-iURwQbEg.js → folder-open-Dh5_gv6L.js} +1 -1
  30. package/dist/client/assets/index-B9hAW3eU.css +1 -0
  31. package/dist/client/assets/index-w1dPHtmq.js +742 -0
  32. package/dist/client/assets/{upload-Beqg4S4L.js → upload-ntU2YHPl.js} +1 -1
  33. package/dist/client/assets/{users-C2o86ZCR.js → users-BWK61s9U.js} +1 -1
  34. package/dist/client/index.html +40 -3
  35. package/dist/client/version.json +1 -1
  36. package/dist/droid-cli/package.json +1 -1
  37. package/dist/extension.js +1326 -182
  38. package/dist/pi-claude-cli/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  40. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  42. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  43. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  44. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  46. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  47. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  48. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  49. package/package.json +1 -1
  50. package/dist/client/assets/AgentDetailView-T_AHPxcG.js +0 -18
  51. package/dist/client/assets/AgentsView-CQhaLHz_.js +0 -29
  52. package/dist/client/assets/ChatView-B60kYfdk.js +0 -6
  53. package/dist/client/assets/GoalsView-CtWi0fxE.css +0 -1
  54. package/dist/client/assets/SettingsModal-0gevFhXV.css +0 -1
  55. package/dist/client/assets/SettingsModal-B8fRuWdQ.js +0 -31
  56. package/dist/client/assets/index-CjuQj4qe.js +0 -742
  57. package/dist/client/assets/index-DmrpDn7c.css +0 -1
package/dist/extension.js CHANGED
@@ -4672,7 +4672,7 @@ var init_db = __esm({
4672
4672
  DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
4673
4673
  DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
4674
4674
  DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
4675
- SCHEMA_VERSION = 92;
4675
+ SCHEMA_VERSION = 99;
4676
4676
  SCHEMA_SQL = `
4677
4677
  -- Tasks table with JSON columns for nested data
4678
4678
  CREATE TABLE IF NOT EXISTS tasks (
@@ -4694,6 +4694,7 @@ CREATE TABLE IF NOT EXISTS tasks (
4694
4694
  pausedReason TEXT,
4695
4695
  baseBranch TEXT,
4696
4696
  branch TEXT,
4697
+ autoMerge INTEGER,
4697
4698
  executionStartBranch TEXT,
4698
4699
  baseCommitSha TEXT,
4699
4700
  modelPresetId TEXT,
@@ -4705,12 +4706,16 @@ CREATE TABLE IF NOT EXISTS tasks (
4705
4706
  planningModelId TEXT,
4706
4707
  mergeRetries INTEGER,
4707
4708
  workflowStepRetries INTEGER,
4709
+ resumeLimboCount INTEGER DEFAULT 0,
4710
+ resumeLimboTipSha TEXT,
4711
+ resumeLimboStepSignature TEXT,
4708
4712
  recoveryRetryCount INTEGER,
4709
4713
  taskDoneRetryCount INTEGER DEFAULT 0,
4710
4714
  worktreeSessionRetryCount INTEGER DEFAULT 0,
4711
4715
  completionHandoffLimboRecoveryCount INTEGER DEFAULT 0,
4712
4716
  mergeConflictBounceCount INTEGER DEFAULT 0,
4713
4717
  mergeAuditBounceCount INTEGER DEFAULT 0,
4718
+ mergeTransientRetryCount INTEGER DEFAULT 0,
4714
4719
  nextRecoveryAt TEXT,
4715
4720
  error TEXT,
4716
4721
  summary TEXT,
@@ -5220,11 +5225,31 @@ CREATE TABLE IF NOT EXISTS missions (
5220
5225
  status TEXT NOT NULL,
5221
5226
  interviewState TEXT NOT NULL,
5222
5227
  baseBranch TEXT,
5228
+ branchStrategy TEXT,
5223
5229
  autoAdvance INTEGER DEFAULT 0,
5230
+ autoMerge INTEGER,
5224
5231
  createdAt TEXT NOT NULL,
5225
5232
  updatedAt TEXT NOT NULL
5226
5233
  );
5227
5234
 
5235
+ CREATE TABLE IF NOT EXISTS branch_groups (
5236
+ id TEXT PRIMARY KEY,
5237
+ sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
5238
+ sourceId TEXT NOT NULL,
5239
+ branchName TEXT NOT NULL UNIQUE,
5240
+ worktreePath TEXT,
5241
+ autoMerge INTEGER NOT NULL DEFAULT 0,
5242
+ prState TEXT NOT NULL DEFAULT 'none' CHECK (prState IN ('none','open','merged','closed')),
5243
+ prUrl TEXT,
5244
+ prNumber INTEGER,
5245
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','finalized','abandoned')),
5246
+ createdAt INTEGER NOT NULL,
5247
+ updatedAt INTEGER NOT NULL,
5248
+ closedAt INTEGER
5249
+ );
5250
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsSource ON branch_groups(sourceType, sourceId);
5251
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsBranchName ON branch_groups(branchName);
5252
+
5228
5253
  -- Goals table (strategic intent across mission timelines)
5229
5254
  CREATE TABLE IF NOT EXISTS goals (
5230
5255
  id TEXT PRIMARY KEY,
@@ -5236,6 +5261,22 @@ CREATE TABLE IF NOT EXISTS goals (
5236
5261
  );
5237
5262
  CREATE INDEX IF NOT EXISTS idxGoalsStatus ON goals(status);
5238
5263
 
5264
+ CREATE TABLE IF NOT EXISTS goal_citations (
5265
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5266
+ goalId TEXT NOT NULL,
5267
+ agentId TEXT NOT NULL,
5268
+ taskId TEXT,
5269
+ surface TEXT NOT NULL,
5270
+ sourceRef TEXT NOT NULL,
5271
+ snippet TEXT NOT NULL,
5272
+ timestamp TEXT NOT NULL
5273
+ );
5274
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsGoalId ON goal_citations(goalId);
5275
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsAgentId ON goal_citations(agentId);
5276
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsTimestamp ON goal_citations(timestamp);
5277
+ CREATE UNIQUE INDEX IF NOT EXISTS uxGoalCitationsDedup
5278
+ ON goal_citations(goalId, surface, sourceRef);
5279
+
5239
5280
  -- Milestones table (phases within a mission)
5240
5281
  CREATE TABLE IF NOT EXISTS milestones (
5241
5282
  id TEXT PRIMARY KEY,
@@ -5532,6 +5573,7 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
5532
5573
  assertion: "TEXT NOT NULL",
5533
5574
  status: "TEXT NOT NULL DEFAULT 'pending'",
5534
5575
  orderIndex: "INTEGER NOT NULL DEFAULT 0",
5576
+ sourceFeatureId: "TEXT",
5535
5577
  createdAt: "TEXT NOT NULL",
5536
5578
  updatedAt: "TEXT NOT NULL"
5537
5579
  },
@@ -7335,6 +7377,11 @@ This means a caller passed a .fusion directory where a project root was expected
7335
7377
  }
7336
7378
  if (version < 75) {
7337
7379
  this.applyMigration(75, () => {
7380
+ this.addColumnIfMissing("tasks", "mergeTransientRetryCount", "INTEGER DEFAULT 0");
7381
+ });
7382
+ }
7383
+ if (version < 76) {
7384
+ this.applyMigration(76, () => {
7338
7385
  this.db.exec(`
7339
7386
  CREATE TABLE IF NOT EXISTS experiment_sessions (
7340
7387
  id TEXT PRIMARY KEY,
@@ -7375,38 +7422,38 @@ This means a caller passed a .fusion directory where a project root was expected
7375
7422
  this.db.exec(`CREATE INDEX IF NOT EXISTS idxExperimentRecordsType ON experiment_session_records(sessionId, type)`);
7376
7423
  });
7377
7424
  }
7378
- if (version < 76) {
7379
- this.applyMigration(76, () => {
7425
+ if (version < 77) {
7426
+ this.applyMigration(77, () => {
7380
7427
  this.addColumnIfMissing("workflow_steps", "gateMode", "TEXT NOT NULL DEFAULT 'advisory'");
7381
7428
  this.db.exec("UPDATE workflow_steps SET gateMode = 'advisory'");
7382
7429
  });
7383
7430
  }
7384
- if (version < 77) {
7385
- this.applyMigration(77, () => {
7431
+ if (version < 78) {
7432
+ this.applyMigration(78, () => {
7386
7433
  this.addColumnIfMissing("tasks", "tokenBudgetSoftAlertedAt", "TEXT");
7387
7434
  this.addColumnIfMissing("tasks", "tokenBudgetHardAlertedAt", "TEXT");
7388
7435
  this.addColumnIfMissing("tasks", "tokenBudgetOverride", "TEXT");
7389
7436
  });
7390
7437
  }
7391
- if (version < 78) {
7392
- this.applyMigration(78, () => {
7438
+ if (version < 79) {
7439
+ this.applyMigration(79, () => {
7393
7440
  this.addColumnIfMissing("tasks", "branchConflictRecoveryCount", "INTEGER DEFAULT 0");
7394
7441
  this.addColumnIfMissing("tasks", "reviewerContextRetryCount", "INTEGER DEFAULT 0");
7395
7442
  this.addColumnIfMissing("tasks", "reviewerFallbackRetryCount", "INTEGER DEFAULT 0");
7396
7443
  });
7397
7444
  }
7398
- if (version < 79) {
7399
- this.applyMigration(79, () => {
7400
- this.addColumnIfMissing("tasks", "overlapBlockedBy", "TEXT");
7401
- });
7402
- }
7403
7445
  if (version < 80) {
7404
7446
  this.applyMigration(80, () => {
7405
- this.addColumnIfMissing("milestones", "acceptanceCriteria", "TEXT");
7447
+ this.addColumnIfMissing("tasks", "overlapBlockedBy", "TEXT");
7406
7448
  });
7407
7449
  }
7408
7450
  if (version < 81) {
7409
7451
  this.applyMigration(81, () => {
7452
+ this.addColumnIfMissing("milestones", "acceptanceCriteria", "TEXT");
7453
+ });
7454
+ }
7455
+ if (version < 82) {
7456
+ this.applyMigration(82, () => {
7410
7457
  this.addColumnIfMissing("tasks", "firstExecutionAt", "TEXT");
7411
7458
  this.addColumnIfMissing("tasks", "cumulativeActiveMs", "INTEGER");
7412
7459
  if (this.hasColumn("tasks", "executionStartedAt")) {
@@ -7419,13 +7466,13 @@ This means a caller passed a .fusion directory where a project root was expected
7419
7466
  }
7420
7467
  });
7421
7468
  }
7422
- if (version < 82) {
7423
- this.applyMigration(82, () => {
7469
+ if (version < 83) {
7470
+ this.applyMigration(83, () => {
7424
7471
  this.addColumnIfMissing("tasks", "worktreeSessionRetryCount", "INTEGER DEFAULT 0");
7425
7472
  });
7426
7473
  }
7427
- if (version < 83) {
7428
- this.applyMigration(83, () => {
7474
+ if (version < 84) {
7475
+ this.applyMigration(84, () => {
7429
7476
  if (!this.hasTable("secrets")) {
7430
7477
  this.db.exec(`
7431
7478
  CREATE TABLE secrets (
@@ -7449,8 +7496,8 @@ This means a caller passed a .fusion directory where a project root was expected
7449
7496
  }
7450
7497
  });
7451
7498
  }
7452
- if (version < 84) {
7453
- this.applyMigration(84, () => {
7499
+ if (version < 85) {
7500
+ this.applyMigration(85, () => {
7454
7501
  if (!this.hasColumn("tasks", "title")) {
7455
7502
  console.log("[title-id-drift] db.ts migration normalized 0 active titles");
7456
7503
  return;
@@ -7472,24 +7519,24 @@ This means a caller passed a .fusion directory where a project root was expected
7472
7519
  console.log(`[title-id-drift] db.ts migration normalized ${normalizedCount} active titles`);
7473
7520
  });
7474
7521
  }
7475
- if (version < 85) {
7476
- this.applyMigration(85, () => {
7477
- this.addColumnIfMissing("tasks", "completionHandoffLimboRecoveryCount", "INTEGER DEFAULT 0");
7478
- });
7479
- }
7480
7522
  if (version < 86) {
7481
7523
  this.applyMigration(86, () => {
7482
- this.addColumnIfMissing("tasks", "prInfos", "TEXT");
7524
+ this.addColumnIfMissing("tasks", "completionHandoffLimboRecoveryCount", "INTEGER DEFAULT 0");
7483
7525
  });
7484
7526
  }
7485
7527
  if (version < 87) {
7486
7528
  this.applyMigration(87, () => {
7487
- this.addColumnIfMissing("tasks", "deletedAt", "TEXT");
7488
- this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_deletedAt ON tasks(deletedAt)");
7529
+ this.addColumnIfMissing("tasks", "prInfos", "TEXT");
7489
7530
  });
7490
7531
  }
7491
7532
  if (version < 88) {
7492
7533
  this.applyMigration(88, () => {
7534
+ this.addColumnIfMissing("tasks", "deletedAt", "TEXT");
7535
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_deletedAt ON tasks(deletedAt)");
7536
+ });
7537
+ }
7538
+ if (version < 89) {
7539
+ this.applyMigration(89, () => {
7493
7540
  this.addColumnIfMissing("tasks", "allowResurrection", "INTEGER DEFAULT 0");
7494
7541
  try {
7495
7542
  const taskColumns = this.getTableColumns("tasks");
@@ -7514,8 +7561,8 @@ This means a caller passed a .fusion directory where a project root was expected
7514
7561
  }
7515
7562
  });
7516
7563
  }
7517
- if (version < 89) {
7518
- this.applyMigration(89, () => {
7564
+ if (version < 90) {
7565
+ this.applyMigration(90, () => {
7519
7566
  this.db.exec(`
7520
7567
  CREATE TABLE IF NOT EXISTS mergeQueue (
7521
7568
  taskId TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
@@ -7538,18 +7585,18 @@ This means a caller passed a .fusion directory where a project root was expected
7538
7585
  `);
7539
7586
  });
7540
7587
  }
7541
- if (version < 90) {
7542
- this.applyMigration(90, () => {
7543
- this.addColumnIfMissing("tasks", "scopeAutoWiden", "TEXT DEFAULT '[]'");
7544
- });
7545
- }
7546
7588
  if (version < 91) {
7547
7589
  this.applyMigration(91, () => {
7548
- this.addColumnIfMissing("missions", "baseBranch", "TEXT");
7590
+ this.addColumnIfMissing("tasks", "scopeAutoWiden", "TEXT DEFAULT '[]'");
7549
7591
  });
7550
7592
  }
7551
7593
  if (version < 92) {
7552
7594
  this.applyMigration(92, () => {
7595
+ this.addColumnIfMissing("missions", "baseBranch", "TEXT");
7596
+ });
7597
+ }
7598
+ if (version < 93) {
7599
+ this.applyMigration(93, () => {
7553
7600
  this.db.exec(`
7554
7601
  CREATE TABLE IF NOT EXISTS goals (
7555
7602
  id TEXT PRIMARY KEY,
@@ -7566,6 +7613,88 @@ This means a caller passed a .fusion directory where a project root was expected
7566
7613
  `);
7567
7614
  });
7568
7615
  }
7616
+ if (version < 94) {
7617
+ this.applyMigration(94, () => {
7618
+ this.db.exec(`
7619
+ CREATE TABLE IF NOT EXISTS goal_citations (
7620
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7621
+ goalId TEXT NOT NULL,
7622
+ agentId TEXT NOT NULL,
7623
+ taskId TEXT,
7624
+ surface TEXT NOT NULL,
7625
+ sourceRef TEXT NOT NULL,
7626
+ snippet TEXT NOT NULL,
7627
+ timestamp TEXT NOT NULL
7628
+ )
7629
+ `);
7630
+ this.db.exec(`
7631
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsGoalId
7632
+ ON goal_citations(goalId)
7633
+ `);
7634
+ this.db.exec(`
7635
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsAgentId
7636
+ ON goal_citations(agentId)
7637
+ `);
7638
+ this.db.exec(`
7639
+ CREATE INDEX IF NOT EXISTS idxGoalCitationsTimestamp
7640
+ ON goal_citations(timestamp)
7641
+ `);
7642
+ this.db.exec(`
7643
+ CREATE UNIQUE INDEX IF NOT EXISTS uxGoalCitationsDedup
7644
+ ON goal_citations(goalId, surface, sourceRef)
7645
+ `);
7646
+ });
7647
+ }
7648
+ if (version < 96) {
7649
+ this.applyMigration(96, () => {
7650
+ this.db.exec(`
7651
+ CREATE TABLE IF NOT EXISTS branch_groups (
7652
+ id TEXT PRIMARY KEY,
7653
+ sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
7654
+ sourceId TEXT NOT NULL,
7655
+ branchName TEXT NOT NULL UNIQUE,
7656
+ worktreePath TEXT,
7657
+ autoMerge INTEGER NOT NULL DEFAULT 0,
7658
+ prState TEXT NOT NULL DEFAULT 'none' CHECK (prState IN ('none','open','merged','closed')),
7659
+ prUrl TEXT,
7660
+ prNumber INTEGER,
7661
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','finalized','abandoned')),
7662
+ createdAt INTEGER NOT NULL,
7663
+ updatedAt INTEGER NOT NULL,
7664
+ closedAt INTEGER
7665
+ )
7666
+ `);
7667
+ this.db.exec(`
7668
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsSource
7669
+ ON branch_groups(sourceType, sourceId)
7670
+ `);
7671
+ this.db.exec(`
7672
+ CREATE INDEX IF NOT EXISTS idxBranchGroupsBranchName
7673
+ ON branch_groups(branchName)
7674
+ `);
7675
+ this.addColumnIfMissing("tasks", "autoMerge", "INTEGER");
7676
+ this.addColumnIfMissing("missions", "autoMerge", "INTEGER");
7677
+ });
7678
+ }
7679
+ if (version < 97) {
7680
+ this.applyMigration(97, () => {
7681
+ if (this.hasTable("mission_contract_assertions")) {
7682
+ this.addColumnIfMissing("mission_contract_assertions", "sourceFeatureId", "TEXT");
7683
+ }
7684
+ });
7685
+ }
7686
+ if (version < 98) {
7687
+ this.applyMigration(98, () => {
7688
+ this.addColumnIfMissing("missions", "branchStrategy", "TEXT");
7689
+ });
7690
+ }
7691
+ if (version < 99) {
7692
+ this.applyMigration(99, () => {
7693
+ this.addColumnIfMissing("tasks", "resumeLimboCount", "INTEGER DEFAULT 0");
7694
+ this.addColumnIfMissing("tasks", "resumeLimboTipSha", "TEXT");
7695
+ this.addColumnIfMissing("tasks", "resumeLimboStepSignature", "TEXT");
7696
+ });
7697
+ }
7569
7698
  }
7570
7699
  /**
7571
7700
  * Run a single migration step inside a transaction and bump the version.
@@ -12302,6 +12431,53 @@ var init_db_migrate = __esm({
12302
12431
  }
12303
12432
  });
12304
12433
 
12434
+ // ../core/src/goal-citation-extractor.ts
12435
+ function extractGoalCitations(text) {
12436
+ const normalized = String(text ?? "");
12437
+ if (normalized.length === 0) {
12438
+ return [];
12439
+ }
12440
+ const matches = [];
12441
+ const seen = /* @__PURE__ */ new Set();
12442
+ const pattern = new RegExp(GOAL_ID_PATTERN.source, GOAL_ID_PATTERN.flags);
12443
+ let match;
12444
+ while ((match = pattern.exec(normalized)) !== null) {
12445
+ const goalId = match[0];
12446
+ if (seen.has(goalId)) {
12447
+ continue;
12448
+ }
12449
+ seen.add(goalId);
12450
+ matches.push({ goalId, index: match.index });
12451
+ }
12452
+ return matches;
12453
+ }
12454
+ function buildSnippet(text, index, max = GOAL_CITATION_SNIPPET_MAX) {
12455
+ const normalized = String(text ?? "");
12456
+ if (normalized.length === 0 || max <= 0) {
12457
+ return "";
12458
+ }
12459
+ const goalMatch = normalized.slice(Math.max(0, index)).match(/^G-[0-9A-Z]+(?:-[0-9A-Z]+)*/);
12460
+ const goalEnd = goalMatch ? index + goalMatch[0].length : index;
12461
+ let start = Math.max(0, index - Math.floor(max / 2));
12462
+ let end = Math.min(normalized.length, start + max);
12463
+ if (end < goalEnd) {
12464
+ end = Math.min(normalized.length, goalEnd);
12465
+ start = Math.max(0, end - max);
12466
+ } else {
12467
+ start = Math.max(0, end - max);
12468
+ }
12469
+ const snippet = normalized.slice(start, end).replace(/\s+/g, " ").trim();
12470
+ return snippet.length <= max ? snippet : snippet.slice(0, max).trim();
12471
+ }
12472
+ var GOAL_ID_PATTERN, GOAL_CITATION_SNIPPET_MAX;
12473
+ var init_goal_citation_extractor = __esm({
12474
+ "../core/src/goal-citation-extractor.ts"() {
12475
+ "use strict";
12476
+ GOAL_ID_PATTERN = /\bG-[0-9A-Z]+(?:-[0-9A-Z]+)*\b/g;
12477
+ GOAL_CITATION_SNIPPET_MAX = 200;
12478
+ }
12479
+ });
12480
+
12305
12481
  // ../core/src/duplicate-detection.ts
12306
12482
  import { createHash as createHash4 } from "node:crypto";
12307
12483
  function normalizeFingerprintPart(value) {
@@ -12558,6 +12734,18 @@ var init_duplicate_guard = __esm({
12558
12734
 
12559
12735
  // ../core/src/mission-store.ts
12560
12736
  import { EventEmitter as EventEmitter4 } from "node:events";
12737
+ function missionBranchStrategyDefaults(strategy) {
12738
+ if (!strategy) {
12739
+ return { assignmentMode: "shared" };
12740
+ }
12741
+ if (strategy.mode === "auto-per-task") {
12742
+ return { assignmentMode: "per-task-derived" };
12743
+ }
12744
+ if ((strategy.mode === "existing" || strategy.mode === "custom-new") && strategy.branchName?.trim()) {
12745
+ return { branch: strategy.branchName.trim(), assignmentMode: "shared" };
12746
+ }
12747
+ return { assignmentMode: "shared" };
12748
+ }
12561
12749
  function deriveMilestoneAcceptanceCriteriaFromFeatures(features) {
12562
12750
  const lines = features.map((feature) => {
12563
12751
  const acceptance = feature.acceptanceCriteria?.trim();
@@ -12606,6 +12794,14 @@ var init_mission_store = __esm({
12606
12794
  * Convert a database row to a Mission object.
12607
12795
  */
12608
12796
  rowToMission(row) {
12797
+ let branchStrategy;
12798
+ if (row.branchStrategy) {
12799
+ try {
12800
+ branchStrategy = JSON.parse(row.branchStrategy);
12801
+ } catch {
12802
+ branchStrategy = void 0;
12803
+ }
12804
+ }
12609
12805
  return {
12610
12806
  id: row.id,
12611
12807
  title: row.title,
@@ -12613,6 +12809,8 @@ var init_mission_store = __esm({
12613
12809
  status: row.status,
12614
12810
  interviewState: row.interviewState,
12615
12811
  baseBranch: row.baseBranch || void 0,
12812
+ branchStrategy,
12813
+ autoMerge: row.autoMerge === null ? void 0 : Boolean(row.autoMerge),
12616
12814
  autoAdvance: Boolean(row.autoAdvance),
12617
12815
  autopilotEnabled: Boolean(row.autopilotEnabled),
12618
12816
  autopilotState: row.autopilotState || "inactive",
@@ -12649,6 +12847,7 @@ var init_mission_store = __esm({
12649
12847
  return {
12650
12848
  id: row.id,
12651
12849
  milestoneId: row.milestoneId,
12850
+ sourceFeatureId: row.sourceFeatureId || void 0,
12652
12851
  title: row.title,
12653
12852
  assertion: row.assertion,
12654
12853
  status: row.status,
@@ -12776,7 +12975,8 @@ var init_mission_store = __esm({
12776
12975
  // ── Mission CRUD Operations ────────────────────────────────────────
12777
12976
  /**
12778
12977
  * Create a new mission.
12779
- * The mission starts in "planning" status with "not_started" interview state.
12978
+ * Missions are always created stopped (status `planning`, autopilot disabled).
12979
+ * Autopilot is only enabled via an explicit start/update after creation.
12780
12980
  *
12781
12981
  * @param input - Mission creation input
12782
12982
  * @returns The created mission
@@ -12791,15 +12991,17 @@ var init_mission_store = __esm({
12791
12991
  status: "planning",
12792
12992
  interviewState: "not_started",
12793
12993
  baseBranch: input.baseBranch,
12994
+ branchStrategy: input.branchStrategy,
12995
+ autoMerge: input.autoMerge,
12794
12996
  autoAdvance: false,
12795
- autopilotEnabled: input.autopilotEnabled ?? false,
12997
+ autopilotEnabled: false,
12796
12998
  autopilotState: "inactive",
12797
12999
  createdAt: now,
12798
13000
  updatedAt: now
12799
13001
  };
12800
13002
  this.db.prepare(`
12801
- INSERT INTO missions (id, title, description, status, interviewState, baseBranch, autoAdvance, autopilotEnabled, autopilotState, createdAt, updatedAt)
12802
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
13003
+ INSERT INTO missions (id, title, description, status, interviewState, baseBranch, branchStrategy, autoMerge, autoAdvance, autopilotEnabled, autopilotState, createdAt, updatedAt)
13004
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
12803
13005
  `).run(
12804
13006
  mission.id,
12805
13007
  mission.title,
@@ -12807,6 +13009,8 @@ var init_mission_store = __esm({
12807
13009
  mission.status,
12808
13010
  mission.interviewState,
12809
13011
  mission.baseBranch ?? null,
13012
+ mission.branchStrategy ? JSON.stringify(mission.branchStrategy) : null,
13013
+ mission.autoMerge === void 0 ? null : mission.autoMerge ? 1 : 0,
12810
13014
  mission.autoAdvance ? 1 : 0,
12811
13015
  mission.autopilotEnabled ? 1 : 0,
12812
13016
  mission.autopilotState ?? "inactive",
@@ -13273,6 +13477,8 @@ var init_mission_store = __esm({
13273
13477
  status = ?,
13274
13478
  interviewState = ?,
13275
13479
  baseBranch = ?,
13480
+ branchStrategy = ?,
13481
+ autoMerge = ?,
13276
13482
  autoAdvance = ?,
13277
13483
  autopilotEnabled = ?,
13278
13484
  autopilotState = ?,
@@ -13285,6 +13491,8 @@ var init_mission_store = __esm({
13285
13491
  updated.status,
13286
13492
  updated.interviewState,
13287
13493
  updated.baseBranch ?? null,
13494
+ updated.branchStrategy ? JSON.stringify(updated.branchStrategy) : null,
13495
+ updated.autoMerge === void 0 ? null : updated.autoMerge ? 1 : 0,
13288
13496
  updated.autoAdvance ? 1 : 0,
13289
13497
  updated.autopilotEnabled ? 1 : 0,
13290
13498
  updated.autopilotState ?? "inactive",
@@ -13800,7 +14008,8 @@ var init_mission_store = __esm({
13800
14008
  this.emit("feature:created", feature);
13801
14009
  this.recomputeSliceStatus(sliceId);
13802
14010
  this.applyDerivedMilestoneAcceptanceCriteria(slice.milestoneId);
13803
- return feature;
14011
+ this.ensureFeatureAssertion(feature);
14012
+ return this.getFeature(feature.id) ?? feature;
13804
14013
  }
13805
14014
  /**
13806
14015
  * Get a feature by ID.
@@ -13885,6 +14094,11 @@ var init_mission_store = __esm({
13885
14094
  if (taskIdChanged || statusChanged) {
13886
14095
  this.recomputeSliceStatus(updated.sliceId);
13887
14096
  }
14097
+ const shouldSyncAssertion = updates.title !== void 0 || updates.description !== void 0 || updates.acceptanceCriteria !== void 0;
14098
+ if (shouldSyncAssertion) {
14099
+ this.ensureFeatureAssertion(updated);
14100
+ return this.getFeature(updated.id) ?? updated;
14101
+ }
13888
14102
  return updated;
13889
14103
  }
13890
14104
  /**
@@ -13899,11 +14113,115 @@ var init_mission_store = __esm({
13899
14113
  throw new Error(`Feature ${id} not found`);
13900
14114
  }
13901
14115
  const sliceId = feature.sliceId;
14116
+ const slice = this.getSlice(sliceId);
14117
+ const milestoneId = slice?.milestoneId;
14118
+ if (milestoneId) {
14119
+ const managedAssertion = this.listContractAssertions(milestoneId).find((assertion) => assertion.sourceFeatureId === feature.id);
14120
+ if (managedAssertion) {
14121
+ this.deleteContractAssertion(managedAssertion.id);
14122
+ }
14123
+ }
13902
14124
  this.db.prepare("DELETE FROM mission_features WHERE id = ?").run(id);
13903
14125
  this.db.bumpLastModified();
13904
14126
  this.emit("feature:deleted", id);
13905
14127
  this.recomputeSliceStatus(sliceId);
13906
14128
  }
14129
+ deriveFeatureAssertion(feature) {
14130
+ const acceptanceCriteria = feature.acceptanceCriteria?.trim();
14131
+ if (acceptanceCriteria) {
14132
+ return { assertionText: acceptanceCriteria, textSource: "acceptanceCriteria" };
14133
+ }
14134
+ const description = feature.description?.trim();
14135
+ if (description) {
14136
+ return { assertionText: description, textSource: "description" };
14137
+ }
14138
+ return {
14139
+ assertionText: `Verify implementation of: ${feature.title}`,
14140
+ textSource: "fallback"
14141
+ };
14142
+ }
14143
+ ensureFeatureAssertion(feature) {
14144
+ const slice = this.getSlice(feature.sliceId);
14145
+ if (!slice) {
14146
+ throw new Error(`Slice ${feature.sliceId} not found`);
14147
+ }
14148
+ const milestoneId = slice.milestoneId;
14149
+ const { assertionText } = this.deriveFeatureAssertion(feature);
14150
+ const existing = this.listContractAssertions(milestoneId).find((assertion) => assertion.sourceFeatureId === feature.id);
14151
+ if (!existing) {
14152
+ const created = this.addContractAssertion(milestoneId, {
14153
+ title: feature.title,
14154
+ assertion: assertionText,
14155
+ status: "pending",
14156
+ sourceFeatureId: feature.id
14157
+ });
14158
+ this.linkFeatureToAssertion(feature.id, created.id);
14159
+ return;
14160
+ }
14161
+ if (existing.title !== feature.title || existing.assertion !== assertionText) {
14162
+ this.updateContractAssertion(existing.id, {
14163
+ title: feature.title,
14164
+ assertion: assertionText
14165
+ });
14166
+ }
14167
+ }
14168
+ /**
14169
+ * Backfill assertion links for legacy features that predate the FN-5695 creation-path fix.
14170
+ * Reuses deriveFeatureAssertion()/ensureFeatureAssertion text-source rules so create/update
14171
+ * and repair flows stay aligned on canonical assertion content.
14172
+ */
14173
+ backfillFeatureAssertions(options) {
14174
+ const dryRun = options?.dryRun ?? true;
14175
+ const missionFilter = options?.missionId;
14176
+ 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));
14177
+ const report = {
14178
+ scanned: features.length,
14179
+ alreadyLinked: 0,
14180
+ repaired: [],
14181
+ skippedErrors: []
14182
+ };
14183
+ for (const feature of features) {
14184
+ try {
14185
+ const linkedAssertions = this.listAssertionsForFeature(feature.id);
14186
+ if (linkedAssertions.length > 0) {
14187
+ report.alreadyLinked += 1;
14188
+ continue;
14189
+ }
14190
+ const slice = this.getSlice(feature.sliceId);
14191
+ if (!slice) {
14192
+ throw new Error(`Slice ${feature.sliceId} not found`);
14193
+ }
14194
+ const milestoneId = slice.milestoneId;
14195
+ const { assertionText, textSource } = this.deriveFeatureAssertion(feature);
14196
+ if (dryRun) {
14197
+ report.repaired.push({
14198
+ featureId: feature.id,
14199
+ milestoneId,
14200
+ assertionId: "(dry-run)",
14201
+ textSource
14202
+ });
14203
+ continue;
14204
+ }
14205
+ const created = this.addContractAssertion(milestoneId, {
14206
+ title: feature.title,
14207
+ assertion: assertionText,
14208
+ status: "pending",
14209
+ sourceFeatureId: feature.id
14210
+ });
14211
+ this.linkFeatureToAssertion(feature.id, created.id);
14212
+ report.repaired.push({
14213
+ featureId: feature.id,
14214
+ milestoneId,
14215
+ assertionId: created.id,
14216
+ textSource
14217
+ });
14218
+ } catch (error) {
14219
+ const message = error instanceof Error ? error.message : String(error);
14220
+ report.skippedErrors.push({ featureId: feature.id, message });
14221
+ }
14222
+ }
14223
+ return report;
14224
+ }
13907
14225
  /**
13908
14226
  * Resolve the mission hierarchy for a slice.
13909
14227
  *
@@ -14496,6 +14814,7 @@ var init_mission_store = __esm({
14496
14814
  const assertion = {
14497
14815
  id,
14498
14816
  milestoneId,
14817
+ sourceFeatureId: input.sourceFeatureId,
14499
14818
  title: input.title,
14500
14819
  assertion: input.assertion,
14501
14820
  status: input.status || "pending",
@@ -14504,8 +14823,8 @@ var init_mission_store = __esm({
14504
14823
  updatedAt: now
14505
14824
  };
14506
14825
  this.db.prepare(`
14507
- INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, createdAt, updatedAt)
14508
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
14826
+ INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, sourceFeatureId, createdAt, updatedAt)
14827
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
14509
14828
  `).run(
14510
14829
  assertion.id,
14511
14830
  assertion.milestoneId,
@@ -14513,6 +14832,7 @@ var init_mission_store = __esm({
14513
14832
  assertion.assertion,
14514
14833
  assertion.status,
14515
14834
  assertion.orderIndex,
14835
+ assertion.sourceFeatureId ?? null,
14516
14836
  assertion.createdAt,
14517
14837
  assertion.updatedAt
14518
14838
  );
@@ -14954,7 +15274,10 @@ ${feature.acceptanceCriteria}`);
14954
15274
  const milestone = slice ? this.getMilestone(slice.milestoneId) : void 0;
14955
15275
  const missionId = milestone?.missionId;
14956
15276
  const mission = missionId ? this.getMission(missionId) : void 0;
15277
+ const strategyDefaults = missionBranchStrategyDefaults(mission?.branchStrategy);
14957
15278
  const resolvedBaseBranch = branchOptions?.baseBranch ?? mission?.baseBranch;
15279
+ const resolvedBranch = branchOptions?.branch ?? strategyDefaults.branch;
15280
+ const resolvedAssignmentMode = branchOptions?.assignmentMode ?? strategyDefaults.assignmentMode;
14958
15281
  const lockScope = missionId ? `mission:${missionId}` : `mission-store:${this.taskStore.getRootDir()}`;
14959
15282
  const guard = await runDeterministicDuplicateGuard(this.taskStore, {
14960
15283
  title: taskTitle || feature.title,
@@ -14968,13 +15291,13 @@ ${feature.acceptanceCriteria}`);
14968
15291
  const createdTask = await this.taskStore.createTask({
14969
15292
  title: taskTitle || feature.title,
14970
15293
  description,
14971
- branch: branchOptions?.branch,
15294
+ branch: resolvedBranch,
14972
15295
  baseBranch: resolvedBaseBranch,
14973
15296
  ...missionId ? {
14974
15297
  branchContext: {
14975
15298
  groupId: `mission:${missionId}`,
14976
15299
  source: "mission",
14977
- assignmentMode: branchOptions?.assignmentMode ?? "shared",
15300
+ assignmentMode: resolvedAssignmentMode,
14978
15301
  inheritedBaseBranch: resolvedBaseBranch
14979
15302
  }
14980
15303
  } : {}
@@ -15019,14 +15342,18 @@ ${feature.acceptanceCriteria}`);
15019
15342
  const definedFeatures = features.filter((f2) => f2.status === "defined");
15020
15343
  const milestone = this.getMilestone(slice.milestoneId);
15021
15344
  const mission = milestone ? this.getMission(milestone.missionId) : void 0;
15345
+ const strategyDefaults = missionBranchStrategyDefaults(mission?.branchStrategy);
15022
15346
  const resolvedBaseBranch = branchOptions?.baseBranch ?? mission?.baseBranch;
15347
+ const resolvedAssignmentMode = branchOptions?.assignmentMode ?? strategyDefaults.assignmentMode;
15348
+ const resolvedBranch = branchOptions?.branch ?? strategyDefaults.branch;
15023
15349
  const triaged = [];
15024
15350
  for (const feature of definedFeatures) {
15025
- const branch = branchOptions?.assignmentMode === "per-task-derived" ? branchOptions?.branch ? `${branchOptions.branch}/${feature.id.toLowerCase()}` : void 0 : branchOptions?.branch;
15351
+ const strategyBranch = resolvedAssignmentMode === "per-task-derived" ? resolvedBranch ? `${resolvedBranch}/${feature.id.toLowerCase()}` : void 0 : resolvedBranch;
15026
15352
  const updated = await this.triageFeature(feature.id, void 0, void 0, {
15027
- ...branchOptions,
15028
- branch,
15029
- baseBranch: resolvedBaseBranch
15353
+ branch: strategyBranch,
15354
+ baseBranch: resolvedBaseBranch,
15355
+ assignmentMode: resolvedAssignmentMode,
15356
+ ...branchOptions
15030
15357
  });
15031
15358
  triaged.push(updated);
15032
15359
  }
@@ -15178,17 +15505,20 @@ ${feature.acceptanceCriteria}`);
15178
15505
  validateSnapshotEnvelope(snapshot);
15179
15506
  let applied = 0;
15180
15507
  for (const mission of snapshot.payload.missions) {
15181
- this.db.prepare(`INSERT INTO missions (id, title, description, status, interviewState, autoAdvance, autopilotEnabled, autopilotState, lastAutopilotActivityAt, createdAt, updatedAt)
15182
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15508
+ this.db.prepare(`INSERT INTO missions (id, title, description, status, interviewState, baseBranch, branchStrategy, autoMerge, autoAdvance, autopilotEnabled, autopilotState, lastAutopilotActivityAt, createdAt, updatedAt)
15509
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15183
15510
  ON CONFLICT(id) DO UPDATE SET
15184
15511
  title=excluded.title, description=excluded.description, status=excluded.status, interviewState=excluded.interviewState,
15185
- autoAdvance=excluded.autoAdvance, autopilotEnabled=excluded.autopilotEnabled, autopilotState=excluded.autopilotState,
15512
+ baseBranch=excluded.baseBranch, branchStrategy=excluded.branchStrategy, autoMerge=excluded.autoMerge, autoAdvance=excluded.autoAdvance, autopilotEnabled=excluded.autopilotEnabled, autopilotState=excluded.autopilotState,
15186
15513
  lastAutopilotActivityAt=excluded.lastAutopilotActivityAt, updatedAt=excluded.updatedAt`).run(
15187
15514
  mission.id,
15188
15515
  mission.title,
15189
15516
  mission.description ?? null,
15190
15517
  mission.status,
15191
15518
  mission.interviewState,
15519
+ mission.baseBranch ?? null,
15520
+ mission.branchStrategy ? JSON.stringify(mission.branchStrategy) : null,
15521
+ mission.autoMerge === void 0 ? null : mission.autoMerge ? 1 : 0,
15192
15522
  mission.autoAdvance ? 1 : 0,
15193
15523
  mission.autopilotEnabled ? 1 : 0,
15194
15524
  mission.autopilotState,
@@ -15274,9 +15604,9 @@ ${feature.acceptanceCriteria}`);
15274
15604
  }
15275
15605
  for (const assertion of snapshot.payload.assertions) {
15276
15606
  if (!assertion.id || !assertion.milestoneId) continue;
15277
- this.db.prepare(`INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, createdAt, updatedAt)
15278
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
15279
- 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);
15607
+ this.db.prepare(`INSERT INTO mission_contract_assertions (id, milestoneId, title, assertion, status, orderIndex, sourceFeatureId, createdAt, updatedAt)
15608
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
15609
+ 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);
15280
15610
  }
15281
15611
  for (const link of snapshot.payload.featureAssertionLinks) {
15282
15612
  if (!link.featureId || !link.assertionId) continue;
@@ -27237,6 +27567,9 @@ var init_secrets_sync_passphrase = __esm({
27237
27567
  function isFusionSiblingBranch(branch) {
27238
27568
  return FUSION_SIBLING_BRANCH_RE.test(branch);
27239
27569
  }
27570
+ function resolveEffectiveAutoMerge(task, settings) {
27571
+ return task.autoMerge ?? settings.autoMerge;
27572
+ }
27240
27573
  function resolveTaskMergeTarget(task, options = {}) {
27241
27574
  let rejected;
27242
27575
  const configuredBase = task.baseBranch?.trim();
@@ -40159,6 +40492,7 @@ var init_store = __esm({
40159
40492
  init_db();
40160
40493
  init_archive_db();
40161
40494
  init_db_migrate();
40495
+ init_goal_citation_extractor();
40162
40496
  init_mission_store();
40163
40497
  init_plugin_store();
40164
40498
  init_insight_store();
@@ -40688,6 +41022,7 @@ var init_store = __esm({
40688
41022
  baseBranch: row.baseBranch || void 0,
40689
41023
  executionStartBranch: row.executionStartBranch || void 0,
40690
41024
  branch: row.branch || void 0,
41025
+ autoMerge: row.autoMerge === null ? void 0 : row.autoMerge === 1,
40691
41026
  baseCommitSha: row.baseCommitSha || void 0,
40692
41027
  scopeOverride: row.scopeOverride ? true : void 0,
40693
41028
  scopeOverrideReason: row.scopeOverrideReason || void 0,
@@ -40702,6 +41037,9 @@ var init_store = __esm({
40702
41037
  mergeRetries: row.mergeRetries ?? void 0,
40703
41038
  workflowStepRetries: row.workflowStepRetries ?? void 0,
40704
41039
  stuckKillCount: row.stuckKillCount ?? void 0,
41040
+ resumeLimboCount: row.resumeLimboCount ?? void 0,
41041
+ resumeLimboTipSha: row.resumeLimboTipSha || void 0,
41042
+ resumeLimboStepSignature: row.resumeLimboStepSignature || void 0,
40705
41043
  postReviewFixCount: row.postReviewFixCount ?? void 0,
40706
41044
  recoveryRetryCount: row.recoveryRetryCount ?? void 0,
40707
41045
  taskDoneRetryCount: row.taskDoneRetryCount ?? void 0,
@@ -40710,6 +41048,7 @@ var init_store = __esm({
40710
41048
  verificationFailureCount: row.verificationFailureCount ?? void 0,
40711
41049
  mergeConflictBounceCount: row.mergeConflictBounceCount ?? void 0,
40712
41050
  mergeAuditBounceCount: row.mergeAuditBounceCount ?? void 0,
41051
+ mergeTransientRetryCount: row.mergeTransientRetryCount ?? void 0,
40713
41052
  branchConflictRecoveryCount: row.branchConflictRecoveryCount ?? void 0,
40714
41053
  reviewerContextRetryCount: row.reviewerContextRetryCount ?? void 0,
40715
41054
  reviewerFallbackRetryCount: row.reviewerFallbackRetryCount ?? void 0,
@@ -40833,6 +41172,28 @@ var init_store = __esm({
40833
41172
  allowResurrection: row.allowResurrection ? true : void 0
40834
41173
  };
40835
41174
  }
41175
+ rowToBranchGroup(row) {
41176
+ return {
41177
+ id: row.id,
41178
+ sourceType: row.sourceType,
41179
+ sourceId: row.sourceId,
41180
+ branchName: row.branchName,
41181
+ worktreePath: row.worktreePath ?? void 0,
41182
+ autoMerge: Boolean(row.autoMerge),
41183
+ prState: row.prState,
41184
+ prUrl: row.prUrl ?? void 0,
41185
+ prNumber: row.prNumber ?? void 0,
41186
+ status: row.status,
41187
+ createdAt: row.createdAt,
41188
+ updatedAt: row.updatedAt,
41189
+ closedAt: row.closedAt ?? void 0
41190
+ };
41191
+ }
41192
+ generateBranchGroupId() {
41193
+ const timestamp = Date.now().toString(36).toUpperCase();
41194
+ const random = Math.random().toString(36).slice(2, 8).toUpperCase();
41195
+ return `BG-${timestamp}-${random}`;
41196
+ }
40836
41197
  archiveEntryToTask(entry, slim = false) {
40837
41198
  return {
40838
41199
  id: entry.id,
@@ -40872,6 +41233,8 @@ var init_store = __esm({
40872
41233
  planningModelId: entry.planningModelId,
40873
41234
  breakIntoSubtasks: entry.breakIntoSubtasks,
40874
41235
  noCommitsExpected: entry.noCommitsExpected,
41236
+ branchContext: entry.branchContext,
41237
+ autoMerge: entry.autoMerge,
40875
41238
  modifiedFiles: slim ? void 0 : entry.modifiedFiles,
40876
41239
  missionId: entry.missionId,
40877
41240
  sliceId: entry.sliceId,
@@ -40985,6 +41348,8 @@ ${recentText}` : void 0
40985
41348
  noCommitsExpected: task.noCommitsExpected,
40986
41349
  baseBranch: task.baseBranch,
40987
41350
  branch: task.branch,
41351
+ branchContext: task.branchContext,
41352
+ autoMerge: task.autoMerge,
40988
41353
  baseCommitSha: task.baseCommitSha,
40989
41354
  mergeRetries: task.mergeRetries,
40990
41355
  error: task.error,
@@ -41025,6 +41390,99 @@ ${recentText}` : void 0
41025
41390
  createdAt: row.createdAt
41026
41391
  };
41027
41392
  }
41393
+ rowToGoalCitation(row) {
41394
+ return {
41395
+ id: row.id,
41396
+ goalId: row.goalId,
41397
+ agentId: row.agentId,
41398
+ ...row.taskId ? { taskId: row.taskId } : {},
41399
+ surface: row.surface,
41400
+ sourceRef: row.sourceRef,
41401
+ snippet: row.snippet,
41402
+ timestamp: row.timestamp
41403
+ };
41404
+ }
41405
+ recordGoalCitations(inputs) {
41406
+ if (inputs.length === 0) {
41407
+ return [];
41408
+ }
41409
+ const now = (/* @__PURE__ */ new Date()).toISOString();
41410
+ const stmt = this.db.prepare(`
41411
+ INSERT OR IGNORE INTO goal_citations (goalId, agentId, taskId, surface, sourceRef, snippet, timestamp)
41412
+ VALUES (?, ?, ?, ?, ?, ?, ?)
41413
+ RETURNING *
41414
+ `);
41415
+ const inserted = [];
41416
+ this.db.transaction(() => {
41417
+ for (const input of inputs) {
41418
+ const row = stmt.get(
41419
+ input.goalId,
41420
+ input.agentId,
41421
+ input.taskId ?? null,
41422
+ input.surface,
41423
+ input.sourceRef,
41424
+ input.snippet,
41425
+ input.timestamp ?? now
41426
+ );
41427
+ if (row) {
41428
+ inserted.push(this.rowToGoalCitation(row));
41429
+ }
41430
+ }
41431
+ if (inserted.length > 0) {
41432
+ this.db.bumpLastModified();
41433
+ }
41434
+ });
41435
+ return inserted;
41436
+ }
41437
+ listGoalCitations(filter = {}) {
41438
+ const clauses = [];
41439
+ const params = [];
41440
+ if (filter.goalId) {
41441
+ clauses.push("goalId = ?");
41442
+ params.push(filter.goalId);
41443
+ }
41444
+ if (filter.agentId) {
41445
+ clauses.push("agentId = ?");
41446
+ params.push(filter.agentId);
41447
+ }
41448
+ if (filter.taskId) {
41449
+ clauses.push("taskId = ?");
41450
+ params.push(filter.taskId);
41451
+ }
41452
+ if (filter.surface) {
41453
+ clauses.push("surface = ?");
41454
+ params.push(filter.surface);
41455
+ }
41456
+ if (filter.startTime) {
41457
+ clauses.push("timestamp >= ?");
41458
+ params.push(filter.startTime);
41459
+ }
41460
+ if (filter.endTime) {
41461
+ clauses.push("timestamp <= ?");
41462
+ params.push(filter.endTime);
41463
+ }
41464
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
41465
+ const limit = Math.max(1, Math.min(filter.limit ?? 200, 1e3));
41466
+ const rows = this.db.prepare(
41467
+ `SELECT * FROM goal_citations ${where} ORDER BY timestamp DESC, id DESC LIMIT ?`
41468
+ ).all(...params, limit);
41469
+ return rows.map((row) => this.rowToGoalCitation(row));
41470
+ }
41471
+ scanAndRecordCitations(text, surface, sourceRef, agentId, taskId, timestamp) {
41472
+ const matches = extractGoalCitations(text);
41473
+ if (matches.length === 0) {
41474
+ return [];
41475
+ }
41476
+ return matches.map((match) => ({
41477
+ goalId: match.goalId,
41478
+ agentId,
41479
+ ...taskId ? { taskId } : {},
41480
+ surface,
41481
+ sourceRef,
41482
+ snippet: buildSnippet(text, match.index),
41483
+ ...timestamp ? { timestamp } : {}
41484
+ }));
41485
+ }
41028
41486
  getTaskSelectClause(slim, tableAlias) {
41029
41487
  if (!slim) {
41030
41488
  return tableAlias ? `${tableAlias}.*` : "*";
@@ -41049,6 +41507,7 @@ ${recentText}` : void 0
41049
41507
  "userPaused",
41050
41508
  "baseBranch",
41051
41509
  "branch",
41510
+ "autoMerge",
41052
41511
  "executionStartBranch",
41053
41512
  "baseCommitSha",
41054
41513
  "modelPresetId",
@@ -41061,6 +41520,9 @@ ${recentText}` : void 0
41061
41520
  "mergeRetries",
41062
41521
  "workflowStepRetries",
41063
41522
  "stuckKillCount",
41523
+ "resumeLimboCount",
41524
+ "resumeLimboTipSha",
41525
+ "resumeLimboStepSignature",
41064
41526
  "postReviewFixCount",
41065
41527
  "recoveryRetryCount",
41066
41528
  "taskDoneRetryCount",
@@ -41069,6 +41531,7 @@ ${recentText}` : void 0
41069
41531
  "verificationFailureCount",
41070
41532
  "mergeConflictBounceCount",
41071
41533
  "mergeAuditBounceCount",
41534
+ "mergeTransientRetryCount",
41072
41535
  "branchConflictRecoveryCount",
41073
41536
  "reviewerContextRetryCount",
41074
41537
  "reviewerFallbackRetryCount",
@@ -41193,6 +41656,7 @@ ${outcome}`;
41193
41656
  "userPaused",
41194
41657
  "baseBranch",
41195
41658
  "branch",
41659
+ "autoMerge",
41196
41660
  "executionStartBranch",
41197
41661
  "baseCommitSha",
41198
41662
  "modelPresetId",
@@ -41205,6 +41669,9 @@ ${outcome}`;
41205
41669
  "mergeRetries",
41206
41670
  "workflowStepRetries",
41207
41671
  "stuckKillCount",
41672
+ "resumeLimboCount",
41673
+ "resumeLimboTipSha",
41674
+ "resumeLimboStepSignature",
41208
41675
  "postReviewFixCount",
41209
41676
  "recoveryRetryCount",
41210
41677
  "taskDoneRetryCount",
@@ -41213,6 +41680,7 @@ ${outcome}`;
41213
41680
  "verificationFailureCount",
41214
41681
  "mergeConflictBounceCount",
41215
41682
  "mergeAuditBounceCount",
41683
+ "mergeTransientRetryCount",
41216
41684
  "branchConflictRecoveryCount",
41217
41685
  "reviewerContextRetryCount",
41218
41686
  "reviewerFallbackRetryCount",
@@ -41326,6 +41794,7 @@ ${outcome}`;
41326
41794
  task.userPaused ? 1 : 0,
41327
41795
  task.baseBranch ?? null,
41328
41796
  task.branch ?? null,
41797
+ task.autoMerge === void 0 ? null : task.autoMerge ? 1 : 0,
41329
41798
  task.executionStartBranch ?? null,
41330
41799
  task.baseCommitSha ?? null,
41331
41800
  task.modelPresetId ?? null,
@@ -41338,6 +41807,9 @@ ${outcome}`;
41338
41807
  task.mergeRetries ?? null,
41339
41808
  task.workflowStepRetries ?? null,
41340
41809
  task.stuckKillCount ?? 0,
41810
+ task.resumeLimboCount ?? 0,
41811
+ task.resumeLimboTipSha ?? null,
41812
+ task.resumeLimboStepSignature ?? null,
41341
41813
  task.postReviewFixCount ?? 0,
41342
41814
  task.recoveryRetryCount ?? null,
41343
41815
  task.taskDoneRetryCount ?? 0,
@@ -41346,6 +41818,7 @@ ${outcome}`;
41346
41818
  task.verificationFailureCount ?? 0,
41347
41819
  task.mergeConflictBounceCount ?? 0,
41348
41820
  task.mergeAuditBounceCount ?? 0,
41821
+ task.mergeTransientRetryCount ?? 0,
41349
41822
  task.branchConflictRecoveryCount ?? 0,
41350
41823
  task.reviewerContextRetryCount ?? 0,
41351
41824
  task.reviewerFallbackRetryCount ?? 0,
@@ -41392,6 +41865,7 @@ ${outcome}`;
41392
41865
  toJsonNullable(task.mergeDetails),
41393
41866
  task.breakIntoSubtasks ? 1 : 0,
41394
41867
  task.noCommitsExpected ? 1 : 0,
41868
+ task.autoMerge === void 0 ? null : task.autoMerge ? 1 : 0,
41395
41869
  toJson(task.enabledWorkflowSteps || []),
41396
41870
  toJson(task.modifiedFiles || []),
41397
41871
  task.missionId ?? null,
@@ -41432,16 +41906,16 @@ ${outcome}`;
41432
41906
  this.db.prepare(`
41433
41907
  INSERT INTO tasks (
41434
41908
  id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
41435
- worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41909
+ worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, autoMerge, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41436
41910
  modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
41437
- workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41911
+ workflowStepRetries, stuckKillCount, resumeLimboCount, resumeLimboTipSha, resumeLimboStepSignature, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, mergeTransientRetryCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41438
41912
  summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
41439
41913
  tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
41440
41914
  firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
41441
41915
  dependencies, steps, log, attachments, steeringComments,
41442
41916
  comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
41443
41917
  sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
41444
- 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
41918
+ 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
41445
41919
  ) VALUES (${placeholders})
41446
41920
  `).run(...values);
41447
41921
  this.db.bumpLastModified();
@@ -41458,16 +41932,16 @@ ${outcome}`;
41458
41932
  this.db.prepare(`
41459
41933
  INSERT INTO tasks (
41460
41934
  id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
41461
- worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41935
+ worktree, blockedBy, overlapBlockedBy, paused, userPaused, baseBranch, branch, autoMerge, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
41462
41936
  modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
41463
- workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41937
+ workflowStepRetries, stuckKillCount, resumeLimboCount, resumeLimboTipSha, resumeLimboStepSignature, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, completionHandoffLimboRecoveryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, mergeTransientRetryCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, nextRecoveryAt, error,
41464
41938
  summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
41465
41939
  tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, tokenBudgetSoftAlertedAt, tokenBudgetHardAlertedAt, tokenBudgetOverride, createdAt, updatedAt, columnMovedAt,
41466
41940
  firstExecutionAt, cumulativeActiveMs, executionStartedAt, executionCompletedAt,
41467
41941
  dependencies, steps, log, attachments, steeringComments,
41468
41942
  comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
41469
41943
  sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
41470
- 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
41944
+ 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
41471
41945
  ) VALUES (${placeholders})
41472
41946
  ON CONFLICT(id) DO UPDATE SET
41473
41947
  lineageId = excluded.lineageId,
@@ -41486,6 +41960,7 @@ ${outcome}`;
41486
41960
  userPaused = excluded.userPaused,
41487
41961
  baseBranch = excluded.baseBranch,
41488
41962
  branch = excluded.branch,
41963
+ autoMerge = excluded.autoMerge,
41489
41964
  executionStartBranch = excluded.executionStartBranch,
41490
41965
  baseCommitSha = excluded.baseCommitSha,
41491
41966
  modelPresetId = excluded.modelPresetId,
@@ -41498,6 +41973,9 @@ ${outcome}`;
41498
41973
  mergeRetries = excluded.mergeRetries,
41499
41974
  workflowStepRetries = excluded.workflowStepRetries,
41500
41975
  stuckKillCount = excluded.stuckKillCount,
41976
+ resumeLimboCount = excluded.resumeLimboCount,
41977
+ resumeLimboTipSha = excluded.resumeLimboTipSha,
41978
+ resumeLimboStepSignature = excluded.resumeLimboStepSignature,
41501
41979
  postReviewFixCount = excluded.postReviewFixCount,
41502
41980
  recoveryRetryCount = excluded.recoveryRetryCount,
41503
41981
  taskDoneRetryCount = excluded.taskDoneRetryCount,
@@ -41506,6 +41984,7 @@ ${outcome}`;
41506
41984
  verificationFailureCount = excluded.verificationFailureCount,
41507
41985
  mergeConflictBounceCount = excluded.mergeConflictBounceCount,
41508
41986
  mergeAuditBounceCount = excluded.mergeAuditBounceCount,
41987
+ mergeTransientRetryCount = excluded.mergeTransientRetryCount,
41509
41988
  branchConflictRecoveryCount = excluded.branchConflictRecoveryCount,
41510
41989
  reviewerContextRetryCount = excluded.reviewerContextRetryCount,
41511
41990
  reviewerFallbackRetryCount = excluded.reviewerFallbackRetryCount,
@@ -41552,6 +42031,7 @@ ${outcome}`;
41552
42031
  mergeDetails = excluded.mergeDetails,
41553
42032
  breakIntoSubtasks = excluded.breakIntoSubtasks,
41554
42033
  noCommitsExpected = excluded.noCommitsExpected,
42034
+ autoMerge = excluded.autoMerge,
41555
42035
  enabledWorkflowSteps = excluded.enabledWorkflowSteps,
41556
42036
  modifiedFiles = excluded.modifiedFiles,
41557
42037
  missionId = excluded.missionId,
@@ -42827,6 +43307,7 @@ ${outcome}`;
42827
43307
  sourceParentTaskId: input.source?.sourceParentTaskId,
42828
43308
  sourceMetadata: withTaskBranchContextInSourceMetadata(input.source?.sourceMetadata, input.branchContext),
42829
43309
  branchContext: input.branchContext,
43310
+ autoMerge: input.autoMerge,
42830
43311
  column: input.column || "triage",
42831
43312
  dependencies: input.dependencies || [],
42832
43313
  breakIntoSubtasks: input.breakIntoSubtasks === true ? true : void 0,
@@ -43169,6 +43650,99 @@ ${newTask.description}
43169
43650
  return { ...task, prompt };
43170
43651
  });
43171
43652
  }
43653
+ createBranchGroup(input) {
43654
+ const now = Date.now();
43655
+ const id = this.generateBranchGroupId();
43656
+ this.db.prepare(`
43657
+ INSERT INTO branch_groups (id, sourceType, sourceId, branchName, worktreePath, autoMerge, prState, prUrl, prNumber, status, createdAt, updatedAt, closedAt)
43658
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
43659
+ `).run(
43660
+ id,
43661
+ input.sourceType,
43662
+ input.sourceId,
43663
+ input.branchName,
43664
+ input.worktreePath ?? null,
43665
+ input.autoMerge ? 1 : 0,
43666
+ input.prState ?? "none",
43667
+ input.prUrl ?? null,
43668
+ input.prNumber ?? null,
43669
+ input.status ?? "open",
43670
+ now,
43671
+ now,
43672
+ input.closedAt ?? null
43673
+ );
43674
+ this.db.bumpLastModified();
43675
+ return this.getBranchGroup(id);
43676
+ }
43677
+ getBranchGroup(id) {
43678
+ const row = this.db.prepare(`SELECT * FROM branch_groups WHERE id = ?`).get(id);
43679
+ return row ? this.rowToBranchGroup(row) : null;
43680
+ }
43681
+ getBranchGroupBySource(sourceType, sourceId) {
43682
+ const row = this.db.prepare(`SELECT * FROM branch_groups WHERE sourceType = ? AND sourceId = ?`).get(sourceType, sourceId);
43683
+ return row ? this.rowToBranchGroup(row) : null;
43684
+ }
43685
+ listBranchGroups(options) {
43686
+ 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();
43687
+ return rows.map((row) => this.rowToBranchGroup(row));
43688
+ }
43689
+ updateBranchGroup(id, patch) {
43690
+ const current = this.getBranchGroup(id);
43691
+ if (!current) {
43692
+ throw new Error(`Branch group ${id} not found`);
43693
+ }
43694
+ const nextStatus = patch.status ?? current.status;
43695
+ const now = Date.now();
43696
+ const nextClosedAt = patch.closedAt === null ? null : patch.closedAt ?? (nextStatus !== "open" && current.status === "open" ? now : current.closedAt ?? null);
43697
+ this.db.prepare(`
43698
+ UPDATE branch_groups
43699
+ SET sourceId = ?, branchName = ?, worktreePath = ?, autoMerge = ?, prState = ?, prUrl = ?, prNumber = ?, status = ?, updatedAt = ?, closedAt = ?
43700
+ WHERE id = ?
43701
+ `).run(
43702
+ patch.sourceId ?? current.sourceId,
43703
+ patch.branchName ?? current.branchName,
43704
+ patch.worktreePath === null ? null : patch.worktreePath ?? current.worktreePath ?? null,
43705
+ patch.autoMerge === void 0 ? current.autoMerge ? 1 : 0 : patch.autoMerge ? 1 : 0,
43706
+ patch.prState ?? current.prState,
43707
+ patch.prUrl === null ? null : patch.prUrl ?? current.prUrl ?? null,
43708
+ patch.prNumber === null ? null : patch.prNumber ?? current.prNumber ?? null,
43709
+ nextStatus,
43710
+ now,
43711
+ nextClosedAt,
43712
+ id
43713
+ );
43714
+ this.db.bumpLastModified();
43715
+ return this.getBranchGroup(id);
43716
+ }
43717
+ async setTaskBranchGroup(taskId, branchGroupId) {
43718
+ await this.withTaskLock(taskId, async () => {
43719
+ const dir = this.taskDir(taskId);
43720
+ const task = await this.readTaskJson(dir);
43721
+ let branchContext;
43722
+ if (branchGroupId) {
43723
+ const group = this.getBranchGroup(branchGroupId);
43724
+ if (!group) {
43725
+ throw new Error(`Branch group ${branchGroupId} not found`);
43726
+ }
43727
+ branchContext = {
43728
+ groupId: group.id,
43729
+ source: group.sourceType,
43730
+ assignmentMode: "shared"
43731
+ };
43732
+ }
43733
+ task.branchContext = branchContext;
43734
+ task.sourceMetadata = withTaskBranchContextInSourceMetadata(task.sourceMetadata, branchContext);
43735
+ if (!branchContext && task.sourceMetadata) {
43736
+ const nextSourceMetadata = { ...task.sourceMetadata };
43737
+ delete nextSourceMetadata[TASK_BRANCH_CONTEXT_METADATA_KEY];
43738
+ task.sourceMetadata = Object.keys(nextSourceMetadata).length > 0 ? nextSourceMetadata : void 0;
43739
+ }
43740
+ task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
43741
+ await this.atomicWriteTaskJson(dir, task);
43742
+ if (this.isWatching) this.taskCache.set(taskId, { ...task });
43743
+ this.emit("task:updated", task);
43744
+ });
43745
+ }
43172
43746
  async getTaskColumns(ids) {
43173
43747
  if (ids.length === 0) {
43174
43748
  return /* @__PURE__ */ new Map();
@@ -43893,6 +44467,7 @@ ${newTask.description}
43893
44467
  throw new Error(`Cannot move ${id} to done: ${mergeBlocker}`);
43894
44468
  }
43895
44469
  }
44470
+ const settingsForInReview = toColumn === "in-review" && task.autoMerge === void 0 ? await this.getSettingsFast() : void 0;
43896
44471
  const movedAt = internal.now ?? (/* @__PURE__ */ new Date()).toISOString();
43897
44472
  task.column = toColumn;
43898
44473
  task.columnMovedAt = movedAt;
@@ -43952,6 +44527,9 @@ ${newTask.description}
43952
44527
  }
43953
44528
  }
43954
44529
  if (toColumn === "in-review") {
44530
+ if (task.autoMerge === void 0 && settingsForInReview) {
44531
+ task.autoMerge = settingsForInReview.autoMerge;
44532
+ }
43955
44533
  task.recoveryRetryCount = void 0;
43956
44534
  task.nextRecoveryAt = void 0;
43957
44535
  if (task.status === "queued") {
@@ -44335,11 +44913,21 @@ ${newTask.description}
44335
44913
  } else if (updates.baseBranch !== void 0) {
44336
44914
  task.baseBranch = updates.baseBranch;
44337
44915
  }
44916
+ if (updates.autoMerge === null) {
44917
+ task.autoMerge = void 0;
44918
+ } else if (updates.autoMerge !== void 0) {
44919
+ task.autoMerge = updates.autoMerge;
44920
+ }
44338
44921
  if (updates.branch === null) {
44339
44922
  task.branch = void 0;
44340
44923
  } else if (updates.branch !== void 0) {
44341
44924
  task.branch = updates.branch;
44342
44925
  }
44926
+ if (updates.autoMerge === null) {
44927
+ task.autoMerge = void 0;
44928
+ } else if (updates.autoMerge !== void 0) {
44929
+ task.autoMerge = updates.autoMerge;
44930
+ }
44343
44931
  if (updates.executionStartBranch === null) {
44344
44932
  task.executionStartBranch = void 0;
44345
44933
  } else if (updates.executionStartBranch !== void 0) {
@@ -44359,6 +44947,21 @@ ${newTask.description}
44359
44947
  } else if (updates.stuckKillCount !== void 0) {
44360
44948
  task.stuckKillCount = updates.stuckKillCount;
44361
44949
  }
44950
+ if (updates.resumeLimboCount === null) {
44951
+ task.resumeLimboCount = void 0;
44952
+ } else if (updates.resumeLimboCount !== void 0) {
44953
+ task.resumeLimboCount = updates.resumeLimboCount;
44954
+ }
44955
+ if (updates.resumeLimboTipSha === null) {
44956
+ task.resumeLimboTipSha = void 0;
44957
+ } else if (updates.resumeLimboTipSha !== void 0) {
44958
+ task.resumeLimboTipSha = updates.resumeLimboTipSha;
44959
+ }
44960
+ if (updates.resumeLimboStepSignature === null) {
44961
+ task.resumeLimboStepSignature = void 0;
44962
+ } else if (updates.resumeLimboStepSignature !== void 0) {
44963
+ task.resumeLimboStepSignature = updates.resumeLimboStepSignature;
44964
+ }
44362
44965
  if (updates.postReviewFixCount === null) {
44363
44966
  task.postReviewFixCount = void 0;
44364
44967
  } else if (updates.postReviewFixCount !== void 0) {
@@ -44399,6 +45002,11 @@ ${newTask.description}
44399
45002
  } else if (updates.mergeAuditBounceCount !== void 0) {
44400
45003
  task.mergeAuditBounceCount = updates.mergeAuditBounceCount;
44401
45004
  }
45005
+ if (updates.mergeTransientRetryCount === null) {
45006
+ task.mergeTransientRetryCount = void 0;
45007
+ } else if (updates.mergeTransientRetryCount !== void 0) {
45008
+ task.mergeTransientRetryCount = updates.mergeTransientRetryCount;
45009
+ }
44402
45010
  if (updates.branchConflictRecoveryCount === null) {
44403
45011
  task.branchConflictRecoveryCount = void 0;
44404
45012
  } else if (updates.branchConflictRecoveryCount !== void 0) {
@@ -46450,8 +47058,42 @@ ${newTask.description}
46450
47058
  INSERT INTO agentLogEntries (taskId, timestamp, text, type, detail, agent)
46451
47059
  VALUES (?, ?, ?, ?, ?, ?)
46452
47060
  `);
47061
+ const citationInputs = [];
46453
47062
  for (const entry of validEntries) {
46454
- stmt.run(entry.taskId, entry.timestamp, entry.text, entry.type, entry.detail, entry.agent);
47063
+ const insertResult = stmt.run(
47064
+ entry.taskId,
47065
+ entry.timestamp,
47066
+ entry.text,
47067
+ entry.type,
47068
+ entry.detail,
47069
+ entry.agent
47070
+ );
47071
+ const insertedId = insertResult.lastInsertRowid;
47072
+ if (insertedId === void 0 || insertedId === null) {
47073
+ continue;
47074
+ }
47075
+ const sourceRef = `agentLog:${String(insertedId)}`;
47076
+ try {
47077
+ citationInputs.push(
47078
+ ...this.scanAndRecordCitations(
47079
+ entry.text,
47080
+ "agent_log",
47081
+ sourceRef,
47082
+ entry.agent ?? "unknown",
47083
+ entry.taskId,
47084
+ entry.timestamp
47085
+ )
47086
+ );
47087
+ } catch (err) {
47088
+ console.warn("[fusion] Failed to scan goal citations from agent_log:", err);
47089
+ }
47090
+ }
47091
+ if (citationInputs.length > 0) {
47092
+ try {
47093
+ this.recordGoalCitations(citationInputs);
47094
+ } catch (err) {
47095
+ console.warn("[fusion] Failed to record goal citations from agent_log batch:", err);
47096
+ }
46455
47097
  }
46456
47098
  this.db.bumpLastModified();
46457
47099
  }
@@ -46489,8 +47131,9 @@ ${newTask.description}
46489
47131
  VALUES (?, ?, ?, ?, ?, ?)
46490
47132
  `);
46491
47133
  this.db.transaction(() => {
47134
+ const citationInputs = [];
46492
47135
  for (const entry of normalizedEntries) {
46493
- stmt.run(
47136
+ const insertResult = stmt.run(
46494
47137
  entry.taskId,
46495
47138
  timestamp,
46496
47139
  entry.text,
@@ -46498,6 +47141,31 @@ ${newTask.description}
46498
47141
  entry.detail ?? null,
46499
47142
  entry.agent ?? null
46500
47143
  );
47144
+ const insertedId = insertResult.lastInsertRowid;
47145
+ if (insertedId === void 0 || insertedId === null) {
47146
+ continue;
47147
+ }
47148
+ try {
47149
+ citationInputs.push(
47150
+ ...this.scanAndRecordCitations(
47151
+ entry.text,
47152
+ "agent_log",
47153
+ `agentLog:${String(insertedId)}`,
47154
+ entry.agent ?? "unknown",
47155
+ entry.taskId,
47156
+ timestamp
47157
+ )
47158
+ );
47159
+ } catch (err) {
47160
+ console.warn("[fusion] Failed to scan goal citations from agent log batch:", err);
47161
+ }
47162
+ }
47163
+ if (citationInputs.length > 0) {
47164
+ try {
47165
+ this.recordGoalCitations(citationInputs);
47166
+ } catch (err) {
47167
+ console.warn("[fusion] Failed to record goal citations from appendAgentLogBatch:", err);
47168
+ }
46501
47169
  }
46502
47170
  this.db.bumpLastModified();
46503
47171
  });
@@ -46892,6 +47560,21 @@ ${newTask.description}
46892
47560
  this.db.bumpLastModified();
46893
47561
  const task = await this.getTask(taskId);
46894
47562
  this.emit("task:updated", task);
47563
+ try {
47564
+ const citationInputs = this.scanAndRecordCitations(
47565
+ input.content,
47566
+ "task_document",
47567
+ `document:${taskId}:${input.key}:rev${document2.revision}`,
47568
+ input.author ?? "user",
47569
+ taskId,
47570
+ document2.updatedAt
47571
+ );
47572
+ if (citationInputs.length > 0) {
47573
+ this.recordGoalCitations(citationInputs);
47574
+ }
47575
+ } catch (err) {
47576
+ console.warn("[fusion] Failed to scan/record goal citations from task document:", err);
47577
+ }
46895
47578
  return document2;
46896
47579
  }
46897
47580
  /**
@@ -48742,6 +49425,7 @@ var init_manual_retry_reset = __esm({
48742
49425
  "use strict";
48743
49426
  MANUAL_RETRY_RESET_COUNTER_KEYS = [
48744
49427
  "stuckKillCount",
49428
+ "resumeLimboCount",
48745
49429
  "recoveryRetryCount",
48746
49430
  "taskDoneRetryCount",
48747
49431
  "worktreeSessionRetryCount",
@@ -69676,6 +70360,8 @@ __export(src_exports, {
69676
70360
  FileMemoryBackend: () => FileMemoryBackend,
69677
70361
  FirstRunDetector: () => FirstRunDetector,
69678
70362
  GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
70363
+ GOAL_CITATION_SNIPPET_MAX: () => GOAL_CITATION_SNIPPET_MAX,
70364
+ GOAL_ID_PATTERN: () => GOAL_ID_PATTERN,
69679
70365
  GlobalSettingsStore: () => GlobalSettingsStore,
69680
70366
  GoalStore: () => GoalStore,
69681
70367
  HIGH_FANOUT_BLOCKER_TODO_THRESHOLD: () => HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
@@ -69846,6 +70532,7 @@ __export(src_exports, {
69846
70532
  buildQmdSearchArgs: () => buildQmdSearchArgs,
69847
70533
  buildResearchDocumentKey: () => buildResearchDocumentKey,
69848
70534
  buildReviewerMemoryInstructions: () => buildReviewerMemoryInstructions,
70535
+ buildSnippet: () => buildSnippet,
69849
70536
  buildTaskLineageTrailer: () => buildTaskLineageTrailer,
69850
70537
  buildTriageMemoryInstructions: () => buildTriageMemoryInstructions,
69851
70538
  buildUnblockWeightMap: () => buildUnblockWeightMap,
@@ -69927,6 +70614,7 @@ __export(src_exports, {
69927
70614
  extractClaudeCliStoredCredential: () => extractClaudeCliStoredCredential,
69928
70615
  extractCodexCliStoredCredential: () => extractCodexCliStoredCredential,
69929
70616
  extractDreamProcessorResult: () => extractDreamProcessorResult,
70617
+ extractGoalCitations: () => extractGoalCitations,
69930
70618
  extractIntentSignature: () => extractIntentSignature,
69931
70619
  extractSandboxProvisioningRequest: () => extractSandboxProvisioningRequest,
69932
70620
  extractTaskIdTokens: () => extractTaskIdTokens,
@@ -70120,6 +70808,7 @@ __export(src_exports, {
70120
70808
  resolveAgentProvisioningPolicy: () => resolveAgentProvisioningPolicy,
70121
70809
  resolveDependencyOrder: () => resolveDependencyOrder,
70122
70810
  resolveEffectiveAgentPermissionPolicy: () => resolveEffectiveAgentPermissionPolicy,
70811
+ resolveEffectiveAutoMerge: () => resolveEffectiveAutoMerge,
70123
70812
  resolveEvalSettings: () => resolveEvalSettings,
70124
70813
  resolveExecutionSettingsModel: () => resolveExecutionSettingsModel,
70125
70814
  resolveGlobalDir: () => resolveGlobalDir,
@@ -70320,6 +71009,7 @@ var init_src = __esm({
70320
71009
  init_mission_store();
70321
71010
  init_goal_types();
70322
71011
  init_goal_store();
71012
+ init_goal_citation_extractor();
70323
71013
  init_central_core();
70324
71014
  init_central_db();
70325
71015
  init_node_connection();
@@ -78875,12 +79565,16 @@ var init_agent_instructions = __esm({
78875
79565
 
78876
79566
  // ../engine/src/prompt-layers.ts
78877
79567
  function buildPromptLayers(input) {
78878
- const { basePrompt, agentInstructions, memorySection, pluginContributions, performanceFeedback } = input;
79568
+ const { basePrompt, agentInstructions, memorySection, goalContext, pluginContributions, performanceFeedback } = input;
78879
79569
  const dynamicParts = [];
78880
79570
  const trimmedMemory = memorySection?.trim() ?? "";
78881
79571
  if (trimmedMemory) {
78882
79572
  dynamicParts.push(trimmedMemory);
78883
79573
  }
79574
+ const trimmedGoalContext = goalContext?.trim() ?? "";
79575
+ if (trimmedGoalContext) {
79576
+ dynamicParts.push(trimmedGoalContext);
79577
+ }
78884
79578
  const trimmedInstructions = agentInstructions?.trim() ?? "";
78885
79579
  if (trimmedInstructions) {
78886
79580
  dynamicParts.push(`## Custom Instructions
@@ -84208,7 +84902,7 @@ function isInsideWorktreesDir(rootDir, worktreePath, settings) {
84208
84902
  return isInsideConfiguredWorktreesDir(rootDir, settings, worktreePath);
84209
84903
  }
84210
84904
  function deriveTaskIdFromBranch(branchName) {
84211
- const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?$/i);
84905
+ const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?(?:-[a-z0-9._-]+)*$/i);
84212
84906
  return match ? match[1].toUpperCase() : branchName.toUpperCase();
84213
84907
  }
84214
84908
  async function scanIdleWorktrees(rootDir, store, settings) {
@@ -100416,6 +101110,12 @@ async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTar
100416
101110
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
100417
101111
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
100418
101112
  }
101113
+ function buildResumeLimboStepSignature(task) {
101114
+ return JSON.stringify({
101115
+ currentStep: task.currentStep ?? null,
101116
+ steps: Array.isArray(task.steps) ? task.steps.map((step) => step.status) : []
101117
+ });
101118
+ }
100419
101119
  function formatRecoveryTimestamp(date = /* @__PURE__ */ new Date()) {
100420
101120
  const pad = (value) => String(value).padStart(2, "0");
100421
101121
  return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
@@ -100630,7 +101330,7 @@ function isNoTaskDoneFailure2(task) {
100630
101330
  function hasStepProgress2(task) {
100631
101331
  return task.steps.some((step) => step.status !== "pending");
100632
101332
  }
100633
- 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;
101333
+ 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;
100634
101334
  var init_self_healing = __esm({
100635
101335
  "../engine/src/self-healing.ts"() {
100636
101336
  "use strict";
@@ -100663,6 +101363,7 @@ var init_self_healing = __esm({
100663
101363
  STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS = 10 * 6e4;
100664
101364
  COMPLETION_HANDOFF_LIMBO_GRACE_MS = 5 * 6e4;
100665
101365
  MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES = 3;
101366
+ MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
100666
101367
  APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
100667
101368
  STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
100668
101369
  STARVED_PEER_PROGRESS_THRESHOLD = 3;
@@ -102179,13 +102880,71 @@ var init_self_healing = __esm({
102179
102880
  }
102180
102881
  }
102181
102882
  const preservedCommitCount = inspection.kind === "fully-subsumed" ? 0 : inspection.taskAttributedCommitCount;
102883
+ const stepSignature = buildResumeLimboStepSignature(task);
102884
+ const hasActiveSessionSignal = Boolean(task.checkedOutBy) || activeTaskIds.has(task.id.toUpperCase());
102885
+ const hasPriorSnapshot = typeof task.resumeLimboTipSha === "string" && typeof task.resumeLimboStepSignature === "string";
102886
+ const unchangedSincePriorResume = hasPriorSnapshot && task.resumeLimboTipSha === inspection.tipSha && task.resumeLimboStepSignature === stepSignature;
102887
+ const isNoProgressResume = task.column === "in-progress" && unchangedSincePriorResume && !hasActiveSessionSignal;
102888
+ const resumeAttemptCount = isNoProgressResume ? (task.resumeLimboCount ?? 0) + 1 : 0;
102889
+ if (task.column === "in-progress" && isNoProgressResume && resumeAttemptCount >= MAX_NO_PROGRESS_RESUME_ATTEMPTS) {
102890
+ const idleAnchor = task.executionStartedAt ?? task.columnMovedAt ?? task.updatedAt;
102891
+ const idleAnchorMs = Date.parse(idleAnchor ?? "");
102892
+ const idleMs = Number.isFinite(idleAnchorMs) ? Math.max(0, Date.now() - idleAnchorMs) : null;
102893
+ await this.store.moveTask(task.id, "todo", {
102894
+ moveSource: "engine",
102895
+ preserveWorktree: true,
102896
+ preserveProgress: true,
102897
+ preserveResumeState: true
102898
+ });
102899
+ await this.store.updateTask(task.id, {
102900
+ resumeLimboCount: 0,
102901
+ resumeLimboTipSha: inspection.tipSha,
102902
+ resumeLimboStepSignature: stepSignature
102903
+ });
102904
+ await this.store.logEntry(
102905
+ task.id,
102906
+ `[recovery] resume-limbo-escalated ${task.id} moved to todo after ${resumeAttemptCount} no-progress reclaim/resume attempts`,
102907
+ JSON.stringify({
102908
+ frozenTipSha: inspection.tipSha,
102909
+ idleMs,
102910
+ resumeAttemptCount,
102911
+ currentStep: task.currentStep ?? null
102912
+ })
102913
+ );
102914
+ try {
102915
+ await createRunAuditor(this.store, {
102916
+ runId: generateSyntheticRunId("self-heal", task.id),
102917
+ agentId: "self-healing",
102918
+ taskId: task.id,
102919
+ taskLineageId: task.lineageId,
102920
+ phase: "reclaim-self-owned-branch-conflicts"
102921
+ }).database({
102922
+ type: "task:resume-limbo-escalated",
102923
+ target: task.id,
102924
+ metadata: {
102925
+ taskId: task.id,
102926
+ frozenTipSha: inspection.tipSha,
102927
+ idleMs,
102928
+ resumeAttemptCount,
102929
+ currentStep: task.currentStep ?? null
102930
+ }
102931
+ });
102932
+ } catch (auditErr) {
102933
+ log20.warn(`Failed to write task:resume-limbo-escalated run-audit event for ${task.id}: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`);
102934
+ }
102935
+ recovered++;
102936
+ continue;
102937
+ }
102182
102938
  await this.store.updateTask(task.id, {
102183
102939
  worktree: inspection.livePath,
102184
102940
  branch: task.branch,
102185
102941
  paused: false,
102186
102942
  pausedReason: void 0,
102187
102943
  status: null,
102188
- error: null
102944
+ error: null,
102945
+ resumeLimboCount: resumeAttemptCount,
102946
+ resumeLimboTipSha: inspection.tipSha,
102947
+ resumeLimboStepSignature: stepSignature
102189
102948
  });
102190
102949
  await this.store.logEntry(
102191
102950
  task.id,
@@ -103417,7 +104176,7 @@ var init_self_healing = __esm({
103417
104176
  const blockerId = task.blockedBy;
103418
104177
  const unresolvedDeps = task.dependencies.filter((depId) => {
103419
104178
  const dep = taskById.get(depId);
103420
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
104179
+ return dep && !dep.deletedAt && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
103421
104180
  });
103422
104181
  const overlapBlocker = task.overlapBlockedBy ? taskById.get(task.overlapBlockedBy) : void 0;
103423
104182
  const hasActiveOverlapBlocker = Boolean(
@@ -103443,6 +104202,9 @@ var init_self_healing = __esm({
103443
104202
  reasonCode = "missing-blocker";
103444
104203
  reason = `blocker ${blockerId} missing`;
103445
104204
  }
104205
+ } else if (blocker.deletedAt) {
104206
+ reasonCode = "soft-deleted-blocker";
104207
+ reason = `blocker ${blockerId} soft-deleted at ${blocker.deletedAt}`;
103446
104208
  } else if (blocker.column === "done") {
103447
104209
  reasonCode = "blocker-done";
103448
104210
  reason = `blocker ${blockerId} is done`;
@@ -104972,6 +105734,12 @@ var init_self_healing = __esm({
104972
105734
  try {
104973
105735
  const hardBlocker = getTaskHardMergeBlocker({
104974
105736
  ...task,
105737
+ // Merge-confirmed tasks have already landed. Treat stale merge
105738
+ // in-flight statuses as soft state to clear during finalization,
105739
+ // not hard blockers that park an otherwise confirmed merge as failed.
105740
+ paused: false,
105741
+ status: task.status === "merging" || task.status === "merging-pr" ? void 0 : task.status,
105742
+ error: void 0,
104975
105743
  steps: task.steps ?? [],
104976
105744
  workflowStepResults: task.workflowStepResults
104977
105745
  });
@@ -109603,6 +110371,258 @@ var init_token_cap_detector = __esm({
109603
110371
  }
109604
110372
  });
109605
110373
 
110374
+ // ../engine/src/goal-context-injector.ts
110375
+ function normalizeTitle(title) {
110376
+ return title.replace(/\s*\n+\s*/g, " ").trim();
110377
+ }
110378
+ function renderGoalContext(goals) {
110379
+ const lines = goals.map((goal) => `- ${goal.id}: ${normalizeTitle(goal.title)}`);
110380
+ return `## Active Goals
110381
+
110382
+ ${lines.join("\n")}`;
110383
+ }
110384
+ function buildGoalContextSection(input) {
110385
+ const { activeGoals, onTruncated } = input;
110386
+ if (activeGoals.length === 0) {
110387
+ return { text: "", emittedGoalIds: [], truncated: null };
110388
+ }
110389
+ const sortedGoals = [...activeGoals].sort((a, b2) => {
110390
+ const byCreated = a.createdAt.localeCompare(b2.createdAt);
110391
+ if (byCreated !== 0) {
110392
+ return byCreated;
110393
+ }
110394
+ return a.id.localeCompare(b2.id);
110395
+ });
110396
+ const resolvedMaxGoals = Math.min(input.maxGoals ?? MAX_INJECTED_GOALS, MAX_INJECTED_GOALS);
110397
+ const charBudget = input.charBudget ?? DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
110398
+ const droppedGoalIds = [];
110399
+ let selectedGoals = sortedGoals.slice(0, resolvedMaxGoals);
110400
+ if (sortedGoals.length > resolvedMaxGoals) {
110401
+ droppedGoalIds.push(...sortedGoals.slice(resolvedMaxGoals).map((goal) => goal.id));
110402
+ }
110403
+ let text = renderGoalContext(selectedGoals);
110404
+ let usedBudgetDrop = false;
110405
+ while (selectedGoals.length > 1 && text.length > charBudget) {
110406
+ const dropped = selectedGoals[selectedGoals.length - 1];
110407
+ selectedGoals = selectedGoals.slice(0, -1);
110408
+ droppedGoalIds.push(dropped.id);
110409
+ text = renderGoalContext(selectedGoals);
110410
+ usedBudgetDrop = true;
110411
+ }
110412
+ let truncated = null;
110413
+ if (droppedGoalIds.length > 0) {
110414
+ truncated = {
110415
+ reason: usedBudgetDrop ? "budget" : "cap",
110416
+ requested: activeGoals.length,
110417
+ emitted: selectedGoals.length,
110418
+ droppedGoalIds,
110419
+ charBudget,
110420
+ producedChars: text.length
110421
+ };
110422
+ onTruncated?.(truncated);
110423
+ }
110424
+ return {
110425
+ text,
110426
+ emittedGoalIds: selectedGoals.map((goal) => goal.id),
110427
+ truncated
110428
+ };
110429
+ }
110430
+ var MAX_INJECTED_GOALS, resolvedActiveGoalLimit, DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
110431
+ var init_goal_context_injector = __esm({
110432
+ "../engine/src/goal-context-injector.ts"() {
110433
+ "use strict";
110434
+ init_src();
110435
+ MAX_INJECTED_GOALS = 5;
110436
+ resolvedActiveGoalLimit = "ACTIVE_GOAL_LIMIT" in src_exports && typeof ACTIVE_GOAL_LIMIT === "number" ? ACTIVE_GOAL_LIMIT : MAX_INJECTED_GOALS;
110437
+ if (MAX_INJECTED_GOALS > resolvedActiveGoalLimit) {
110438
+ throw new Error(
110439
+ `MAX_INJECTED_GOALS (${MAX_INJECTED_GOALS}) cannot exceed ACTIVE_GOAL_LIMIT (${resolvedActiveGoalLimit})`
110440
+ );
110441
+ }
110442
+ DEFAULT_GOAL_INJECTION_CHAR_BUDGET = 600;
110443
+ }
110444
+ });
110445
+
110446
+ // ../engine/src/goal-injection-diagnostics.ts
110447
+ function classifyGoalInjectionResult(result) {
110448
+ if (result.emittedGoalIds.length === 0) {
110449
+ return {
110450
+ outcome: "no-goals",
110451
+ goalCount: 0,
110452
+ goalIds: [],
110453
+ truncated: false
110454
+ };
110455
+ }
110456
+ return {
110457
+ outcome: "applied",
110458
+ goalCount: result.emittedGoalIds.length,
110459
+ goalIds: result.emittedGoalIds,
110460
+ truncated: result.truncated !== null
110461
+ };
110462
+ }
110463
+ function classifyGoalInjectionFailure(reason, error) {
110464
+ const resolvedErrorClass = error && typeof error === "object" && "constructor" in error && typeof error.constructor?.name === "string" ? error.constructor.name : void 0;
110465
+ return {
110466
+ outcome: "disabled-or-failed",
110467
+ goalCount: 0,
110468
+ goalIds: [],
110469
+ truncated: false,
110470
+ reason,
110471
+ ...resolvedErrorClass ? { errorClass: resolvedErrorClass } : {}
110472
+ };
110473
+ }
110474
+ function resolveGoalContextForDiagnostics(input) {
110475
+ if (!input.listActiveGoals) {
110476
+ return {
110477
+ goalContext: "",
110478
+ classification: classifyGoalInjectionFailure(input.disabledReason ?? "store-unavailable")
110479
+ };
110480
+ }
110481
+ try {
110482
+ const activeGoals = input.listActiveGoals();
110483
+ const injector = input.injector ?? ((goals) => buildGoalContextSection({ activeGoals: goals }));
110484
+ try {
110485
+ const injectionResult = injector(activeGoals);
110486
+ return {
110487
+ goalContext: injectionResult.text,
110488
+ classification: classifyGoalInjectionResult(injectionResult)
110489
+ };
110490
+ } catch (injectorError) {
110491
+ return {
110492
+ goalContext: "",
110493
+ classification: classifyGoalInjectionFailure("injector-threw", injectorError)
110494
+ };
110495
+ }
110496
+ } catch (listError) {
110497
+ return {
110498
+ goalContext: "",
110499
+ classification: classifyGoalInjectionFailure("list-failed", listError)
110500
+ };
110501
+ }
110502
+ }
110503
+ function formatAgentLogLine(input) {
110504
+ const ids = JSON.stringify(input.goalIds);
110505
+ const reason = input.reason ? ` reason=${input.reason}` : "";
110506
+ const errorClass = input.errorClass ? ` err=${input.errorClass}` : "";
110507
+ return `[goal-injection] ${input.outcome} count=${input.goalCount} ids=${ids} truncated=${String(input.truncated)}${reason}${errorClass}`;
110508
+ }
110509
+ async function emitGoalInjectionDiagnostic(input) {
110510
+ const record = {
110511
+ lane: input.lane,
110512
+ outcome: input.outcome,
110513
+ goalCount: input.goalCount,
110514
+ goalIds: [...input.goalIds],
110515
+ truncated: input.truncated,
110516
+ ...input.reason ? { reason: input.reason } : {},
110517
+ ...input.errorClass ? { errorClass: input.errorClass } : {},
110518
+ ...input.runId ? { runId: input.runId } : {},
110519
+ ...input.agentId ? { agentId: input.agentId } : {},
110520
+ ...input.taskId ? { taskId: input.taskId } : {},
110521
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
110522
+ };
110523
+ if (input.store && record.taskId) {
110524
+ try {
110525
+ await input.store.logEntry(record.taskId, formatAgentLogLine(record), void 0, input.runContext ?? void 0);
110526
+ } catch (error) {
110527
+ diagnosticsLog.warn(
110528
+ `failed to append goal-injection task log for ${record.taskId}: ${error instanceof Error ? error.message : String(error)}`
110529
+ );
110530
+ }
110531
+ }
110532
+ const auditStore = input.store;
110533
+ const hasAuditWriter = Boolean(auditStore && typeof auditStore.recordRunAuditEvent === "function");
110534
+ if (!input.runContext || !hasAuditWriter || !auditStore) {
110535
+ diagnosticsLog.warn(
110536
+ `goal-injection diagnostic emitted without run-audit side effect (lane=${record.lane}, hasContext=${Boolean(input.runContext)}, hasWriter=${Boolean(hasAuditWriter)})`
110537
+ );
110538
+ return record;
110539
+ }
110540
+ try {
110541
+ await auditStore.recordRunAuditEvent({
110542
+ taskId: input.runContext.taskId,
110543
+ agentId: input.runContext.agentId,
110544
+ runId: input.runContext.runId,
110545
+ domain: "database",
110546
+ mutationType: "prompt:goal-injection",
110547
+ target: record.lane,
110548
+ metadata: {
110549
+ lane: record.lane,
110550
+ outcome: record.outcome,
110551
+ goalCount: record.goalCount,
110552
+ goalIds: record.goalIds,
110553
+ truncated: record.truncated,
110554
+ ...record.reason ? { reason: record.reason } : {},
110555
+ ...record.errorClass ? { errorClass: record.errorClass } : {},
110556
+ ...record.runId ? { runId: record.runId } : {},
110557
+ ...record.agentId ? { agentId: record.agentId } : {},
110558
+ ...record.taskId ? { taskId: record.taskId } : {}
110559
+ }
110560
+ });
110561
+ } catch (error) {
110562
+ diagnosticsLog.warn(
110563
+ `failed to append goal-injection run-audit event for lane=${record.lane}: ${error instanceof Error ? error.message : String(error)}`
110564
+ );
110565
+ }
110566
+ return record;
110567
+ }
110568
+ var diagnosticsLog;
110569
+ var init_goal_injection_diagnostics = __esm({
110570
+ "../engine/src/goal-injection-diagnostics.ts"() {
110571
+ "use strict";
110572
+ init_goal_context_injector();
110573
+ init_logger2();
110574
+ diagnosticsLog = createLogger2("goal-injection-diagnostics");
110575
+ }
110576
+ });
110577
+
110578
+ // ../engine/src/goal-anchoring-audit.ts
110579
+ async function emitGoalAnchoringAudit(auditor, input) {
110580
+ const isApplied = input.goalsInjected > 0;
110581
+ await auditor.database({
110582
+ type: isApplied ? GOAL_INJECTION_APPLIED : GOAL_INJECTION_SKIPPED,
110583
+ target: input.taskId ?? "goals",
110584
+ metadata: {
110585
+ lane: input.lane,
110586
+ count: input.goalsInjected,
110587
+ ...typeof input.truncated === "boolean" ? { truncated: input.truncated } : {},
110588
+ ...input.reason ? { reason: input.reason } : {}
110589
+ }
110590
+ });
110591
+ }
110592
+ function emitGoalRetrievalAudit(store, ctx, input) {
110593
+ if (!ctx.runId || !ctx.agentId) return;
110594
+ try {
110595
+ store.recordRunAuditEvent({
110596
+ runId: ctx.runId,
110597
+ agentId: ctx.agentId,
110598
+ taskId: ctx.taskId,
110599
+ domain: "database",
110600
+ mutationType: GOAL_RETRIEVAL_INVOKED,
110601
+ target: input.goalId ?? "goals",
110602
+ metadata: {
110603
+ toolName: input.toolName,
110604
+ count: input.resultCount,
110605
+ notFound: input.notFound ?? false
110606
+ }
110607
+ });
110608
+ } catch (error) {
110609
+ console.warn("[fusion-extension] goal retrieval audit emission skipped", error);
110610
+ }
110611
+ }
110612
+ var GOAL_INJECTION_APPLIED, GOAL_INJECTION_SKIPPED, GOAL_RETRIEVAL_INVOKED, GOAL_AUDIT_TYPES;
110613
+ var init_goal_anchoring_audit = __esm({
110614
+ "../engine/src/goal-anchoring-audit.ts"() {
110615
+ "use strict";
110616
+ GOAL_INJECTION_APPLIED = "goal:injection-applied";
110617
+ GOAL_INJECTION_SKIPPED = "goal:injection-skipped";
110618
+ GOAL_RETRIEVAL_INVOKED = "goal:retrieval-invoked";
110619
+ GOAL_AUDIT_TYPES = [GOAL_INJECTION_APPLIED, GOAL_INJECTION_SKIPPED, GOAL_RETRIEVAL_INVOKED];
110620
+ if (new Set(GOAL_AUDIT_TYPES).size !== GOAL_AUDIT_TYPES.length || GOAL_AUDIT_TYPES.some((value) => typeof value !== "string" || !value.startsWith("goal:"))) {
110621
+ throw new Error("Goal anchoring audit mutation types must be unique goal:* strings.");
110622
+ }
110623
+ }
110624
+ });
110625
+
109606
110626
  // ../engine/src/auto-recovery-handlers/branch-worktree.ts
109607
110627
  import { exec as exec31 } from "node:child_process";
109608
110628
  import { existsSync as existsSync39 } from "node:fs";
@@ -110467,7 +111487,7 @@ function isTransientMissingTaskJsonError(error, task) {
110467
111487
  const normalizedTaskJsonPath = resolvePath(filePath);
110468
111488
  return normalizedTaskJsonPath.startsWith(`${normalizedWorktree}/`);
110469
111489
  }
110470
- function detectPendingReviewBlock(task, codeReviewVerdicts) {
111490
+ function detectPendingReviewBlock(task, _codeReviewVerdicts) {
110471
111491
  const inProgressStepIndices = [];
110472
111492
  for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
110473
111493
  if (task.steps[stepIndex]?.status === "in-progress") {
@@ -110479,9 +111499,6 @@ function detectPendingReviewBlock(task, codeReviewVerdicts) {
110479
111499
  }
110480
111500
  const recentActions = (task.log ?? []).slice(-30).map((entry) => entry.action?.trim()).filter((action) => Boolean(action));
110481
111501
  for (const stepIndex of inProgressStepIndices) {
110482
- if (codeReviewVerdicts.get(stepIndex) === "REVISE") {
110483
- return { blocked: true, reason: "code-review-revise-outstanding", stepIndex };
110484
- }
110485
111502
  const stepDisplay = stepIndex + 1;
110486
111503
  const codeRequest = `code review requested for Step ${stepDisplay}`;
110487
111504
  const planRequest = `plan review requested for Step ${stepDisplay}`;
@@ -111042,6 +112059,8 @@ var init_executor = __esm({
111042
112059
  init_worktree_hooks();
111043
112060
  init_agent_instructions();
111044
112061
  init_prompt_layers();
112062
+ init_goal_injection_diagnostics();
112063
+ init_goal_anchoring_audit();
111045
112064
  init_run_audit();
111046
112065
  init_auto_recovery();
111047
112066
  init_restart_recovery_coordinator();
@@ -111428,6 +112447,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
111428
112447
  executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
111429
112448
  try {
111430
112449
  await this.clearResumeFailureState(task);
112450
+ await this.store.updateTask(task.id, {
112451
+ resumeLimboCount: 0,
112452
+ resumeLimboTipSha: null,
112453
+ resumeLimboStepSignature: null
112454
+ });
111431
112455
  await this.store.logEntry(task.id, "Resuming execution after unpause", void 0, this.getRunContextFor(task.id));
111432
112456
  await this.recoverApprovedStepsOnResume(task.id);
111433
112457
  } catch (clearErr) {
@@ -113803,8 +114827,30 @@ ${summary}`,
113803
114827
  if (executorPluginContributions) {
113804
114828
  executorLog.log(`${task.id}: applied plugin prompt contributions for executor-system surface`);
113805
114829
  }
114830
+ const executorGoalResolution = resolveGoalContextForDiagnostics({
114831
+ listActiveGoals: typeof this.store.getGoalStore === "function" ? () => this.store.getGoalStore().listGoals({ status: "active" }) : void 0
114832
+ });
114833
+ const executorGoalContext = executorGoalResolution.goalContext;
114834
+ const executorGoalClassification = executorGoalResolution.classification;
114835
+ await emitGoalAnchoringAudit(audit, {
114836
+ lane: "executor",
114837
+ taskId: task.id,
114838
+ goalsInjected: executorGoalClassification.goalCount,
114839
+ truncated: executorGoalClassification.truncated,
114840
+ reason: executorGoalClassification.outcome === "no-goals" ? "no-active-goals" : void 0
114841
+ });
114842
+ await emitGoalInjectionDiagnostic({
114843
+ lane: "executor",
114844
+ ...executorGoalClassification,
114845
+ runId: engineRunContext.runId,
114846
+ agentId: engineRunContext.agentId,
114847
+ taskId: task.id,
114848
+ store: this.store,
114849
+ runContext: engineRunContext
114850
+ });
113806
114851
  const executorLayers = buildPromptLayers({
113807
114852
  basePrompt: getExecutorSystemPrompt(settings),
114853
+ goalContext: executorGoalContext,
113808
114854
  agentInstructions: executorInstructions,
113809
114855
  pluginContributions: executorPluginContributions
113810
114856
  });
@@ -119274,7 +120320,8 @@ async function collectTaskEvaluationEvidence(params) {
119274
120320
  taskDoneRetryCount: task.taskDoneRetryCount ?? 0,
119275
120321
  verificationFailureCount: task.verificationFailureCount ?? 0,
119276
120322
  mergeConflictBounceCount: task.mergeConflictBounceCount ?? 0,
119277
- mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0
120323
+ mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0,
120324
+ mergeTransientRetryCount: task.mergeTransientRetryCount ?? 0
119278
120325
  }
119279
120326
  }],
119280
120327
  commits: commitEvidence,
@@ -119733,6 +120780,25 @@ function filterPathsByIgnoreList(paths, ignorePaths) {
119733
120780
  }
119734
120781
  return paths.filter((path6) => !normalizedIgnorePaths.some((ignore) => isIgnoredOverlapPath(path6, ignore)));
119735
120782
  }
120783
+ function getUnmetSchedulingDependencies(task, tasks) {
120784
+ return task.dependencies.filter((depId) => {
120785
+ const dep = tasks.find((candidate) => candidate.id === depId);
120786
+ return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
120787
+ });
120788
+ }
120789
+ function isRunnableQueuedOverlapCandidate(task, tasks, now = Date.now(), activeScopes, scope = []) {
120790
+ if (task.column !== "todo" || task.status !== "queued") return false;
120791
+ if (task.paused || task.userPaused) return false;
120792
+ if (task.nextRecoveryAt && new Date(task.nextRecoveryAt).getTime() > now) return false;
120793
+ if (getUnmetSchedulingDependencies(task, tasks).length > 0) return false;
120794
+ if (!activeScopes || activeScopes.size === 0) return true;
120795
+ if (scope.length === 0) return true;
120796
+ for (const activeScope of activeScopes.values()) {
120797
+ if (!activeScope.length) continue;
120798
+ if (pathsOverlap2(scope, activeScope)) return false;
120799
+ }
120800
+ return true;
120801
+ }
119736
120802
  function findHigherPriorityQueuedOverlap(candidate, queuedScopes, overlap) {
119737
120803
  let higher = null;
119738
120804
  for (const queued of queuedScopes) {
@@ -120407,34 +121473,47 @@ var init_scheduler = __esm({
120407
121473
  );
120408
121474
  }
120409
121475
  const activeScopes = /* @__PURE__ */ new Map();
121476
+ const activeScopeColumns = /* @__PURE__ */ new Map();
121477
+ const setActiveScopeLease = (taskId, scope, column) => {
121478
+ activeScopes.set(taskId, scope);
121479
+ activeScopeColumns.set(taskId, column);
121480
+ };
120410
121481
  const inversionEmitted = /* @__PURE__ */ new Set();
120411
121482
  const queuedHigherPriorityScopes = [];
121483
+ const queuedHigherPriorityTaskById = /* @__PURE__ */ new Map();
121484
+ const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
121485
+ const filteredScopeByTaskId = /* @__PURE__ */ new Map();
121486
+ const getFilteredFileScope = async (taskId) => {
121487
+ const cached = filteredScopeByTaskId.get(taskId);
121488
+ if (cached) return cached;
121489
+ const scope = await this.store.parseFileScopeFromPrompt(taskId);
121490
+ const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121491
+ filteredScopeByTaskId.set(taskId, filteredScope);
121492
+ return filteredScope;
121493
+ };
120412
121494
  if (settings.groupOverlappingFiles) {
120413
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
120414
121495
  for (const t of inProgress) {
120415
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120416
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
120417
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
121496
+ const filteredScope = await getFilteredFileScope(t.id);
121497
+ if (filteredScope.length > 0) setActiveScopeLease(t.id, filteredScope, "in-progress");
121498
+ }
121499
+ const inReviewWithWorktree = tasks.filter(
121500
+ (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
121501
+ );
121502
+ for (const t of inReviewWithWorktree) {
121503
+ const filteredScope = await getFilteredFileScope(t.id);
121504
+ if (filteredScope.length > 0) setActiveScopeLease(t.id, filteredScope, "in-review");
120418
121505
  }
120419
121506
  for (const t of todo) {
120420
- if (t.status !== "queued" || t.paused || t.userPaused) continue;
120421
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120422
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121507
+ const filteredScope = await getFilteredFileScope(t.id);
120423
121508
  if (filteredScope.length === 0) continue;
121509
+ if (!isRunnableQueuedOverlapCandidate(t, tasks, now, activeScopes, filteredScope)) continue;
120424
121510
  queuedHigherPriorityScopes.push({
120425
121511
  id: t.id,
120426
121512
  priority: t.priority,
120427
121513
  createdAt: t.createdAt,
120428
121514
  scope: filteredScope
120429
121515
  });
120430
- }
120431
- const inReviewWithWorktree = tasks.filter(
120432
- (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
120433
- );
120434
- for (const t of inReviewWithWorktree) {
120435
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120436
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
120437
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
121516
+ queuedHigherPriorityTaskById.set(t.id, t);
120438
121517
  }
120439
121518
  }
120440
121519
  const ordered = resolveDependencyOrder(todo);
@@ -120455,10 +121534,7 @@ var init_scheduler = __esm({
120455
121534
  continue;
120456
121535
  }
120457
121536
  }
120458
- const unmetDeps = task.dependencies.filter((depId) => {
120459
- const dep = tasks.find((t) => t.id === depId);
120460
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
120461
- });
121537
+ const unmetDeps = getUnmetSchedulingDependencies(task, tasks);
120462
121538
  if (unmetDeps.length > 0) {
120463
121539
  await this.store.updateTask(task.id, {
120464
121540
  status: "queued",
@@ -120492,17 +121568,18 @@ var init_scheduler = __esm({
120492
121568
  continue;
120493
121569
  }
120494
121570
  if (settings.groupOverlappingFiles) {
120495
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
120496
- const taskScope = filterPathsByIgnoreList(
120497
- await this.store.parseFileScopeFromPrompt(task.id),
120498
- overlapIgnorePaths
120499
- );
121571
+ const taskScope = await getFilteredFileScope(task.id);
120500
121572
  if (taskScope.length > 0) {
120501
121573
  const activeScopeEntries = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId));
120502
121574
  const overlapBlockerId = task.overlapBlockedBy || task.blockedBy;
120503
121575
  const currentBlockerScope = overlapBlockerId ? activeScopes.get(overlapBlockerId) : void 0;
120504
121576
  const hasValidCurrentBlocker = Boolean(overlapBlockerId) && Boolean(currentBlockerScope) && this.pathsOverlap(taskScope, currentBlockerScope);
120505
121577
  const overlappingTaskId = hasValidCurrentBlocker ? overlapBlockerId : activeScopeEntries.find(([, ipScope]) => this.pathsOverlap(taskScope, ipScope))?.[0] ?? null;
121578
+ const runnableQueuedHigherPriorityScopes = queuedHigherPriorityScopes.filter((queuedCandidate) => {
121579
+ const queuedTask = queuedHigherPriorityTaskById.get(queuedCandidate.id);
121580
+ if (!queuedTask) return false;
121581
+ return isRunnableQueuedOverlapCandidate(queuedTask, tasks, now, activeScopes, queuedCandidate.scope);
121582
+ });
120506
121583
  const higherPriorityQueuedOverlap = findHigherPriorityQueuedOverlap(
120507
121584
  {
120508
121585
  id: task.id,
@@ -120510,7 +121587,7 @@ var init_scheduler = __esm({
120510
121587
  createdAt: task.createdAt,
120511
121588
  scope: taskScope
120512
121589
  },
120513
- queuedHigherPriorityScopes,
121590
+ runnableQueuedHigherPriorityScopes,
120514
121591
  this.pathsOverlap.bind(this)
120515
121592
  );
120516
121593
  if (higherPriorityQueuedOverlap) {
@@ -120525,7 +121602,7 @@ var init_scheduler = __esm({
120525
121602
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
120526
121603
  await this.logDispatchQueuedReason(
120527
121604
  task.id,
120528
- `queued \u2014 deferred for higher-priority queued task ${higherPriorityQueuedOverlap.id} (overlap)`
121605
+ `queued \u2014 deferred for higher-priority runnable queued task ${higherPriorityQueuedOverlap.id} (overlap)`
120529
121606
  );
120530
121607
  continue;
120531
121608
  }
@@ -120557,7 +121634,7 @@ var init_scheduler = __esm({
120557
121634
  blockerId: overlapBlockerTask.id,
120558
121635
  blockerPriority: overlapBlockerTask.priority ?? null,
120559
121636
  blockerCreatedAt: overlapBlockerTask.createdAt ?? null,
120560
- blockerColumn: overlapBlockerTask.column,
121637
+ blockerColumn: activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask.column,
120561
121638
  source: "scheduler.overlap-priority-inversion"
120562
121639
  }
120563
121640
  });
@@ -120568,7 +121645,11 @@ var init_scheduler = __esm({
120568
121645
  }
120569
121646
  }
120570
121647
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
120571
- await this.logDispatchQueuedReason(task.id, `queued \u2014 file scope overlap with ${overlappingTaskId}`);
121648
+ const activeLeaseColumn = activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask?.column ?? "unknown";
121649
+ await this.logDispatchQueuedReason(
121650
+ task.id,
121651
+ `queued \u2014 blocked by active file-scope lease ${overlappingTaskId} (column=${activeLeaseColumn})`
121652
+ );
120572
121653
  continue;
120573
121654
  }
120574
121655
  if (task.overlapBlockedBy) {
@@ -120787,11 +121868,8 @@ var init_scheduler = __esm({
120787
121868
  this.options.onSchedule?.(task);
120788
121869
  started++;
120789
121870
  if (settings.groupOverlappingFiles) {
120790
- const scope = filterPathsByIgnoreList(
120791
- await this.store.parseFileScopeFromPrompt(task.id),
120792
- settings.overlapIgnorePaths
120793
- );
120794
- if (scope.length > 0) activeScopes.set(task.id, scope);
121871
+ const scope = await getFilteredFileScope(task.id);
121872
+ if (scope.length > 0) setActiveScopeLease(task.id, scope, "in-progress");
120795
121873
  }
120796
121874
  }
120797
121875
  await this.emitHighOverlapFanoutWarnings(tasks);
@@ -123821,6 +124899,8 @@ var init_agent_heartbeat = __esm({
123821
124899
  init_agent_instructions();
123822
124900
  init_heartbeat_procedure_resolver();
123823
124901
  init_prompt_layers();
124902
+ init_goal_injection_diagnostics();
124903
+ init_goal_anchoring_audit();
123824
124904
  init_logger2();
123825
124905
  init_worktree_acquisition();
123826
124906
  init_run_audit();
@@ -125212,12 +126292,16 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125212
126292
  engineRunContext.taskId = taskId;
125213
126293
  }
125214
126294
  let autoClaimCandidates = [];
126295
+ let autoClaimSnapshotCandidateCount = 0;
126296
+ let autoClaimRoleFilteredCount = 0;
125215
126297
  const autoClaimEnabled = isAutoClaimRelevantTasksEnabled(agent);
125216
126298
  if (!taskId && canRunNoTaskHeartbeat && autoClaimEnabled && this.snapshotManager) {
125217
126299
  try {
125218
126300
  const snapshot = await this.snapshotManager.getSnapshot();
126301
+ autoClaimSnapshotCandidateCount = snapshot.tasks.length;
125219
126302
  const roleCompatibleCandidates = snapshot.tasks.filter((candidate) => canAgentTakeImplementationTask(agent, candidate));
125220
126303
  const skippedIncompatibleCount = snapshot.tasks.length - roleCompatibleCandidates.length;
126304
+ autoClaimRoleFilteredCount = skippedIncompatibleCount;
125221
126305
  if (skippedIncompatibleCount > 0) {
125222
126306
  heartbeatLog.log(
125223
126307
  `Agent ${agentId} (role=${agent.role}) skipped auto-claim of ${skippedIncompatibleCount} implementation task(s) \u2014 only executor agents may claim implementation work`
@@ -125473,8 +126557,29 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125473
126557
  if (heartbeatPluginContributions) {
125474
126558
  heartbeatLog.log(`applied plugin prompt contributions for heartbeat surface`);
125475
126559
  }
126560
+ const heartbeatGoalResolution = resolveGoalContextForDiagnostics({
126561
+ listActiveGoals: typeof taskStore.getGoalStore === "function" ? () => taskStore.getGoalStore().listGoals({ status: "active" }) : void 0
126562
+ });
126563
+ const heartbeatGoalContext = heartbeatGoalResolution.goalContext;
126564
+ const heartbeatGoalClassification = heartbeatGoalResolution.classification;
126565
+ await emitGoalAnchoringAudit(audit, {
126566
+ lane: "heartbeat",
126567
+ taskId,
126568
+ goalsInjected: heartbeatGoalClassification.goalCount,
126569
+ truncated: heartbeatGoalClassification.truncated,
126570
+ reason: heartbeatGoalClassification.outcome === "no-goals" ? "no-active-goals" : void 0
126571
+ });
126572
+ await emitGoalInjectionDiagnostic({
126573
+ lane: "heartbeat",
126574
+ ...heartbeatGoalClassification,
126575
+ runId: run.id,
126576
+ agentId,
126577
+ store: taskStore,
126578
+ runContext: engineRunContext
126579
+ });
125476
126580
  const heartbeatLayers = buildPromptLayers({
125477
126581
  basePrompt: baseHeartbeatSystemPrompt,
126582
+ goalContext: heartbeatGoalContext,
125478
126583
  agentInstructions: [resolvedInstructionsText, memoryInstructions, selfImprovePrompt].filter((part) => part.trim()).join("\n\n"),
125479
126584
  pluginContributions: heartbeatPluginContributions
125480
126585
  });
@@ -125749,10 +126854,15 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125749
126854
  );
125750
126855
  }
125751
126856
  const promptCandidateLimit = resolveAutoClaimCandidatesInPromptLimit(agent, heartbeatModelSettings);
126857
+ 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";
126858
+ const noRoleCompatibleCandidateLines = autoClaimCandidates.length === 0 && autoClaimSnapshotCandidateCount > 0 && autoClaimRoleFilteredCount > 0 ? [
126859
+ `- Snapshot found ${autoClaimSnapshotCandidateCount} eligible Todo task(s), but this agent role cannot auto-claim implementation work.`,
126860
+ "- Backlog auto-claim is restricted to executor-role agents; use delegation or create coordination follow-up instead of assuming the board is empty."
126861
+ ] : [];
125752
126862
  const candidateLines = promptCandidateLimit > 0 ? [
125753
126863
  "",
125754
126864
  "Open Task Candidates (auto-claim scan):",
125755
- ...autoClaimCandidates.slice(0, promptCandidateLimit).map((candidate) => `- ${candidate.id}: ${candidate.title ?? candidate.descriptionFirstLine}`)
126865
+ ...autoClaimCandidates.length > 0 ? autoClaimCandidates.slice(0, promptCandidateLimit).map((candidate) => `- ${candidate.id}: ${candidate.title ?? candidate.descriptionFirstLine}`) : noRoleCompatibleCandidateLines
125756
126866
  ] : [];
125757
126867
  executionPrompt = [
125758
126868
  `Heartbeat execution for agent "${agent.name}" (ID: ${agent.id})`,
@@ -125768,7 +126878,7 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125768
126878
  ...wakeTriggerSourceLine ? [wakeTriggerSourceLine] : [],
125769
126879
  `- pending messages: ${pendingMessages.length}`,
125770
126880
  `- pending room messages: ${pendingRoomMessages.total}`,
125771
- `- auto-claim relevant tasks: ${autoClaimEnabled ? promptCandidateLimit === 0 ? "disabled (prompt-suppressed)" : "enabled" : "disabled"}`,
126881
+ `- auto-claim relevant tasks: ${autoClaimStatus}`,
125772
126882
  "",
125773
126883
  "Treat this wake delta as the highest-priority change for this heartbeat.",
125774
126884
  "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
@@ -127001,78 +128111,6 @@ var init_error_classifier = __esm({
127001
128111
  }
127002
128112
  });
127003
128113
 
127004
- // ../engine/src/goal-context-injector.ts
127005
- function normalizeTitle(title) {
127006
- return title.replace(/\s*\n+\s*/g, " ").trim();
127007
- }
127008
- function renderGoalContext(goals) {
127009
- const lines = goals.map((goal) => `- ${goal.id}: ${normalizeTitle(goal.title)}`);
127010
- return `## Active Goals
127011
-
127012
- ${lines.join("\n")}`;
127013
- }
127014
- function buildGoalContextSection(input) {
127015
- const { activeGoals, onTruncated } = input;
127016
- if (activeGoals.length === 0) {
127017
- return { text: "", emittedGoalIds: [], truncated: null };
127018
- }
127019
- const sortedGoals = [...activeGoals].sort((a, b2) => {
127020
- const byCreated = a.createdAt.localeCompare(b2.createdAt);
127021
- if (byCreated !== 0) {
127022
- return byCreated;
127023
- }
127024
- return a.id.localeCompare(b2.id);
127025
- });
127026
- const resolvedMaxGoals = Math.min(input.maxGoals ?? MAX_INJECTED_GOALS, MAX_INJECTED_GOALS);
127027
- const charBudget = input.charBudget ?? DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
127028
- const droppedGoalIds = [];
127029
- let selectedGoals = sortedGoals.slice(0, resolvedMaxGoals);
127030
- if (sortedGoals.length > resolvedMaxGoals) {
127031
- droppedGoalIds.push(...sortedGoals.slice(resolvedMaxGoals).map((goal) => goal.id));
127032
- }
127033
- let text = renderGoalContext(selectedGoals);
127034
- let usedBudgetDrop = false;
127035
- while (selectedGoals.length > 1 && text.length > charBudget) {
127036
- const dropped = selectedGoals[selectedGoals.length - 1];
127037
- selectedGoals = selectedGoals.slice(0, -1);
127038
- droppedGoalIds.push(dropped.id);
127039
- text = renderGoalContext(selectedGoals);
127040
- usedBudgetDrop = true;
127041
- }
127042
- let truncated = null;
127043
- if (droppedGoalIds.length > 0) {
127044
- truncated = {
127045
- reason: usedBudgetDrop ? "budget" : "cap",
127046
- requested: activeGoals.length,
127047
- emitted: selectedGoals.length,
127048
- droppedGoalIds,
127049
- charBudget,
127050
- producedChars: text.length
127051
- };
127052
- onTruncated?.(truncated);
127053
- }
127054
- return {
127055
- text,
127056
- emittedGoalIds: selectedGoals.map((goal) => goal.id),
127057
- truncated
127058
- };
127059
- }
127060
- var MAX_INJECTED_GOALS, resolvedActiveGoalLimit, DEFAULT_GOAL_INJECTION_CHAR_BUDGET;
127061
- var init_goal_context_injector = __esm({
127062
- "../engine/src/goal-context-injector.ts"() {
127063
- "use strict";
127064
- init_src();
127065
- MAX_INJECTED_GOALS = 5;
127066
- resolvedActiveGoalLimit = "ACTIVE_GOAL_LIMIT" in src_exports && typeof ACTIVE_GOAL_LIMIT === "number" ? ACTIVE_GOAL_LIMIT : MAX_INJECTED_GOALS;
127067
- if (MAX_INJECTED_GOALS > resolvedActiveGoalLimit) {
127068
- throw new Error(
127069
- `MAX_INJECTED_GOALS (${MAX_INJECTED_GOALS}) cannot exceed ACTIVE_GOAL_LIMIT (${resolvedActiveGoalLimit})`
127070
- );
127071
- }
127072
- DEFAULT_GOAL_INJECTION_CHAR_BUDGET = 600;
127073
- }
127074
- });
127075
-
127076
128114
  // ../engine/src/experiment/benchmark-runner.ts
127077
128115
  import { spawn as spawn7 } from "node:child_process";
127078
128116
  import { mkdtemp, writeFile as writeFile17 } from "node:fs/promises";
@@ -132991,7 +134029,8 @@ var init_in_process_runtime = __esm({
132991
134029
  this.usageLimitPauser = pauser;
132992
134030
  }
132993
134031
  /**
132994
- * Set up event forwarding from TaskStore to runtime listeners.
134032
+ * Set up event forwarding from TaskStore to runtime listeners
134033
+ * for task:created, task:moved, task:updated, and task:deleted.
132995
134034
  */
132996
134035
  setupEventForwarding() {
132997
134036
  this.taskStore.on("task:created", (task) => {
@@ -133006,6 +134045,10 @@ var init_in_process_runtime = __esm({
133006
134045
  this.recordActivity();
133007
134046
  this.emit("task:updated", task);
133008
134047
  });
134048
+ this.taskStore.on("task:deleted", (task, meta) => {
134049
+ this.recordActivity();
134050
+ this.emit("task:deleted", task, meta);
134051
+ });
133009
134052
  runtimeLog.log("Event forwarding setup complete");
133010
134053
  }
133011
134054
  /**
@@ -133056,7 +134099,7 @@ var init_in_process_runtime = __esm({
133056
134099
  function generateCorrelationId() {
133057
134100
  return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
133058
134101
  }
133059
- var START_RUNTIME, STOP_RUNTIME, GET_METRICS, OK, ERROR, PONG, TASK_CREATED, TASK_MOVED, TASK_UPDATED, ERROR_EVENT, HEALTH_CHANGED;
134102
+ var START_RUNTIME, STOP_RUNTIME, GET_METRICS, OK, ERROR, PONG, TASK_CREATED, TASK_MOVED, TASK_UPDATED, TASK_DELETED, ERROR_EVENT, HEALTH_CHANGED;
133060
134103
  var init_ipc_protocol = __esm({
133061
134104
  "../engine/src/ipc/ipc-protocol.ts"() {
133062
134105
  "use strict";
@@ -133069,6 +134112,7 @@ var init_ipc_protocol = __esm({
133069
134112
  TASK_CREATED = "TASK_CREATED";
133070
134113
  TASK_MOVED = "TASK_MOVED";
133071
134114
  TASK_UPDATED = "TASK_UPDATED";
134115
+ TASK_DELETED = "TASK_DELETED";
133072
134116
  ERROR_EVENT = "ERROR_EVENT";
133073
134117
  HEALTH_CHANGED = "HEALTH_CHANGED";
133074
134118
  }
@@ -133453,6 +134497,9 @@ var init_child_process_runtime = __esm({
133453
134497
  this.ipcHost.on(TASK_UPDATED, (payload) => {
133454
134498
  this.emit("task:updated", payload.task);
133455
134499
  });
134500
+ this.ipcHost.on(TASK_DELETED, (payload) => {
134501
+ this.emit("task:deleted", payload.task, payload.meta);
134502
+ });
133456
134503
  this.ipcHost.on(ERROR_EVENT, (payload) => {
133457
134504
  const error = new Error(payload.message);
133458
134505
  if (payload.code) {
@@ -134575,6 +135622,9 @@ var init_project_manager = __esm({
134575
135622
  runtime.on("task:updated", (task) => {
134576
135623
  this.emit("task:updated", { projectId, projectName, task });
134577
135624
  });
135625
+ runtime.on("task:deleted", (task, meta) => {
135626
+ this.emit("task:deleted", { projectId, projectName, task, meta });
135627
+ });
134578
135628
  runtime.on("error", (error) => {
134579
135629
  this.emit("error", { projectId, projectName, error });
134580
135630
  this.logActivity(
@@ -136253,6 +137303,7 @@ var init_project_engine = __esm({
136253
137303
  init_research_step_runner();
136254
137304
  init_run_audit();
136255
137305
  init_verification_followup_dedup();
137306
+ init_transient_error_detector();
136256
137307
  init_tunnel_process_manager();
136257
137308
  execFileAsync12 = promisify45(execFile17);
136258
137309
  MERGE_HANDOFF_GRACE_MS = 300;
@@ -136323,6 +137374,10 @@ var init_project_engine = __esm({
136323
137374
  manualMergeResolvers = /* @__PURE__ */ new Map();
136324
137375
  shuttingDown = false;
136325
137376
  static MAX_AUTO_MERGE_RETRIES = 3;
137377
+ /** FN-5697/FN-5674: cap transient provider/network abort retries in auto-merge.
137378
+ * Examples: "This operation was aborted", "socket hang up", `server_error`.
137379
+ * After this cap, the task is parked failed for human visibility. */
137380
+ static MAX_AUTO_MERGE_TRANSIENT_RETRIES = 3;
136326
137381
  /** Cap on outer in-review→in-progress bounces caused by deterministic
136327
137382
  * verification failures during auto-merge. After this many failed merges
136328
137383
  * for the same task, we stop bouncing it back, mark it failed, and create
@@ -137305,7 +138360,15 @@ ${detail}`
137305
138360
  this.internalEnqueueMerge(taskId);
137306
138361
  continue;
137307
138362
  }
137308
- const blockerReason = getTaskHardMergeBlocker(task);
138363
+ const blockerReason = getTaskHardMergeBlocker({
138364
+ ...task,
138365
+ // Merge-confirmed tasks have already landed. Treat stale merge
138366
+ // in-flight statuses as soft state to clear during finalization,
138367
+ // not hard blockers that park an otherwise confirmed merge as failed.
138368
+ paused: false,
138369
+ status: task.status === "merging" || task.status === "merging-pr" ? void 0 : task.status,
138370
+ error: void 0
138371
+ });
137309
138372
  if (blockerReason) {
137310
138373
  await store.updateTask(taskId, {
137311
138374
  status: "failed",
@@ -137824,6 +138887,16 @@ ${detail}`
137824
138887
  }
137825
138888
  } else {
137826
138889
  try {
138890
+ if (await this.maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg)) {
138891
+ continue;
138892
+ }
138893
+ if (this.isTransientMergeRetryExhausted(taskOnErr, errorMsg)) {
138894
+ await store.logEntry(
138895
+ taskId,
138896
+ `Auto-merge transient retries exhausted (${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}); parking task as failed: ${errorMsg}`,
138897
+ "MergeTransientRetryExhausted"
138898
+ );
138899
+ }
137827
138900
  await store.updateTask(taskId, {
137828
138901
  status: "failed",
137829
138902
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -137842,6 +138915,16 @@ ${detail}`
137842
138915
  }
137843
138916
  } else {
137844
138917
  try {
138918
+ if (await this.maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg)) {
138919
+ continue;
138920
+ }
138921
+ if (this.isTransientMergeRetryExhausted(taskOnErr, errorMsg)) {
138922
+ await store.logEntry(
138923
+ taskId,
138924
+ `Auto-merge transient retries exhausted (${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES}); parking task as failed: ${errorMsg}`,
138925
+ "MergeTransientRetryExhausted"
138926
+ );
138927
+ }
137845
138928
  await store.updateTask(taskId, {
137846
138929
  status: "failed",
137847
138930
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -137877,6 +138960,40 @@ ${detail}`
137877
138960
  this.mergeRunning = false;
137878
138961
  }
137879
138962
  }
138963
+ isTransientMergeRetryExhausted(task, errorMsg) {
138964
+ if (!task || !isTransientError(errorMsg)) {
138965
+ return false;
138966
+ }
138967
+ const current = task.mergeTransientRetryCount ?? 0;
138968
+ return current >= _ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES;
138969
+ }
138970
+ async maybeRetryTransientMerge(store, taskId, taskOnErr, errorMsg) {
138971
+ if (!taskOnErr || !isTransientError(errorMsg)) {
138972
+ return false;
138973
+ }
138974
+ const currentRetries = taskOnErr.mergeTransientRetryCount ?? 0;
138975
+ if (currentRetries >= _ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES) {
138976
+ return false;
138977
+ }
138978
+ const nextRetryCount = currentRetries + 1;
138979
+ const delayMs = 5e3 * Math.pow(2, currentRetries);
138980
+ await store.updateTask(taskId, {
138981
+ mergeTransientRetryCount: nextRetryCount,
138982
+ status: null
138983
+ });
138984
+ await store.logEntry(
138985
+ taskId,
138986
+ `Auto-merge transient retry ${nextRetryCount}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES} scheduled in ${delayMs / 1e3}s: ${errorMsg}`,
138987
+ "MergeTransientRetry"
138988
+ );
138989
+ runtimeLog.log(
138990
+ `Auto-merge transient retry ${nextRetryCount}/${_ProjectEngine.MAX_AUTO_MERGE_TRANSIENT_RETRIES} for ${taskId} in ${delayMs / 1e3}s`
138991
+ );
138992
+ setTimeout(() => {
138993
+ if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
138994
+ }, delayMs);
138995
+ return true;
138996
+ }
137880
138997
  wireAutoMerge(store, _cwd) {
137881
138998
  this.taskMovedHandler = async ({ task, to: to2 }) => {
137882
138999
  if (to2 !== "in-review") return;
@@ -141441,6 +142558,9 @@ __export(src_exports2, {
141441
142558
  ExperimentGitNotConfiguredError: () => ExperimentGitNotConfiguredError,
141442
142559
  ExperimentMaxIterationsError: () => ExperimentMaxIterationsError,
141443
142560
  ExperimentRevertConflictError: () => ExperimentRevertConflictError,
142561
+ GOAL_INJECTION_APPLIED: () => GOAL_INJECTION_APPLIED,
142562
+ GOAL_INJECTION_SKIPPED: () => GOAL_INJECTION_SKIPPED,
142563
+ GOAL_RETRIEVAL_INVOKED: () => GOAL_RETRIEVAL_INVOKED,
141444
142564
  GitHubProvider: () => GitHubProvider,
141445
142565
  HEARTBEAT_NO_TASK_SYSTEM_PROMPT: () => HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
141446
142566
  HEARTBEAT_PROCEDURE: () => HEARTBEAT_PROCEDURE,
@@ -141532,6 +142652,8 @@ __export(src_exports2, {
141532
142652
  buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
141533
142653
  buildSystemPromptWithInstructions: () => buildSystemPromptWithInstructions,
141534
142654
  classifyBootstrapMisbinding: () => classifyBootstrapMisbinding,
142655
+ classifyGoalInjectionFailure: () => classifyGoalInjectionFailure,
142656
+ classifyGoalInjectionResult: () => classifyGoalInjectionResult,
141535
142657
  classifyTaskError: () => classifyTaskError,
141536
142658
  cleanupOrphanedWorktrees: () => cleanupOrphanedWorktrees,
141537
142659
  cleanupSecretsEnvFile: () => cleanupSecretsEnvFile,
@@ -141556,6 +142678,9 @@ __export(src_exports2, {
141556
142678
  describeModel: () => describeModel,
141557
142679
  dropAutostashBySha: () => dropAutostashBySha,
141558
142680
  dropAutostashHandle: () => dropAutostashHandle,
142681
+ emitGoalAnchoringAudit: () => emitGoalAnchoringAudit,
142682
+ emitGoalInjectionDiagnostic: () => emitGoalInjectionDiagnostic,
142683
+ emitGoalRetrievalAudit: () => emitGoalRetrievalAudit,
141559
142684
  ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
141560
142685
  executeApprovedAgentProvisioning: () => executeApprovedAgentProvisioning,
141561
142686
  executeApprovedWorktrunkInstall: () => executeApprovedWorktrunkInstall,
@@ -141671,6 +142796,8 @@ var init_src2 = __esm({
141671
142796
  init_web_fetch();
141672
142797
  init_error_classifier();
141673
142798
  init_goal_context_injector();
142799
+ init_goal_injection_diagnostics();
142800
+ init_goal_anchoring_audit();
141674
142801
  init_worktrunk_installer();
141675
142802
  init_worktrunk_failure_handler();
141676
142803
  init_usage_limit_detector();
@@ -164297,12 +165424,17 @@ Status: ${createdMission.status}${createdMission.autoAdvance ? "\nAuto-advance:
164297
165424
  )
164298
165425
  }),
164299
165426
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
165427
+ const fnCtx = ctx;
164300
165428
  const store = await getStore2(ctx.cwd);
164301
165429
  const goalStore = store.getGoalStore();
164302
165430
  const status = params.status ?? "active";
164303
165431
  const goals = status === "all" ? goalStore.listGoals() : goalStore.listGoals({ status });
164304
165432
  const activeCount = goalStore.listGoals({ status: "active" }).length;
164305
165433
  const softWarning = activeCount >= 3;
165434
+ emitGoalRetrievalAudit(store, fnCtx, {
165435
+ toolName: "fn_goal_list",
165436
+ resultCount: goals.length
165437
+ });
164306
165438
  const lines = [];
164307
165439
  lines.push(`Goals (${goals.length}) [filter: ${status}]`);
164308
165440
  lines.push(`Active: ${activeCount}/5`);
@@ -164421,10 +165553,17 @@ Status: ${goal.status}${softWarning ? `
164421
165553
  id: Type8.String({ description: "Goal ID (G-\u2026)" })
164422
165554
  }),
164423
165555
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
165556
+ const fnCtx = ctx;
164424
165557
  const store = await getStore2(ctx.cwd);
164425
165558
  const goalStore = store.getGoalStore();
164426
165559
  const goal = goalStore.getGoal(params.id);
164427
165560
  if (!goal) {
165561
+ emitGoalRetrievalAudit(store, fnCtx, {
165562
+ toolName: "fn_goal_show",
165563
+ resultCount: 0,
165564
+ goalId: params.id,
165565
+ notFound: true
165566
+ });
164428
165567
  return {
164429
165568
  isError: true,
164430
165569
  content: [{ type: "text", text: `Goal ${params.id} not found` }],
@@ -164439,6 +165578,11 @@ Status: ${goal.status}${softWarning ? `
164439
165578
  if (goal.description) {
164440
165579
  lines.push(`Description: ${goal.description}`);
164441
165580
  }
165581
+ emitGoalRetrievalAudit(store, fnCtx, {
165582
+ toolName: "fn_goal_show",
165583
+ resultCount: 1,
165584
+ goalId: params.id
165585
+ });
164442
165586
  return {
164443
165587
  content: [{ type: "text", text: lines.join("\n") }],
164444
165588
  details: { goal }