@runfusion/fusion 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/bin.js +1599 -174
  2. package/dist/client/.vite/manifest.json +66 -66
  3. package/dist/client/assets/AgentDetailView-CquLtIia.js +18 -0
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-BoD6come.js → AgentPermissionPolicyEditor-Dk-oEs1X.js} +1 -1
  5. package/dist/client/assets/AgentsView-BZVOpQ05.js +29 -0
  6. package/dist/client/assets/ChatView-CErtz86Y.js +6 -0
  7. package/dist/client/assets/{DevServerView-Dd613aZ5.js → DevServerView-BRA00XoT.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-H-fUl2dF.js → DirectoryPicker-D496NTjZ.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-Dshc9e5K.js → DocumentsView-DIRlgdeB.js} +1 -1
  10. package/dist/client/assets/{EvalsView-50W0K5h7.js → EvalsView-By5mtr80.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-BXBljPFj.js → ExperimentalAgentOnboardingModal-Br57oNdX.js} +1 -1
  12. package/dist/client/assets/GoalsView-B65urz9k.css +1 -0
  13. package/dist/client/assets/GoalsView-COI14CWJ.js +1 -0
  14. package/dist/client/assets/{InsightsView-sVbGRKTl.js → InsightsView-Hm7vX2lr.js} +2 -2
  15. package/dist/client/assets/{MemoryView-006a_sB0.js → MemoryView-BLozyYur.js} +1 -1
  16. package/dist/client/assets/{NodesView-CiRPqgVx.js → NodesView-bXC6WoE2.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-Y1ETuTxf.js → PiExtensionsManager-DEiqcMkx.js} +2 -2
  18. package/dist/client/assets/{PluginManager-Df_KHiXn.js → PluginManager-l4224zxk.js} +1 -1
  19. package/dist/client/assets/{ResearchView-DU_QuS_-.js → ResearchView-CpsR1Q56.js} +1 -1
  20. package/dist/client/assets/{SecretsView-D61Jvysn.js → SecretsView-Bwme1zZI.js} +1 -1
  21. package/dist/client/assets/{SettingsModal-BarzmqNb.js → SettingsModal-32vUNWRe.js} +1 -1
  22. package/dist/client/assets/SettingsModal-D_uIvf6n.css +1 -0
  23. package/dist/client/assets/SettingsModal-DdrXxMPW.js +31 -0
  24. package/dist/client/assets/{SetupWizardModal-BF3iRNq9.js → SetupWizardModal-BFD9E0H_.js} +1 -1
  25. package/dist/client/assets/{SkillsView-tCKOFw3j.js → SkillsView-CZMLY7F_.js} +1 -1
  26. package/dist/client/assets/{StashRecoveryView-nGtypd10.js → StashRecoveryView-jgZeWbZo.js} +1 -1
  27. package/dist/client/assets/{TodoView-IyGPV6AO.js → TodoView-DXdnOymj.js} +2 -2
  28. package/dist/client/assets/{dashboard-view-DiA5-hnb.js → dashboard-view-H8SnaCPP.js} +2 -2
  29. package/dist/client/assets/{folder-open-Byqfi_N-.js → folder-open-Dh5_gv6L.js} +1 -1
  30. package/dist/client/assets/index-B9hAW3eU.css +1 -0
  31. package/dist/client/assets/index-w1dPHtmq.js +742 -0
  32. package/dist/client/assets/{upload-D6_BjqVi.js → upload-ntU2YHPl.js} +1 -1
  33. package/dist/client/assets/{users-CERYgInO.js → users-BWK61s9U.js} +1 -1
  34. package/dist/client/index.html +40 -3
  35. package/dist/client/version.json +1 -1
  36. package/dist/droid-cli/package.json +1 -1
  37. package/dist/extension.js +1449 -125
  38. package/dist/pi-claude-cli/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  40. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  42. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  43. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  44. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  46. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  47. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  48. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  49. package/package.json +1 -1
  50. package/skill/fusion/SKILL.md +1 -1
  51. package/skill/fusion/references/extension-tools.md +16 -0
  52. package/skill/fusion/references/fusion-capabilities.md +1 -0
  53. package/dist/client/assets/AgentDetailView-CGycokdR.js +0 -18
  54. package/dist/client/assets/AgentsView-B_afRRfc.js +0 -29
  55. package/dist/client/assets/ChatView-IjNrSZSf.js +0 -6
  56. package/dist/client/assets/GoalsView-Cg136Jcn.css +0 -1
  57. package/dist/client/assets/GoalsView-Dh508Z2s.js +0 -1
  58. package/dist/client/assets/SettingsModal-0gevFhXV.css +0 -1
  59. package/dist/client/assets/SettingsModal-Cpur3WpT.js +0 -31
  60. package/dist/client/assets/index-B9Ro_NwC.js +0 -742
  61. package/dist/client/assets/index-DVFBXjGT.css +0 -1
