ultra-builder-pro-cli 0.13.0 → 0.15.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 (172) hide show
  1. package/.ultra-template/docs/research/README.md +11 -7
  2. package/.ultra-template/specs/architecture.md +36 -36
  3. package/.ultra-template/specs/discovery.md +46 -33
  4. package/.ultra-template/specs/product.md +38 -29
  5. package/.ultra-template/specs/research-distillate.md +48 -0
  6. package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
  7. package/CHANGELOG.md +86 -1
  8. package/README.md +40 -26
  9. package/adapters/_shared/codex-assets.cjs +29 -165
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/runtime-assets.cjs +1 -0
  12. package/adapters/claude.js +3 -2
  13. package/adapters/codex.js +1 -0
  14. package/adapters/kimi.js +3 -2
  15. package/adapters/opencode.js +33 -122
  16. package/agents/review-coordinator.md +9 -6
  17. package/docs/AGENT-CONTEXT.md +21 -11
  18. package/docs/ARCHITECTURE.md +41 -13
  19. package/docs/PLAN.zh-CN.md +3 -2
  20. package/docs/ROADMAP.md +9 -5
  21. package/docs/RUNTIME-COMPAT-MATRIX.md +6 -6
  22. package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
  23. package/docs/WORKFLOW-LIFECYCLE.md +266 -0
  24. package/hooks/README.md +6 -1
  25. package/hooks/active_task_context.py +22 -50
  26. package/hooks/context_spine.py +61 -332
  27. package/hooks/health_check.py +29 -18
  28. package/hooks/pre_stop_check.py +28 -33
  29. package/hooks/subagent_tracker.py +54 -85
  30. package/hooks/workflow_checkpoint.py +39 -47
  31. package/hooks/workflow_context.py +4 -9
  32. package/hooks/workflow_resume.py +48 -128
  33. package/mcp-server/breadcrumb.cjs +22 -0
  34. package/mcp-server/hook-event.cjs +34 -0
  35. package/mcp-server/lib/archive-journal.cjs +17 -5
  36. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  37. package/mcp-server/lib/change-workflow.cjs +550 -85
  38. package/mcp-server/lib/context-spine.cjs +242 -24
  39. package/mcp-server/lib/doctor.cjs +33 -3
  40. package/mcp-server/lib/init-project.cjs +210 -16
  41. package/mcp-server/lib/plan-store.cjs +4 -1
  42. package/mcp-server/lib/prd-parser.cjs +14 -41
  43. package/mcp-server/lib/pricing.cjs +7 -10
  44. package/mcp-server/lib/project-breadcrumb.cjs +145 -0
  45. package/mcp-server/lib/projector.cjs +57 -3
  46. package/mcp-server/lib/spec-learning.cjs +79 -2
  47. package/mcp-server/lib/state-db.cjs +145 -8
  48. package/mcp-server/lib/state-ops.cjs +160 -9
  49. package/mcp-server/lib/task-expander.cjs +9 -38
  50. package/mcp-server/lib/workflow-state.cjs +2292 -0
  51. package/mcp-server/server.cjs +182 -50
  52. package/orchestrator/daemon.cjs +1 -2
  53. package/orchestrator/dispatch-rules.cjs +5 -31
  54. package/orchestrator/planner/plan-builder.cjs +21 -12
  55. package/package.json +14 -6
  56. package/skills/cc-collab/SKILL.md +2 -2
  57. package/skills/codex-collab/SKILL.md +2 -3
  58. package/skills/ultra-change/SKILL.md +53 -68
  59. package/skills/ultra-change/references/change-contract.md +36 -0
  60. package/skills/ultra-deliver/SKILL.md +69 -79
  61. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  62. package/skills/ultra-dev/SKILL.md +54 -59
  63. package/skills/ultra-doctor/SKILL.md +40 -71
  64. package/skills/ultra-init/SKILL.md +96 -103
  65. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  66. package/skills/ultra-plan/SKILL.md +79 -75
  67. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  68. package/skills/ultra-research/SKILL.md +87 -60
  69. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  70. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  71. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  72. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  73. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  74. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  75. package/skills/ultra-research/references/10-user-personas.md +29 -0
  76. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  77. package/skills/ultra-research/references/20-user-stories.md +30 -0
  78. package/skills/ultra-research/references/21-features-scope.md +29 -0
  79. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  80. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  81. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  82. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  83. package/skills/ultra-research/references/40-deployment.md +29 -0
  84. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  85. package/skills/ultra-research/references/99-synthesis.md +35 -0
  86. package/skills/ultra-review/SKILL.md +57 -73
  87. package/skills/ultra-review/references/review-modes.md +27 -0
  88. package/skills/ultra-review/references/unified-schema.md +25 -0
  89. package/skills/ultra-review/scripts/review_wait.py +40 -3
  90. package/skills/ultra-status/SKILL.md +41 -45
  91. package/skills/ultra-test/SKILL.md +48 -55
  92. package/skills/ultra-verify/SKILL.md +22 -31
  93. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  94. package/skills/ultra-verify/scripts/verify_wait.py +54 -54
  95. package/spec/cli-protocol.md +7 -2
  96. package/spec/mcp-tools.yaml +422 -73
  97. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  98. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  99. package/spec/schemas/mcp-tools.schema.json +1 -1
  100. package/spec/schemas/state-db.sql +82 -1
  101. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  102. package/spec/schemas/test-report.v1.schema.json +194 -0
  103. package/ultra-tools/cli.cjs +1 -1
  104. package/ultra-tools/commands/migrate.cjs +3 -3
  105. package/ultra-tools/commands/status.cjs +213 -12
  106. package/.ultra-template/delivery-report.json +0 -9
  107. package/.ultra-template/test-report.json +0 -8
  108. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  109. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  110. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  111. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  112. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  113. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  114. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -131
  115. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  116. package/adapters/_shared/tests/validate.test.cjs +0 -30
  117. package/adapters/tests/claude.test.cjs +0 -137
  118. package/adapters/tests/codex-hook.test.cjs +0 -115
  119. package/adapters/tests/codex.test.cjs +0 -423
  120. package/adapters/tests/kimi-hook.test.cjs +0 -122
  121. package/adapters/tests/kimi.test.cjs +0 -259
  122. package/adapters/tests/opencode.test.cjs +0 -371
  123. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  124. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  125. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  126. package/mcp-server/lib/context-spine.test.cjs +0 -93
  127. package/mcp-server/lib/doctor.test.cjs +0 -148
  128. package/mcp-server/lib/llm-client.cjs +0 -157
  129. package/mcp-server/lib/llm-client.test.cjs +0 -156
  130. package/mcp-server/lib/plan-store.test.cjs +0 -132
  131. package/mcp-server/lib/prd-parser.test.cjs +0 -171
  132. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  133. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  134. package/mcp-server/lib/state-db.test.cjs +0 -314
  135. package/mcp-server/lib/task-expander.test.cjs +0 -324
  136. package/mcp-server/lib/topo.test.cjs +0 -158
  137. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  138. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  139. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  140. package/mcp-server/tests/concurrency.test.cjs +0 -112
  141. package/mcp-server/tests/db-ops.test.cjs +0 -121
  142. package/mcp-server/tests/init-project.test.cjs +0 -393
  143. package/mcp-server/tests/migrate.test.cjs +0 -232
  144. package/mcp-server/tests/projector.test.cjs +0 -162
  145. package/mcp-server/tests/server-contract.test.cjs +0 -1471
  146. package/mcp-server/tests/staleness.test.cjs +0 -190
  147. package/mcp-server/tests/state-db.test.cjs +0 -106
  148. package/mcp-server/tests/state-ops.test.cjs +0 -449
  149. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  150. package/mcp-server/tests/telemetry.test.cjs +0 -219
  151. package/orchestrator/planner/expand-prompt.cjs +0 -62
  152. package/orchestrator/planner/expand-prompt.test.cjs +0 -52
  153. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  154. package/orchestrator/planner/prd-prompt.cjs +0 -44
  155. package/orchestrator/planner/prd-prompt.test.cjs +0 -30
  156. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  157. package/orchestrator/tests/daemon.test.cjs +0 -311
  158. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  159. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  160. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  161. package/orchestrator/tests/recovery.test.cjs +0 -190
  162. package/orchestrator/tests/session-runner.test.cjs +0 -314
  163. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  164. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  165. package/skills/ultra-research/references/architecture.md +0 -38
  166. package/skills/ultra-research/references/problem-product.md +0 -39
  167. package/skills/ultra-research/references/quality-delivery.md +0 -34
  168. package/skills/ultra-research/references/synthesis.md +0 -38
  169. package/skills/ultra-research/references/users-scope.md +0 -40
  170. package/ultra-tools/legacy-memory.test.cjs +0 -80
  171. package/ultra-tools/status-command.test.cjs +0 -181
  172. package/ultra-tools/system-command.test.cjs +0 -309