package/dist/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();
@@ -70833,6 +71523,8 @@ var init_gating_classifications = __esm({
70833
71523
  "fn_insight_show",
70834
71524
  "fn_insight_run_list",
70835
71525
  "fn_insight_run_show",
71526
+ "fn_goal_list",
71527
+ "fn_goal_show",
70836
71528
  "fn_mission_list",
70837
71529
  "fn_mission_show",
70838
71530
  "fn_list_agents",
@@ -78873,12 +79565,16 @@ var init_agent_instructions = __esm({
78873
79565
 
78874
79566
  // ../engine/src/prompt-layers.ts
78875
79567
  function buildPromptLayers(input) {
78876
- const { basePrompt, agentInstructions, memorySection, pluginContributions, performanceFeedback } = input;
79568
+ const { basePrompt, agentInstructions, memorySection, goalContext, pluginContributions, performanceFeedback } = input;
78877
79569
  const dynamicParts = [];
78878
79570
  const trimmedMemory = memorySection?.trim() ?? "";
78879
79571
  if (trimmedMemory) {
78880
79572
  dynamicParts.push(trimmedMemory);
78881
79573
  }
79574
+ const trimmedGoalContext = goalContext?.trim() ?? "";
79575
+ if (trimmedGoalContext) {
79576
+ dynamicParts.push(trimmedGoalContext);
79577
+ }
78882
79578
  const trimmedInstructions = agentInstructions?.trim() ?? "";
78883
79579
  if (trimmedInstructions) {
78884
79580
  dynamicParts.push(`## Custom Instructions
@@ -84206,7 +84902,7 @@ function isInsideWorktreesDir(rootDir, worktreePath, settings) {
84206
84902
  return isInsideConfiguredWorktreesDir(rootDir, settings, worktreePath);
84207
84903
  }
84208
84904
  function deriveTaskIdFromBranch(branchName) {
84209
- const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?$/i);
84905
+ const match = branchName.match(/^fusion\/(fn-\d+)(?:-\d+)?(?:-[a-z0-9._-]+)*$/i);
84210
84906
  return match ? match[1].toUpperCase() : branchName.toUpperCase();
84211
84907
  }
84212
84908
  async function scanIdleWorktrees(rootDir, store, settings) {
@@ -91675,6 +92371,7 @@ __export(merger_exports, {
91675
92371
  VERIFICATION_LOG_MAX_CHARS: () => VERIFICATION_LOG_MAX_CHARS,
91676
92372
  VerificationError: () => VerificationError,
91677
92373
  __executePostMergeScriptStepForTests: () => __executePostMergeScriptStepForTests,
92374
+ __testOnlyBuildDeterministicMergeMessage: () => buildDeterministicMergeMessage,
91678
92375
  __test__: () => __test__,
91679
92376
  aiMergeTask: () => aiMergeTask,
91680
92377
  applyAutostashBySha: () => applyAutostashBySha,
@@ -94410,7 +95107,7 @@ async function filterStagedGitignoredPaths(rootDir, taskId) {
94410
95107
  return { unstaged: [], remainingStaged };
94411
95108
  }
94412
95109
  }
94413
- async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary, aiSubject, fixModifiedFiles = /* @__PURE__ */ new Set(), store, auditor) {
95110
+ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary, aiBody, aiSubject, fixModifiedFiles = /* @__PURE__ */ new Set(), store, auditor) {
94414
95111
  try {
94415
95112
  const { stdout: squashStagedOut } = await execAsync26("git diff --cached --name-only", {
94416
95113
  cwd: rootDir,
@@ -94710,7 +95407,7 @@ ${msg}`;
94710
95407
  diffStat: messageDiffStat,
94711
95408
  includeTaskId,
94712
95409
  aiSummary,
94713
- aiBody: void 0,
95410
+ aiBody,
94714
95411
  aiSubject
94715
95412
  });
94716
95413
  let lineageId;
@@ -98142,6 +98839,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
98142
98839
  settings,
98143
98840
  options.signal,
98144
98841
  aiMergeSummary,
98842
+ aiMergeBody,
98145
98843
  aiMergeSubject,
98146
98844
  verificationFixModifiedFiles,
98147
98845
  store,
@@ -98273,6 +98971,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
98273
98971
  settings,
98274
98972
  options.signal,
98275
98973
  aiMergeSummary,
98974
+ aiMergeBody,
98276
98975
  aiMergeSubject,
98277
98976
  buildFixModifiedFiles,
98278
98977
  store,
@@ -99187,6 +99886,7 @@ async function executeMergeAttempt(params, aiTracker) {
99187
99886
  diffStat,
99188
99887
  includeTaskId,
99189
99888
  aiSummary: safeBody,
99889
+ aiBody: aiBody?.trim().length ? aiBody : safeBody,
99190
99890
  aiSubject
99191
99891
  });
99192
99892
  await enforceSquashFileScopeInvariant({
@@ -99381,6 +100081,7 @@ async function executeMergeAttempt(params, aiTracker) {
99381
100081
  diffStat: actualContext.diffStat || diffStat,
99382
100082
  includeTaskId,
99383
100083
  aiSummary,
100084
+ aiBody,
99384
100085
  aiSubject
99385
100086
  });
99386
100087
  const trailerArg = buildTaskTrailerArgs(taskId);
@@ -100409,6 +101110,12 @@ async function classifyOwnedLandedEvidenceForSelfHealing(rootDir, task, mergeTar
100409
101110
  const { classifyOwnedLandedEvidence: classifyOwnedLandedEvidence2 } = await Promise.resolve().then(() => (init_merger(), merger_exports));
100410
101111
  return classifyOwnedLandedEvidence2(rootDir, task, { mergeTargetBranch });
100411
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
+ }
100412
101119
  function formatRecoveryTimestamp(date = /* @__PURE__ */ new Date()) {
100413
101120
  const pad = (value) => String(value).padStart(2, "0");
100414
101121
  return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
@@ -100623,7 +101330,7 @@ function isNoTaskDoneFailure2(task) {
100623
101330
  function hasStepProgress2(task) {
100624
101331
  return task.steps.some((step) => step.status !== "pending");
100625
101332
  }
100626
- 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;
100627
101334
  var init_self_healing = __esm({
100628
101335
  "../engine/src/self-healing.ts"() {
100629
101336
  "use strict";
@@ -100656,6 +101363,7 @@ var init_self_healing = __esm({
100656
101363
  STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS = 10 * 6e4;
100657
101364
  COMPLETION_HANDOFF_LIMBO_GRACE_MS = 5 * 6e4;
100658
101365
  MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES = 3;
101366
+ MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
100659
101367
  APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
100660
101368
  STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
100661
101369
  STARVED_PEER_PROGRESS_THRESHOLD = 3;
@@ -102172,13 +102880,71 @@ var init_self_healing = __esm({
102172
102880
  }
102173
102881
  }
102174
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
+ }
102175
102938
  await this.store.updateTask(task.id, {
102176
102939
  worktree: inspection.livePath,
102177
102940
  branch: task.branch,
102178
102941
  paused: false,
102179
102942
  pausedReason: void 0,
102180
102943
  status: null,
102181
- error: null
102944
+ error: null,
102945
+ resumeLimboCount: resumeAttemptCount,
102946
+ resumeLimboTipSha: inspection.tipSha,
102947
+ resumeLimboStepSignature: stepSignature
102182
102948
  });
102183
102949
  await this.store.logEntry(
102184
102950
  task.id,
@@ -103410,7 +104176,7 @@ var init_self_healing = __esm({
103410
104176
  const blockerId = task.blockedBy;
103411
104177
  const unresolvedDeps = task.dependencies.filter((depId) => {
103412
104178
  const dep = taskById.get(depId);
103413
- 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";
103414
104180
  });
103415
104181
  const overlapBlocker = task.overlapBlockedBy ? taskById.get(task.overlapBlockedBy) : void 0;
103416
104182
  const hasActiveOverlapBlocker = Boolean(
@@ -103436,6 +104202,9 @@ var init_self_healing = __esm({
103436
104202
  reasonCode = "missing-blocker";
103437
104203
  reason = `blocker ${blockerId} missing`;
103438
104204
  }
104205
+ } else if (blocker.deletedAt) {
104206
+ reasonCode = "soft-deleted-blocker";
104207
+ reason = `blocker ${blockerId} soft-deleted at ${blocker.deletedAt}`;
103439
104208
  } else if (blocker.column === "done") {
103440
104209
  reasonCode = "blocker-done";
103441
104210
  reason = `blocker ${blockerId} is done`;
@@ -104965,6 +105734,12 @@ var init_self_healing = __esm({
104965
105734
  try {
104966
105735
  const hardBlocker = getTaskHardMergeBlocker({
104967
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,
104968
105743
  steps: task.steps ?? [],
104969
105744
  workflowStepResults: task.workflowStepResults
104970
105745
  });
@@ -109596,6 +110371,258 @@ var init_token_cap_detector = __esm({
109596
110371
  }
109597
110372
  });
109598
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
+
109599
110626
  // ../engine/src/auto-recovery-handlers/branch-worktree.ts
109600
110627
  import { exec as exec31 } from "node:child_process";
109601
110628
  import { existsSync as existsSync39 } from "node:fs";
@@ -110460,7 +111487,7 @@ function isTransientMissingTaskJsonError(error, task) {
110460
111487
  const normalizedTaskJsonPath = resolvePath(filePath);
110461
111488
  return normalizedTaskJsonPath.startsWith(`${normalizedWorktree}/`);
110462
111489
  }
110463
- function detectPendingReviewBlock(task, codeReviewVerdicts) {
111490
+ function detectPendingReviewBlock(task, _codeReviewVerdicts) {
110464
111491
  const inProgressStepIndices = [];
110465
111492
  for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
110466
111493
  if (task.steps[stepIndex]?.status === "in-progress") {
@@ -110472,9 +111499,6 @@ function detectPendingReviewBlock(task, codeReviewVerdicts) {
110472
111499
  }
110473
111500
  const recentActions = (task.log ?? []).slice(-30).map((entry) => entry.action?.trim()).filter((action) => Boolean(action));
110474
111501
  for (const stepIndex of inProgressStepIndices) {
110475
- if (codeReviewVerdicts.get(stepIndex) === "REVISE") {
110476
- return { blocked: true, reason: "code-review-revise-outstanding", stepIndex };
110477
- }
110478
111502
  const stepDisplay = stepIndex + 1;
110479
111503
  const codeRequest = `code review requested for Step ${stepDisplay}`;
110480
111504
  const planRequest = `plan review requested for Step ${stepDisplay}`;
@@ -111035,6 +112059,8 @@ var init_executor = __esm({
111035
112059
  init_worktree_hooks();
111036
112060
  init_agent_instructions();
111037
112061
  init_prompt_layers();
112062
+ init_goal_injection_diagnostics();
112063
+ init_goal_anchoring_audit();
111038
112064
  init_run_audit();
111039
112065
  init_auto_recovery();
111040
112066
  init_restart_recovery_coordinator();
@@ -111421,6 +112447,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
111421
112447
  executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
111422
112448
  try {
111423
112449
  await this.clearResumeFailureState(task);
112450
+ await this.store.updateTask(task.id, {
112451
+ resumeLimboCount: 0,
112452
+ resumeLimboTipSha: null,
112453
+ resumeLimboStepSignature: null
112454
+ });
111424
112455
  await this.store.logEntry(task.id, "Resuming execution after unpause", void 0, this.getRunContextFor(task.id));
111425
112456
  await this.recoverApprovedStepsOnResume(task.id);
111426
112457
  } catch (clearErr) {
@@ -113796,8 +114827,30 @@ ${summary}`,
113796
114827
  if (executorPluginContributions) {
113797
114828
  executorLog.log(`${task.id}: applied plugin prompt contributions for executor-system surface`);
113798
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
+ });
113799
114851
  const executorLayers = buildPromptLayers({
113800
114852
  basePrompt: getExecutorSystemPrompt(settings),
114853
+ goalContext: executorGoalContext,
113801
114854
  agentInstructions: executorInstructions,
113802
114855
  pluginContributions: executorPluginContributions
113803
114856
  });
@@ -114114,12 +115167,7 @@ ${summary}`,
114114
115167
  this.tokenUsageBaselines.delete(task.id);
114115
115168
  session.dispose();
114116
115169
  await this.persistTokenUsage(task.id);
114117
- await this.store.updateTask(task.id, {
114118
- status: "failed",
114119
- error: "executor-exit-while-review-pending"
114120
- });
114121
115170
  await this.handoffTaskToReview(task, "executor-exit-while-review-pending");
114122
- this.options.onError?.(task, new Error("executor-exit-while-review-pending"));
114123
115171
  pendingReviewParked = true;
114124
115172
  break;
114125
115173
  }
@@ -119272,7 +120320,8 @@ async function collectTaskEvaluationEvidence(params) {
119272
120320
  taskDoneRetryCount: task.taskDoneRetryCount ?? 0,
119273
120321
  verificationFailureCount: task.verificationFailureCount ?? 0,
119274
120322
  mergeConflictBounceCount: task.mergeConflictBounceCount ?? 0,
119275
- mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0
120323
+ mergeAuditBounceCount: task.mergeAuditBounceCount ?? 0,
120324
+ mergeTransientRetryCount: task.mergeTransientRetryCount ?? 0
119276
120325
  }
119277
120326
  }],
119278
120327
  commits: commitEvidence,
@@ -119731,6 +120780,25 @@ function filterPathsByIgnoreList(paths, ignorePaths) {
119731
120780
  }
119732
120781
  return paths.filter((path6) => !normalizedIgnorePaths.some((ignore) => isIgnoredOverlapPath(path6, ignore)));
119733
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
+ }
119734
120802
  function findHigherPriorityQueuedOverlap(candidate, queuedScopes, overlap) {
119735
120803
  let higher = null;
119736
120804
  for (const queued of queuedScopes) {
@@ -120405,34 +121473,47 @@ var init_scheduler = __esm({
120405
121473
  );
120406
121474
  }
120407
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
+ };
120408
121481
  const inversionEmitted = /* @__PURE__ */ new Set();
120409
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
+ };
120410
121494
  if (settings.groupOverlappingFiles) {
120411
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
120412
121495
  for (const t of inProgress) {
120413
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120414
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
120415
- 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");
120416
121505
  }
120417
121506
  for (const t of todo) {
120418
- if (t.status !== "queued" || t.paused || t.userPaused) continue;
120419
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120420
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
121507
+ const filteredScope = await getFilteredFileScope(t.id);
120421
121508
  if (filteredScope.length === 0) continue;
121509
+ if (!isRunnableQueuedOverlapCandidate(t, tasks, now, activeScopes, filteredScope)) continue;
120422
121510
  queuedHigherPriorityScopes.push({
120423
121511
  id: t.id,
120424
121512
  priority: t.priority,
120425
121513
  createdAt: t.createdAt,
120426
121514
  scope: filteredScope
120427
121515
  });
120428
- }
120429
- const inReviewWithWorktree = tasks.filter(
120430
- (t) => t.column === "in-review" && Boolean(t.worktree) && !t.paused && t.status !== "failed"
120431
- );
120432
- for (const t of inReviewWithWorktree) {
120433
- const scope = await this.store.parseFileScopeFromPrompt(t.id);
120434
- const filteredScope = filterPathsByIgnoreList(scope, overlapIgnorePaths);
120435
- if (filteredScope.length > 0) activeScopes.set(t.id, filteredScope);
121516
+ queuedHigherPriorityTaskById.set(t.id, t);
120436
121517
  }
120437
121518
  }
120438
121519
  const ordered = resolveDependencyOrder(todo);
@@ -120453,10 +121534,7 @@ var init_scheduler = __esm({
120453
121534
  continue;
120454
121535
  }
120455
121536
  }
120456
- const unmetDeps = task.dependencies.filter((depId) => {
120457
- const dep = tasks.find((t) => t.id === depId);
120458
- return dep && dep.column !== "done" && dep.column !== "in-review" && dep.column !== "archived";
120459
- });
121537
+ const unmetDeps = getUnmetSchedulingDependencies(task, tasks);
120460
121538
  if (unmetDeps.length > 0) {
120461
121539
  await this.store.updateTask(task.id, {
120462
121540
  status: "queued",
@@ -120490,17 +121568,18 @@ var init_scheduler = __esm({
120490
121568
  continue;
120491
121569
  }
120492
121570
  if (settings.groupOverlappingFiles) {
120493
- const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
120494
- const taskScope = filterPathsByIgnoreList(
120495
- await this.store.parseFileScopeFromPrompt(task.id),
120496
- overlapIgnorePaths
120497
- );
121571
+ const taskScope = await getFilteredFileScope(task.id);
120498
121572
  if (taskScope.length > 0) {
120499
121573
  const activeScopeEntries = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId));
120500
121574
  const overlapBlockerId = task.overlapBlockedBy || task.blockedBy;
120501
121575
  const currentBlockerScope = overlapBlockerId ? activeScopes.get(overlapBlockerId) : void 0;
120502
121576
  const hasValidCurrentBlocker = Boolean(overlapBlockerId) && Boolean(currentBlockerScope) && this.pathsOverlap(taskScope, currentBlockerScope);
120503
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
+ });
120504
121583
  const higherPriorityQueuedOverlap = findHigherPriorityQueuedOverlap(
120505
121584
  {
120506
121585
  id: task.id,
@@ -120508,7 +121587,7 @@ var init_scheduler = __esm({
120508
121587
  createdAt: task.createdAt,
120509
121588
  scope: taskScope
120510
121589
  },
120511
- queuedHigherPriorityScopes,
121590
+ runnableQueuedHigherPriorityScopes,
120512
121591
  this.pathsOverlap.bind(this)
120513
121592
  );
120514
121593
  if (higherPriorityQueuedOverlap) {
@@ -120523,7 +121602,7 @@ var init_scheduler = __esm({
120523
121602
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
120524
121603
  await this.logDispatchQueuedReason(
120525
121604
  task.id,
120526
- `queued \u2014 deferred for higher-priority queued task ${higherPriorityQueuedOverlap.id} (overlap)`
121605
+ `queued \u2014 deferred for higher-priority runnable queued task ${higherPriorityQueuedOverlap.id} (overlap)`
120527
121606
  );
120528
121607
  continue;
120529
121608
  }
@@ -120555,7 +121634,7 @@ var init_scheduler = __esm({
120555
121634
  blockerId: overlapBlockerTask.id,
120556
121635
  blockerPriority: overlapBlockerTask.priority ?? null,
120557
121636
  blockerCreatedAt: overlapBlockerTask.createdAt ?? null,
120558
- blockerColumn: overlapBlockerTask.column,
121637
+ blockerColumn: activeScopeColumns.get(overlappingTaskId) ?? overlapBlockerTask.column,
120559
121638
  source: "scheduler.overlap-priority-inversion"
120560
121639
  }
120561
121640
  });
@@ -120566,7 +121645,11 @@ var init_scheduler = __esm({
120566
121645
  }
120567
121646
  }
120568
121647
  await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
120569
- 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
+ );
120570
121653
  continue;
120571
121654
  }
120572
121655
  if (task.overlapBlockedBy) {
@@ -120785,11 +121868,8 @@ var init_scheduler = __esm({
120785
121868
  this.options.onSchedule?.(task);
120786
121869
  started++;
120787
121870
  if (settings.groupOverlappingFiles) {
120788
- const scope = filterPathsByIgnoreList(
120789
- await this.store.parseFileScopeFromPrompt(task.id),
120790
- settings.overlapIgnorePaths
120791
- );
120792
- 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");
120793
121873
  }
120794
121874
  }
120795
121875
  await this.emitHighOverlapFanoutWarnings(tasks);
@@ -121942,7 +123022,10 @@ var init_mission_execution_loop = __esm({
121942
123022
  try {
121943
123023
  await this.missionStore.transitionLoopState(feature.id, "implementing");
121944
123024
  if (feature.taskId) {
121945
- await this.processTaskOutcome(feature.taskId);
123025
+ const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
123026
+ if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
123027
+ await this.processTaskOutcome(feature.taskId);
123028
+ }
121946
123029
  }
121947
123030
  recoveredCount++;
121948
123031
  } catch (err) {
@@ -121953,7 +123036,10 @@ var init_mission_execution_loop = __esm({
121953
123036
  loopLog.log(`Recovery: feature ${feature.id} awaiting fix implementation`);
121954
123037
  if (feature.taskId) {
121955
123038
  try {
121956
- await this.processTaskOutcome(feature.taskId);
123039
+ const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
123040
+ if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
123041
+ await this.processTaskOutcome(feature.taskId);
123042
+ }
121957
123043
  recoveredCount++;
121958
123044
  } catch (err) {
121959
123045
  loopLog.error(`Recovery failed for needs_fix feature ${feature.id}:`, err);
@@ -121995,10 +123081,17 @@ var init_mission_execution_loop = __esm({
121995
123081
  }
121996
123082
  if (feature.loopState !== "implementing") {
121997
123083
  loopLog.log(`Feature ${feature.id} loopState is "${feature.loopState}"; skipping validation`);
123084
+ this.logFeatureWarningEvent(feature.id, "validation_skipped_loop_state", `Validation skipped: feature ${feature.id} is in loopState "${feature.loopState}" (expected "implementing").`, {
123085
+ taskId,
123086
+ loopState: feature.loopState
123087
+ });
121998
123088
  return;
121999
123089
  }
122000
123090
  if (this.activeValidations.has(feature.id)) {
122001
123091
  loopLog.log(`Feature ${feature.id} already has an active validation; skipping duplicate trigger`);
123092
+ this.logFeatureWarningEvent(feature.id, "validation_deduplicated", `Validation already running for feature ${feature.id}; duplicate trigger ignored.`, {
123093
+ taskId
123094
+ });
122002
123095
  return;
122003
123096
  }
122004
123097
  const assertions = this.missionStore.listAssertionsForFeature(feature.id);
@@ -122466,6 +123559,10 @@ ${taskContext}` : ""}`;
122466
123559
  this.missionStore.completeValidatorRun(runId, "blocked", blockedReason);
122467
123560
  }
122468
123561
  loopLog.log(`Feature ${featureId} blocked: ${blockedReason}`);
123562
+ this.logFeatureErrorEvent(featureId, "validation_blocked", `Validation blocked for feature ${featureId}: ${blockedReason ?? "no reason provided"}`, {
123563
+ runId,
123564
+ blockedReason: blockedReason ?? null
123565
+ });
122469
123566
  if (this.missionAutopilot?.notifyValidationComplete) {
122470
123567
  await this.missionAutopilot.notifyValidationComplete(featureId, "blocked");
122471
123568
  }
@@ -122483,6 +123580,10 @@ ${taskContext}` : ""}`;
122483
123580
  this.missionStore.completeValidatorRun(runId, "error", error);
122484
123581
  }
122485
123582
  loopLog.error(`Feature ${featureId} validation error: ${error}`);
123583
+ this.logFeatureErrorEvent(featureId, "validation_error", `Validation error for feature ${featureId}: ${error}`, {
123584
+ runId,
123585
+ error
123586
+ });
122486
123587
  if (this.missionAutopilot?.notifyValidationComplete) {
122487
123588
  await this.missionAutopilot.notifyValidationComplete(featureId, "error");
122488
123589
  }
@@ -122491,6 +123592,31 @@ ${taskContext}` : ""}`;
122491
123592
  loopLog.error(`Error handling validation error for ${featureId}:`, err);
122492
123593
  }
122493
123594
  }
123595
+ logFeatureWarningEvent(featureId, code, description, metadata) {
123596
+ this.logFeatureMissionEvent(featureId, "warning", code, description, metadata);
123597
+ }
123598
+ logFeatureErrorEvent(featureId, code, description, metadata) {
123599
+ this.logFeatureMissionEvent(featureId, "error", code, description, metadata);
123600
+ }
123601
+ logFeatureMissionEvent(featureId, eventType, code, description, metadata) {
123602
+ const feature = this.missionStore.getFeature(featureId);
123603
+ if (!feature) return;
123604
+ const slice = this.missionStore.getSlice(feature.sliceId);
123605
+ if (!slice) return;
123606
+ const milestone = this.missionStore.getMilestone(slice.milestoneId);
123607
+ if (!milestone) return;
123608
+ try {
123609
+ this.missionStore.logMissionEvent?.(milestone.missionId, eventType, description, {
123610
+ code,
123611
+ featureId,
123612
+ sliceId: slice.id,
123613
+ milestoneId: milestone.id,
123614
+ ...metadata
123615
+ });
123616
+ } catch (err) {
123617
+ loopLog.warn(`Failed to log mission ${eventType} event for feature ${featureId}:`, err);
123618
+ }
123619
+ }
122494
123620
  };
122495
123621
  }
122496
123622
  });
@@ -123773,6 +124899,8 @@ var init_agent_heartbeat = __esm({
123773
124899
  init_agent_instructions();
123774
124900
  init_heartbeat_procedure_resolver();
123775
124901
  init_prompt_layers();
124902
+ init_goal_injection_diagnostics();
124903
+ init_goal_anchoring_audit();
123776
124904
  init_logger2();
123777
124905
  init_worktree_acquisition();
123778
124906
  init_run_audit();
@@ -125164,12 +126292,16 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125164
126292
  engineRunContext.taskId = taskId;
125165
126293
  }
125166
126294
  let autoClaimCandidates = [];
126295
+ let autoClaimSnapshotCandidateCount = 0;
126296
+ let autoClaimRoleFilteredCount = 0;
125167
126297
  const autoClaimEnabled = isAutoClaimRelevantTasksEnabled(agent);
125168
126298
  if (!taskId && canRunNoTaskHeartbeat && autoClaimEnabled && this.snapshotManager) {
125169
126299
  try {
125170
126300
  const snapshot = await this.snapshotManager.getSnapshot();
126301
+ autoClaimSnapshotCandidateCount = snapshot.tasks.length;
125171
126302
  const roleCompatibleCandidates = snapshot.tasks.filter((candidate) => canAgentTakeImplementationTask(agent, candidate));
125172
126303
  const skippedIncompatibleCount = snapshot.tasks.length - roleCompatibleCandidates.length;
126304
+ autoClaimRoleFilteredCount = skippedIncompatibleCount;
125173
126305
  if (skippedIncompatibleCount > 0) {
125174
126306
  heartbeatLog.log(
125175
126307
  `Agent ${agentId} (role=${agent.role}) skipped auto-claim of ${skippedIncompatibleCount} implementation task(s) \u2014 only executor agents may claim implementation work`
@@ -125425,8 +126557,29 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125425
126557
  if (heartbeatPluginContributions) {
125426
126558
  heartbeatLog.log(`applied plugin prompt contributions for heartbeat surface`);
125427
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
+ });
125428
126580
  const heartbeatLayers = buildPromptLayers({
125429
126581
  basePrompt: baseHeartbeatSystemPrompt,
126582
+ goalContext: heartbeatGoalContext,
125430
126583
  agentInstructions: [resolvedInstructionsText, memoryInstructions, selfImprovePrompt].filter((part) => part.trim()).join("\n\n"),
125431
126584
  pluginContributions: heartbeatPluginContributions
125432
126585
  });
@@ -125701,10 +126854,15 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125701
126854
  );
125702
126855
  }
125703
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
+ ] : [];
125704
126862
  const candidateLines = promptCandidateLimit > 0 ? [
125705
126863
  "",
125706
126864
  "Open Task Candidates (auto-claim scan):",
125707
- ...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
125708
126866
  ] : [];
125709
126867
  executionPrompt = [
125710
126868
  `Heartbeat execution for agent "${agent.name}" (ID: ${agent.id})`,
@@ -125720,7 +126878,7 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
125720
126878
  ...wakeTriggerSourceLine ? [wakeTriggerSourceLine] : [],
125721
126879
  `- pending messages: ${pendingMessages.length}`,
125722
126880
  `- pending room messages: ${pendingRoomMessages.total}`,
125723
- `- auto-claim relevant tasks: ${autoClaimEnabled ? promptCandidateLimit === 0 ? "disabled (prompt-suppressed)" : "enabled" : "disabled"}`,
126881
+ `- auto-claim relevant tasks: ${autoClaimStatus}`,
125724
126882
  "",
125725
126883
  "Treat this wake delta as the highest-priority change for this heartbeat.",
125726
126884
  "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
@@ -132086,9 +133244,19 @@ var init_in_process_runtime = __esm({
132086
133244
  missionAutopilot: missionAutopilot ? {
132087
133245
  notifyValidationComplete: async (featureId) => {
132088
133246
  const feature = missionStore.getFeature(featureId);
132089
- if (feature?.taskId) {
132090
- await missionAutopilot.handleTaskCompletion(feature.taskId);
133247
+ if (!feature?.taskId) {
133248
+ return;
133249
+ }
133250
+ const slice = missionStore.getSlice(feature.sliceId);
133251
+ const milestone = slice ? missionStore.getMilestone(slice.milestoneId) : void 0;
133252
+ const missionId = milestone?.missionId;
133253
+ if (missionId) {
133254
+ const mission = missionStore.getMission(missionId);
133255
+ if (mission?.autopilotEnabled && !missionAutopilot.isWatching(missionId)) {
133256
+ missionAutopilot.watchMission(missionId);
133257
+ }
132091
133258
  }
133259
+ await missionAutopilot.handleTaskCompletion(feature.taskId);
132092
133260
  }
132093
133261
  } : void 0,
132094
133262
  rootDir: this.config.workingDirectory,
@@ -132861,7 +134029,8 @@ var init_in_process_runtime = __esm({
132861
134029
  this.usageLimitPauser = pauser;
132862
134030
  }
132863
134031
  /**
132864
- * 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.
132865
134034
  */
132866
134035
  setupEventForwarding() {
132867
134036
  this.taskStore.on("task:created", (task) => {
@@ -132876,6 +134045,10 @@ var init_in_process_runtime = __esm({
132876
134045
  this.recordActivity();
132877
134046
  this.emit("task:updated", task);
132878
134047
  });
134048
+ this.taskStore.on("task:deleted", (task, meta) => {
134049
+ this.recordActivity();
134050
+ this.emit("task:deleted", task, meta);
134051
+ });
132879
134052
  runtimeLog.log("Event forwarding setup complete");
132880
134053
  }
132881
134054
  /**
@@ -132926,7 +134099,7 @@ var init_in_process_runtime = __esm({
132926
134099
  function generateCorrelationId() {
132927
134100
  return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
132928
134101
  }
132929
- 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;
132930
134103
  var init_ipc_protocol = __esm({
132931
134104
  "../engine/src/ipc/ipc-protocol.ts"() {
132932
134105
  "use strict";
@@ -132939,6 +134112,7 @@ var init_ipc_protocol = __esm({
132939
134112
  TASK_CREATED = "TASK_CREATED";
132940
134113
  TASK_MOVED = "TASK_MOVED";
132941
134114
  TASK_UPDATED = "TASK_UPDATED";
134115
+ TASK_DELETED = "TASK_DELETED";
132942
134116
  ERROR_EVENT = "ERROR_EVENT";
132943
134117
  HEALTH_CHANGED = "HEALTH_CHANGED";
132944
134118
  }
@@ -133323,6 +134497,9 @@ var init_child_process_runtime = __esm({
133323
134497
  this.ipcHost.on(TASK_UPDATED, (payload) => {
133324
134498
  this.emit("task:updated", payload.task);
133325
134499
  });
134500
+ this.ipcHost.on(TASK_DELETED, (payload) => {
134501
+ this.emit("task:deleted", payload.task, payload.meta);
134502
+ });
133326
134503
  this.ipcHost.on(ERROR_EVENT, (payload) => {
133327
134504
  const error = new Error(payload.message);
133328
134505
  if (payload.code) {
@@ -134445,6 +135622,9 @@ var init_project_manager = __esm({
134445
135622
  runtime.on("task:updated", (task) => {
134446
135623
  this.emit("task:updated", { projectId, projectName, task });
134447
135624
  });
135625
+ runtime.on("task:deleted", (task, meta) => {
135626
+ this.emit("task:deleted", { projectId, projectName, task, meta });
135627
+ });
134448
135628
  runtime.on("error", (error) => {
134449
135629
  this.emit("error", { projectId, projectName, error });
134450
135630
  this.logActivity(
@@ -134807,6 +135987,10 @@ function buildMergeSystemPrompt2(agentPrompts) {
134807
135987
  " - The subject line must CONCISELY SUMMARIZE the squashed changes in",
134808
135988
  ' imperative mood (e.g. "add X", "fix Y") based on the actual diff \u2014 do',
134809
135989
  " not just restate the task title.",
135990
+ " - The commit BODY must include:",
135991
+ " 1) one short narrative summary line,",
135992
+ " 2) a bullet list of key changes, and",
135993
+ " 3) a `Files changed:` section populated from `git diff --stat`.",
134810
135994
  " - Include the task-id prefix and the trailer lines EXACTLY as given in the",
134811
135995
  " task instructions (they associate the commit with the board task)."
134812
135996
  ].filter((l) => l !== void 0).join("\n");
@@ -134820,11 +136004,15 @@ function buildMergePrompt2(input) {
134820
136004
  "Steps:",
134821
136005
  ` 1. Run: git merge --squash ${input.branch}`,
134822
136006
  " 2. If there are conflicts, resolve them (favor the task's intent), then `git add` the resolved files.",
134823
- " 3. Commit the staged result as a SINGLE commit whose subject summarizes the",
134824
- ` actual changes${input.taskTitle ? ` (task title hint: ${JSON.stringify(input.taskTitle)})` : ""}, including the required trailers:`,
134825
- ` git commit -m ${subjectShape}${trailerArgs}`,
136007
+ " 3. Build a merge body from the staged squash diff:",
136008
+ " - one short narrative summary line",
136009
+ " - bullet list of key changes",
136010
+ " - `Files changed:` + the output of `git diff --stat`",
136011
+ " 4. Commit the staged result as a SINGLE commit whose subject summarizes the",
136012
+ ` actual changes${input.taskTitle ? ` (task title hint: ${JSON.stringify(input.taskTitle)})` : ""}, including the body above and required trailers:`,
136013
+ ` git commit -m ${subjectShape} -m "<narrative + bullet list + Files changed: ...>"${trailerArgs}`,
134826
136014
  " Keep the trailer line(s) verbatim \u2014 they link the commit to the board task.",
134827
- " 4. Verify `git log --oneline ${tip}..HEAD` shows exactly one new commit and `git status` is clean.".replace("${tip}", short(input.tipSha)),
136015
+ " 5. Verify `git log --oneline ${tip}..HEAD` shows exactly one new commit and `git status` is clean.".replace("${tip}", short(input.tipSha)),
134828
136016
  "",
134829
136017
  "If `git merge --squash` reports the branch is already up to date (nothing to",
134830
136018
  "merge), do nothing and leave HEAD unchanged."
@@ -136115,6 +137303,7 @@ var init_project_engine = __esm({
136115
137303
  init_research_step_runner();
136116
137304
  init_run_audit();
136117
137305
  init_verification_followup_dedup();
137306
+ init_transient_error_detector();
136118
137307
  init_tunnel_process_manager();
136119
137308
  execFileAsync12 = promisify45(execFile17);
136120
137309
  MERGE_HANDOFF_GRACE_MS = 300;
@@ -136185,6 +137374,10 @@ var init_project_engine = __esm({
136185
137374
  manualMergeResolvers = /* @__PURE__ */ new Map();
136186
137375
  shuttingDown = false;
136187
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;
136188
137381
  /** Cap on outer in-review→in-progress bounces caused by deterministic
136189
137382
  * verification failures during auto-merge. After this many failed merges
136190
137383
  * for the same task, we stop bouncing it back, mark it failed, and create
@@ -137167,7 +138360,15 @@ ${detail}`
137167
138360
  this.internalEnqueueMerge(taskId);
137168
138361
  continue;
137169
138362
  }
137170
- 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
+ });
137171
138372
  if (blockerReason) {
137172
138373
  await store.updateTask(taskId, {
137173
138374
  status: "failed",
@@ -137686,6 +138887,16 @@ ${detail}`
137686
138887
  }
137687
138888
  } else {
137688
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
+ }
137689
138900
  await store.updateTask(taskId, {
137690
138901
  status: "failed",
137691
138902
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -137704,6 +138915,16 @@ ${detail}`
137704
138915
  }
137705
138916
  } else {
137706
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
+ }
137707
138928
  await store.updateTask(taskId, {
137708
138929
  status: "failed",
137709
138930
  mergeRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES,
@@ -137739,6 +138960,40 @@ ${detail}`
137739
138960
  this.mergeRunning = false;
137740
138961
  }
137741
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
+ }
137742
138997
  wireAutoMerge(store, _cwd) {
137743
138998
  this.taskMovedHandler = async ({ task, to: to2 }) => {
137744
138999
  if (to2 !== "in-review") return;
@@ -141288,6 +142543,7 @@ __export(src_exports2, {
141288
142543
  BranchConflictError: () => BranchConflictError,
141289
142544
  BranchCrossContaminationError: () => BranchCrossContaminationError,
141290
142545
  CronRunner: () => CronRunner,
142546
+ DEFAULT_GOAL_INJECTION_CHAR_BUDGET: () => DEFAULT_GOAL_INJECTION_CHAR_BUDGET,
141291
142547
  DEFAULT_NTFY_EVENTS: () => DEFAULT_NTFY_EVENTS,
141292
142548
  DiffVolumeRegressionError: () => DiffVolumeRegressionError,
141293
142549
  EngineAlreadyRunningError: () => EngineAlreadyRunningError,
@@ -141302,6 +142558,9 @@ __export(src_exports2, {
141302
142558
  ExperimentGitNotConfiguredError: () => ExperimentGitNotConfiguredError,
141303
142559
  ExperimentMaxIterationsError: () => ExperimentMaxIterationsError,
141304
142560
  ExperimentRevertConflictError: () => ExperimentRevertConflictError,
142561
+ GOAL_INJECTION_APPLIED: () => GOAL_INJECTION_APPLIED,
142562
+ GOAL_INJECTION_SKIPPED: () => GOAL_INJECTION_SKIPPED,
142563
+ GOAL_RETRIEVAL_INVOKED: () => GOAL_RETRIEVAL_INVOKED,
141305
142564
  GitHubProvider: () => GitHubProvider,
141306
142565
  HEARTBEAT_NO_TASK_SYSTEM_PROMPT: () => HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
141307
142566
  HEARTBEAT_PROCEDURE: () => HEARTBEAT_PROCEDURE,
@@ -141312,6 +142571,7 @@ __export(src_exports2, {
141312
142571
  KNOWN_EXTERNAL_INTEGRATIONS: () => KNOWN_EXTERNAL_INTEGRATIONS,
141313
142572
  LLMSynthesisProvider: () => LLMSynthesisProvider,
141314
142573
  LocalDocsProvider: () => LocalDocsProvider,
142574
+ MAX_INJECTED_GOALS: () => MAX_INJECTED_GOALS,
141315
142575
  MOCK_PROVIDER_ID: () => MOCK_PROVIDER_ID3,
141316
142576
  MOCK_SYNTHETIC_TOKEN_USAGE: () => MOCK_SYNTHETIC_TOKEN_USAGE,
141317
142577
  MergeAbortedError: () => MergeAbortedError,
@@ -141387,10 +142647,13 @@ __export(src_exports2, {
141387
142647
  assertSafeUrl: () => assertSafeUrl,
141388
142648
  auditSquashMerge: () => auditSquashMerge,
141389
142649
  buildAgentChatPrompt: () => buildAgentChatPrompt,
142650
+ buildGoalContextSection: () => buildGoalContextSection,
141390
142651
  buildNtfyClickUrl: () => buildNtfyClickUrl,
141391
142652
  buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
141392
142653
  buildSystemPromptWithInstructions: () => buildSystemPromptWithInstructions,
141393
142654
  classifyBootstrapMisbinding: () => classifyBootstrapMisbinding,
142655
+ classifyGoalInjectionFailure: () => classifyGoalInjectionFailure,
142656
+ classifyGoalInjectionResult: () => classifyGoalInjectionResult,
141394
142657
  classifyTaskError: () => classifyTaskError,
141395
142658
  cleanupOrphanedWorktrees: () => cleanupOrphanedWorktrees,
141396
142659
  cleanupSecretsEnvFile: () => cleanupSecretsEnvFile,
@@ -141415,6 +142678,9 @@ __export(src_exports2, {
141415
142678
  describeModel: () => describeModel,
141416
142679
  dropAutostashBySha: () => dropAutostashBySha,
141417
142680
  dropAutostashHandle: () => dropAutostashHandle,
142681
+ emitGoalAnchoringAudit: () => emitGoalAnchoringAudit,
142682
+ emitGoalInjectionDiagnostic: () => emitGoalInjectionDiagnostic,
142683
+ emitGoalRetrievalAudit: () => emitGoalRetrievalAudit,
141418
142684
  ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
141419
142685
  executeApprovedAgentProvisioning: () => executeApprovedAgentProvisioning,
141420
142686
  executeApprovedWorktrunkInstall: () => executeApprovedWorktrunkInstall,
@@ -141529,6 +142795,9 @@ var init_src2 = __esm({
141529
142795
  init_external_integrations();
141530
142796
  init_web_fetch();
141531
142797
  init_error_classifier();
142798
+ init_goal_context_injector();
142799
+ init_goal_injection_diagnostics();
142800
+ init_goal_anchoring_audit();
141532
142801
  init_worktrunk_installer();
141533
142802
  init_worktrunk_failure_handler();
141534
142803
  init_usage_limit_detector();
@@ -164155,12 +165424,17 @@ Status: ${createdMission.status}${createdMission.autoAdvance ? "\nAuto-advance:
164155
165424
  )
164156
165425
  }),
164157
165426
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
165427
+ const fnCtx = ctx;
164158
165428
  const store = await getStore2(ctx.cwd);
164159
165429
  const goalStore = store.getGoalStore();
164160
165430
  const status = params.status ?? "active";
164161
165431
  const goals = status === "all" ? goalStore.listGoals() : goalStore.listGoals({ status });
164162
165432
  const activeCount = goalStore.listGoals({ status: "active" }).length;
164163
165433
  const softWarning = activeCount >= 3;
165434
+ emitGoalRetrievalAudit(store, fnCtx, {
165435
+ toolName: "fn_goal_list",
165436
+ resultCount: goals.length
165437
+ });
164164
165438
  const lines = [];
164165
165439
  lines.push(`Goals (${goals.length}) [filter: ${status}]`);
164166
165440
  lines.push(`Active: ${activeCount}/5`);
@@ -164265,6 +165539,56 @@ Status: ${goal.status}${softWarning ? `
164265
165539
  };
164266
165540
  }
164267
165541
  });
165542
+ pi2.registerTool({
165543
+ name: "fn_goal_show",
165544
+ label: "fn: Show Goal",
165545
+ description: "Show full details for a single goal by ID.",
165546
+ promptSnippet: "Show goal details by ID",
165547
+ promptGuidelines: [
165548
+ "Use to inspect a specific goal after listing or referencing its ID",
165549
+ "Cite the goal ID and status when summarizing or planning from this output",
165550
+ "Read-only retrieval; does not mutate goals"
165551
+ ],
165552
+ parameters: Type8.Object({
165553
+ id: Type8.String({ description: "Goal ID (G-\u2026)" })
165554
+ }),
165555
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
165556
+ const fnCtx = ctx;
165557
+ const store = await getStore2(ctx.cwd);
165558
+ const goalStore = store.getGoalStore();
165559
+ const goal = goalStore.getGoal(params.id);
165560
+ if (!goal) {
165561
+ emitGoalRetrievalAudit(store, fnCtx, {
165562
+ toolName: "fn_goal_show",
165563
+ resultCount: 0,
165564
+ goalId: params.id,
165565
+ notFound: true
165566
+ });
165567
+ return {
165568
+ isError: true,
165569
+ content: [{ type: "text", text: `Goal ${params.id} not found` }],
165570
+ details: { code: "GOAL_NOT_FOUND", goalId: params.id }
165571
+ };
165572
+ }
165573
+ const lines = [];
165574
+ lines.push(`${goal.id}: ${goal.title}`);
165575
+ lines.push(`Status: ${goal.status}`);
165576
+ lines.push(`Created: ${goal.createdAt}`);
165577
+ lines.push(`Updated: ${goal.updatedAt}`);
165578
+ if (goal.description) {
165579
+ lines.push(`Description: ${goal.description}`);
165580
+ }
165581
+ emitGoalRetrievalAudit(store, fnCtx, {
165582
+ toolName: "fn_goal_show",
165583
+ resultCount: 1,
165584
+ goalId: params.id
165585
+ });
165586
+ return {
165587
+ content: [{ type: "text", text: lines.join("\n") }],
165588
+ details: { goal }
165589
+ };
165590
+ }
165591
+ });
164268
165592
  pi2.registerTool({
164269
165593
  name: "fn_mission_show",
164270
165594
  label: "fn: Show Mission",