@@ -7,6 +7,7 @@ const { spawnSync } = require('node:child_process');
7
7
 
8
8
  const ops = require('./state-ops.cjs');
9
9
  const providerRefs = require('./provider-refs.cjs');
10
+ const workflows = require('./workflow-state.cjs');
10
11
 
11
12
  const BASELINE_ID = /^[a-zA-Z0-9_-]+$/;
12
13
  const MODES = new Set(['greenfield', 'brownfield']);
@@ -124,6 +125,7 @@ function rowToBaseline(row) {
124
125
  classification: parseJson(row.classification_json, 'classification_json', {}),
125
126
  worktree_files: parseJson(row.worktree_files_json, 'worktree_files_json', []),
126
127
  worktree_accepted: Boolean(row.worktree_accepted),
128
+ known_red_accepted: Boolean(row.known_red_accepted),
127
129
  provider_refs: parseJson(row.provider_refs_json, 'provider_refs_json', {}),
128
130
  };
129
131
  for (const key of [
@@ -496,7 +498,8 @@ function recordBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent = t
496
498
  db.prepare(
497
499
  `UPDATE baselines SET status = ?, scope_json = ?, repository_revision = ?,
498
500
  repository_branch = ?, worktree_state = ?, worktree_digest = ?, worktree_files_json = ?,
499
- worktree_accepted = 0, spec_refs_json = ?, evidence_json = ?, verification_json = ?,
501
+ worktree_accepted = 0, known_red_accepted = 0,
502
+ spec_refs_json = ?, evidence_json = ?, verification_json = ?,
500
503
  unknowns_json = ?, gaps_json = ?, classification_json = ?, provider_refs_json = ?,
501
504
  approved_by = NULL, approval_note = NULL, converged_at = NULL, updated_at = ? WHERE id = ?`,
502
505
  ).run(
@@ -551,7 +554,114 @@ function storedEvidenceBlockers(baseline, rootDir) {
551
554
  return blockers;
552
555
  }
553
556
 
554
- function convergenceBlockers(baseline, input, rootDir) {
557
+ function storedReadyInvariantBlockers(baseline) {
558
+ const blockers = [];
559
+ const arrays = ['scope', 'spec_refs', 'evidence', 'verification', 'unknowns', 'gaps'];
560
+ for (const field of arrays) {
561
+ if (!Array.isArray(baseline[field])) blockers.push(`BASELINE_STATE_INVALID:${field}`);
562
+ }
563
+ if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) {
564
+ blockers.push('BASELINE_SCOPE_MISSING');
565
+ }
566
+ if (Array.isArray(baseline.spec_refs)) {
567
+ for (const kind of ['discovery', 'product', 'architecture']) {
568
+ if (!baseline.spec_refs.some((item) => item?.kind === kind)) {
569
+ blockers.push(`BASELINE_SPEC_MISSING:${kind}`);
570
+ }
571
+ }
572
+ }
573
+ if (!Array.isArray(baseline.evidence) || baseline.evidence.length === 0) {
574
+ blockers.push('BASELINE_EVIDENCE_MISSING');
575
+ } else if (baseline.mode === 'brownfield'
576
+ && !baseline.evidence.some((item) => item?.kind === 'source')) {
577
+ blockers.push('BASELINE_SOURCE_EVIDENCE_MISSING');
578
+ }
579
+ if (!Array.isArray(baseline.verification) || baseline.verification.length === 0) {
580
+ blockers.push('BASELINE_VERIFICATION_MISSING');
581
+ } else {
582
+ for (const item of baseline.verification) {
583
+ if (!item || typeof item !== 'object') {
584
+ blockers.push('BASELINE_STATE_INVALID:verification');
585
+ } else if (item.status === 'not_run') {
586
+ blockers.push(`BASELINE_VERIFICATION_NOT_RUN:${item.name || 'unknown'}`);
587
+ } else if (item.status === 'known_red') {
588
+ if (!String(item.rationale || '').trim()) {
589
+ blockers.push(`BASELINE_KNOWN_RED_RATIONALE_MISSING:${item.name || 'unknown'}`);
590
+ }
591
+ if (!baseline.known_red_accepted) {
592
+ blockers.push(`BASELINE_KNOWN_RED_NOT_ACCEPTED:${item.name || 'unknown'}`);
593
+ }
594
+ } else if (item.status !== 'pass') {
595
+ blockers.push(`BASELINE_VERIFICATION_STATUS_INVALID:${item.name || 'unknown'}`);
596
+ }
597
+ }
598
+ }
599
+ if (!String(baseline.repository_revision || '').trim()) {
600
+ blockers.push('BASELINE_REVISION_MISSING');
601
+ }
602
+ if (!String(baseline.approved_by || '').trim()) blockers.push('BASELINE_APPROVER_MISSING');
603
+ if (String(baseline.approval_note || '').trim().length < 3) {
604
+ blockers.push('BASELINE_APPROVAL_NOTE_MISSING');
605
+ }
606
+ if (!baseline.converged_at || Number.isNaN(Date.parse(baseline.converged_at))) {
607
+ blockers.push('BASELINE_CONVERGENCE_TIMESTAMP_MISSING');
608
+ }
609
+ if (Array.isArray(baseline.unknowns)) {
610
+ for (const item of baseline.unknowns.filter((unknown) => unknown?.blocking === true)) {
611
+ blockers.push(`BASELINE_UNKNOWN_BLOCKING:${item.summary || 'unknown'}`);
612
+ }
613
+ }
614
+ if (Array.isArray(baseline.gaps)) {
615
+ for (const gap of baseline.gaps.filter((item) => (
616
+ item?.status === 'open' && item?.blocking === true
617
+ ))) {
618
+ blockers.push(`BASELINE_GAP_BLOCKING:${gap.id || 'unknown'}`);
619
+ }
620
+ }
621
+ return [...new Set(blockers)];
622
+ }
623
+
624
+ function completedBaselineResearch(db, baseline, rootDir) {
625
+ const acceptedMode = baseline.mode === 'greenfield' ? 'full' : 'adoption';
626
+ const row = db.prepare(
627
+ `SELECT id FROM workflow_runs
628
+ WHERE kind = 'research' AND baseline_id = ? AND mode = ? AND status = 'completed'
629
+ ORDER BY completed_at DESC, rowid DESC LIMIT 1`,
630
+ ).get(baseline.id, acceptedMode);
631
+ return row ? workflows.readWorkflow(db, row.id, { rootDir }) : null;
632
+ }
633
+
634
+ function convergenceResearchBlockers(db, baseline, rootDir) {
635
+ const run = completedBaselineResearch(db, baseline, rootDir);
636
+ if (!run) return ['BASELINE_RESEARCH_INCOMPLETE'];
637
+ return run.artifact_health.blockers.map((item) => `BASELINE_RESEARCH_${item}`);
638
+ }
639
+
640
+ function storedResearchHealth(db, baseline, rootDir) {
641
+ if (!baseline.research_run_id) {
642
+ return { blockers: ['BASELINE_RESEARCH_PROVENANCE_MISSING'], warnings: [] };
643
+ }
644
+ const run = workflows.readWorkflow(db, baseline.research_run_id, { rootDir });
645
+ if (!run || run.kind !== 'research' || run.status !== 'completed') {
646
+ return { blockers: ['BASELINE_RESEARCH_RECORD_INVALID'], warnings: [] };
647
+ }
648
+ const livingSpecifications = new Set(
649
+ (Array.isArray(baseline.spec_refs) ? baseline.spec_refs : [])
650
+ .map((item) => item?.path).filter(Boolean),
651
+ );
652
+ const blockers = run.artifact_health.blockers.filter((item) => {
653
+ const separator = item.indexOf(':');
654
+ if (separator < 0) return true;
655
+ const artifactPath = item.slice(separator + 1);
656
+ return !livingSpecifications.has(artifactPath);
657
+ });
658
+ return {
659
+ blockers: blockers.map((item) => `BASELINE_RESEARCH_${item}`),
660
+ warnings: [],
661
+ };
662
+ }
663
+
664
+ function convergenceBlockers(db, baseline, input, rootDir) {
555
665
  const blockers = new Set();
556
666
  const expected = typeof input.expected_revision === 'string' ? input.expected_revision.trim() : '';
557
667
  if (!expected) blockers.add('BASELINE_REVISION_REQUIRED');
@@ -561,9 +671,10 @@ function convergenceBlockers(baseline, input, rootDir) {
561
671
  if (baseline.repository_branch && snapshot.branch
562
672
  && baseline.repository_branch !== snapshot.branch) blockers.add('BASELINE_BRANCH_STALE');
563
673
  if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) blockers.add('BASELINE_SCOPE_MISSING');
564
- for (const kind of ['product', 'architecture']) {
674
+ for (const kind of ['discovery', 'product', 'architecture']) {
565
675
  if (!baseline.spec_refs.some((ref) => ref.kind === kind)) blockers.add(`BASELINE_SPEC_MISSING:${kind}`);
566
676
  }
677
+ for (const blocker of convergenceResearchBlockers(db, baseline, rootDir)) blockers.add(blocker);
567
678
  for (const blocker of storedSpecBlockers(baseline, rootDir)) blockers.add(blocker);
568
679
  for (const blocker of storedEvidenceBlockers(baseline, rootDir)) blockers.add(blocker);
569
680
  for (const blocker of storedScopeBlockers(baseline, rootDir)) blockers.add(blocker);
@@ -603,7 +714,7 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
603
714
  if (!['draft', 'adopting', 'blocked'].includes(current.status)) {
604
715
  throw new BaselineWorkflowError('BASELINE_NOT_CONVERGEABLE', `baseline ${current.id} is ${current.status}`);
605
716
  }
606
- const blockers = convergenceBlockers(current, input, rootDir);
717
+ const blockers = convergenceBlockers(db, current, input, rootDir);
607
718
  const ts = nowIso();
608
719
  if (blockers.length > 0) {
609
720
  ops.tx(db, () => {
@@ -614,22 +725,32 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
614
725
  type: 'baseline_blocked', payload: { baseline_id: current.id, blockers },
615
726
  });
616
727
  }
728
+ workflows.blockInitializationInTx(db, current.id, blockers);
617
729
  });
618
730
  return { ready: false, status: 'blocked', blockers, baseline: readBaseline(db, current.id) };
619
731
  }
732
+ const research = completedBaselineResearch(db, current, rootDir);
620
733
  ops.tx(db, () => {
621
734
  db.prepare(
622
735
  `UPDATE baselines SET status = 'ready', approved_by = ?, approval_note = ?,
623
- worktree_accepted = ?, converged_at = ?, updated_at = ? WHERE id = ?`,
736
+ worktree_accepted = ?, known_red_accepted = ?, research_run_id = ?,
737
+ converged_at = ?, updated_at = ? WHERE id = ?`,
624
738
  ).run(
625
739
  String(input.approved_by).trim(), String(input.approval_note).trim(),
626
- input.accept_dirty_worktree === true ? 1 : 0, ts, ts, current.id,
740
+ input.accept_dirty_worktree === true ? 1 : 0,
741
+ input.accept_known_red === true ? 1 : 0, research.id, ts, ts, current.id,
627
742
  );
628
743
  if (emitEvent) {
629
744
  ops.appendEventInTx(db, {
630
745
  type: 'baseline_converged', payload: { baseline_id: current.id, revision: current.repository_revision },
631
746
  });
632
747
  }
748
+ workflows.completeInitializationInTx(db, {
749
+ ...current,
750
+ approved_by: String(input.approved_by).trim(),
751
+ research_run_id: research.id,
752
+ converged_at: ts,
753
+ });
633
754
  });
634
755
  return { ready: true, status: 'ready', blockers: [], baseline: readBaseline(db, current.id) };
635
756
  }
@@ -654,12 +775,17 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
654
775
  status: 'fail', blockers: [`BASELINE_NOT_READY:${baseline.status}`], warnings: [], baseline,
655
776
  };
656
777
  }
778
+ const research = storedResearchHealth(db, baseline, rootDir);
657
779
  const blockers = [
658
- ...storedScopeBlockers(baseline, rootDir),
659
- ...storedSpecBlockers(baseline, rootDir),
660
- ...storedEvidenceBlockers(baseline, rootDir),
780
+ ...storedReadyInvariantBlockers(baseline),
781
+ ...(Array.isArray(baseline.scope) ? storedScopeBlockers(baseline, rootDir) : []),
782
+ ...(Array.isArray(baseline.spec_refs) ? storedSpecBlockers(baseline, rootDir) : []),
783
+ ...(Array.isArray(baseline.evidence) ? storedEvidenceBlockers(baseline, rootDir) : []),
784
+ ...research.blockers,
661
785
  ];
662
- const snapshot = gitWorktreeSnapshot(rootDir, baseline.scope);
786
+ const snapshot = gitWorktreeSnapshot(
787
+ rootDir, Array.isArray(baseline.scope) && baseline.scope.length > 0 ? baseline.scope : ['.'],
788
+ );
663
789
  if (snapshot.head && baseline.repository_revision && snapshot.head !== baseline.repository_revision) {
664
790
  blockers.push('BASELINE_HEAD_STALE');
665
791
  } else if (!snapshot.head && baseline.repository_revision?.startsWith('workspace:')) {
@@ -677,10 +803,10 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
677
803
  if (baseline.worktree_state === 'dirty' && !baseline.worktree_accepted) {
678
804
  blockers.push('BASELINE_DIRTY_WORKTREE_NOT_ACCEPTED');
679
805
  }
680
- for (const gap of baseline.gaps.filter((item) => item.status === 'open' && item.blocking)) {
681
- blockers.push(`BASELINE_GAP_BLOCKING:${gap.id}`);
682
- }
683
- return { status: blockers.length === 0 ? 'pass' : 'fail', blockers, warnings: [], baseline };
806
+ return {
807
+ status: blockers.length === 0 ? 'pass' : 'fail', blockers: [...new Set(blockers)].sort(),
808
+ warnings: research.warnings, baseline,
809
+ };
684
810
  }
685
811
 
686
812
  function inferSpecKind(file) {
@@ -688,7 +814,9 @@ function inferSpecKind(file) {
688
814
  return SPEC_KINDS.has(name) ? name : 'other';
689
815
  }
690
816
 
691
- function reconcileBaseline(db, { baseline_updates = [], change_id = null } = {}, {
817
+ function reconcileBaseline(db, {
818
+ baseline_updates = [], change_id = null, reconciliation = null,
819
+ } = {}, {
692
820
  rootDir = process.cwd(), emitEvent = true,
693
821
  } = {}) {
694
822
  const current = readBaseline(db);
@@ -717,6 +845,12 @@ function reconcileBaseline(db, { baseline_updates = [], change_id = null } = {},
717
845
  }
718
846
  }
719
847
  const specs = [...refs.values()];
848
+ const resolvedGapIds = new Set(reconciliation?.resolved_gap_ids || []);
849
+ const resolvedUnknowns = new Set(reconciliation?.resolved_unknowns || []);
850
+ const gaps = current.gaps.map((gap) => (
851
+ resolvedGapIds.has(gap.id) ? { ...gap, status: 'resolved' } : gap
852
+ ));
853
+ const unknowns = current.unknowns.filter((unknown) => !resolvedUnknowns.has(unknown.summary));
720
854
  const snapshot = gitWorktreeSnapshot(rootDir, current.scope);
721
855
  const revision = snapshot.head || workspaceRevision({
722
856
  scope: current.scope, specs, evidence,
@@ -726,16 +860,22 @@ function reconcileBaseline(db, { baseline_updates = [], change_id = null } = {},
726
860
  db.prepare(
727
861
  `UPDATE baselines SET repository_revision = ?, repository_branch = ?, worktree_state = ?,
728
862
  worktree_digest = ?, worktree_files_json = ?, worktree_accepted = 0,
729
- spec_refs_json = ?, evidence_json = ?, updated_at = ? WHERE id = ?`,
863
+ spec_refs_json = ?, evidence_json = ?, gaps_json = ?, unknowns_json = ?,
864
+ updated_at = ? WHERE id = ?`,
730
865
  ).run(
731
866
  revision, snapshot.branch, snapshot.state, snapshot.digest, JSON.stringify(snapshot.files),
732
- JSON.stringify(specs), JSON.stringify(evidence), ts, current.id,
867
+ JSON.stringify(specs), JSON.stringify(evidence), JSON.stringify(gaps),
868
+ JSON.stringify(unknowns), ts, current.id,
733
869
  );
734
870
  if (emitEvent) {
735
871
  ops.appendEventInTx(db, {
736
872
  type: 'baseline_reconciled',
737
873
  change_id,
738
- payload: { baseline_id: current.id, baseline_updates, repository_revision: revision },
874
+ payload: {
875
+ baseline_id: current.id, baseline_updates, repository_revision: revision,
876
+ semantic_change_ids: (reconciliation?.semantic_changes || []).map((item) => item.id),
877
+ resolved_gap_ids: [...resolvedGapIds], resolved_unknowns: [...resolvedUnknowns],
878
+ },
739
879
  });
740
880
  }
741
881
  });