ultra-builder-pro-cli 0.14.0 → 0.16.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 (163) 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 +77 -1
  8. package/README.md +46 -24
  9. package/adapters/_shared/codex-assets.cjs +6 -2
  10. package/adapters/_shared/file-ops.cjs +6 -2
  11. package/adapters/_shared/handbook.cjs +2 -1
  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 +3 -2
  16. package/agents/review-coordinator.md +9 -6
  17. package/commands/ultra-change.md +1 -1
  18. package/commands/ultra-plan.md +1 -1
  19. package/commands/ultra-research.md +1 -1
  20. package/commands/ultra-think.md +2 -2
  21. package/docs/AGENT-CONTEXT.md +43 -19
  22. package/docs/ARCHITECTURE.md +54 -13
  23. package/docs/PLAN.zh-CN.md +4 -2
  24. package/docs/ROADMAP.md +10 -4
  25. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  26. package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
  27. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  28. package/docs/WORKFLOW-LIFECYCLE.md +342 -0
  29. package/hooks/README.md +6 -1
  30. package/hooks/health_check.py +12 -3
  31. package/hooks/subagent_tracker.py +52 -77
  32. package/mcp-server/hook-event.cjs +34 -0
  33. package/mcp-server/lib/archive-journal.cjs +17 -5
  34. package/mcp-server/lib/baseline-workflow.cjs +158 -18
  35. package/mcp-server/lib/change-workflow.cjs +598 -85
  36. package/mcp-server/lib/context-spine.cjs +305 -25
  37. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  38. package/mcp-server/lib/doctor.cjs +42 -3
  39. package/mcp-server/lib/init-project.cjs +210 -16
  40. package/mcp-server/lib/plan-store.cjs +4 -1
  41. package/mcp-server/lib/pricing.cjs +7 -10
  42. package/mcp-server/lib/project-breadcrumb.cjs +15 -0
  43. package/mcp-server/lib/projector.cjs +57 -3
  44. package/mcp-server/lib/spec-learning.cjs +79 -2
  45. package/mcp-server/lib/state-db.cjs +168 -8
  46. package/mcp-server/lib/state-ops.cjs +160 -9
  47. package/mcp-server/lib/workflow-state.cjs +2305 -0
  48. package/mcp-server/server.cjs +215 -15
  49. package/orchestrator/daemon.cjs +1 -2
  50. package/orchestrator/dispatch-rules.cjs +5 -31
  51. package/orchestrator/planner/plan-builder.cjs +21 -12
  52. package/package.json +13 -3
  53. package/skills/ultra-change/SKILL.md +94 -80
  54. package/skills/ultra-change/references/change-contract.md +36 -0
  55. package/skills/ultra-deliver/SKILL.md +76 -79
  56. package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
  57. package/skills/ultra-dev/SKILL.md +62 -59
  58. package/skills/ultra-doctor/SKILL.md +61 -82
  59. package/skills/ultra-init/SKILL.md +101 -103
  60. package/skills/ultra-init/references/brownfield-adoption.md +2 -2
  61. package/skills/ultra-plan/SKILL.md +99 -85
  62. package/skills/ultra-plan/references/semantic-preflight.md +18 -0
  63. package/skills/ultra-research/SKILL.md +102 -60
  64. package/skills/ultra-research/references/00-problem-validation.md +36 -0
  65. package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
  66. package/skills/ultra-research/references/02-market-assessment.md +30 -0
  67. package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
  68. package/skills/ultra-research/references/04-product-strategy.md +32 -0
  69. package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
  70. package/skills/ultra-research/references/10-user-personas.md +29 -0
  71. package/skills/ultra-research/references/11-user-scenarios.md +28 -0
  72. package/skills/ultra-research/references/20-user-stories.md +30 -0
  73. package/skills/ultra-research/references/21-features-scope.md +29 -0
  74. package/skills/ultra-research/references/22-success-metrics.md +28 -0
  75. package/skills/ultra-research/references/30-architecture-context.md +30 -0
  76. package/skills/ultra-research/references/31-solution-strategy.md +28 -0
  77. package/skills/ultra-research/references/32-building-blocks.md +28 -0
  78. package/skills/ultra-research/references/40-deployment.md +29 -0
  79. package/skills/ultra-research/references/41-quality-risks.md +28 -0
  80. package/skills/ultra-research/references/99-synthesis.md +35 -0
  81. package/skills/ultra-review/SKILL.md +63 -73
  82. package/skills/ultra-review/references/review-modes.md +27 -0
  83. package/skills/ultra-review/references/unified-schema.md +25 -0
  84. package/skills/ultra-review/scripts/review_wait.py +40 -3
  85. package/skills/ultra-status/SKILL.md +41 -41
  86. package/skills/ultra-test/SKILL.md +54 -55
  87. package/skills/ultra-think/SKILL.md +26 -12
  88. package/skills/ultra-think/references/decision-dialogue.md +130 -0
  89. package/skills/ultra-verify/SKILL.md +1 -1
  90. package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
  91. package/spec/cli-protocol.md +17 -3
  92. package/spec/mcp-tools.yaml +683 -64
  93. package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
  94. package/spec/schemas/delivery-report.v1.schema.json +111 -0
  95. package/spec/schemas/mcp-tools.schema.json +1 -1
  96. package/spec/schemas/state-db.sql +140 -1
  97. package/spec/schemas/tasks.v4.5.schema.json +8 -1
  98. package/spec/schemas/test-report.v1.schema.json +194 -0
  99. package/ultra-tools/cli.cjs +1 -1
  100. package/ultra-tools/commands/migrate.cjs +3 -3
  101. package/ultra-tools/commands/status.cjs +228 -12
  102. package/.ultra-template/delivery-report.json +0 -9
  103. package/.ultra-template/test-report.json +0 -8
  104. package/adapters/_shared/tests/file-ops.test.cjs +0 -54
  105. package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
  106. package/adapters/_shared/tests/handbook.test.cjs +0 -101
  107. package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
  108. package/adapters/_shared/tests/provenance.test.cjs +0 -133
  109. package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
  110. package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
  111. package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
  112. package/adapters/_shared/tests/validate.test.cjs +0 -30
  113. package/adapters/tests/claude.test.cjs +0 -138
  114. package/adapters/tests/codex-hook.test.cjs +0 -142
  115. package/adapters/tests/codex.test.cjs +0 -430
  116. package/adapters/tests/kimi-hook.test.cjs +0 -149
  117. package/adapters/tests/kimi.test.cjs +0 -259
  118. package/adapters/tests/opencode.test.cjs +0 -357
  119. package/mcp-server/lib/archive-journal.test.cjs +0 -35
  120. package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
  121. package/mcp-server/lib/change-workflow.test.cjs +0 -971
  122. package/mcp-server/lib/context-spine.test.cjs +0 -93
  123. package/mcp-server/lib/doctor.test.cjs +0 -148
  124. package/mcp-server/lib/plan-store.test.cjs +0 -132
  125. package/mcp-server/lib/prd-parser.test.cjs +0 -82
  126. package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
  127. package/mcp-server/lib/runtime-state.test.cjs +0 -92
  128. package/mcp-server/lib/spec-learning.test.cjs +0 -67
  129. package/mcp-server/lib/state-db.test.cjs +0 -314
  130. package/mcp-server/lib/task-expander.test.cjs +0 -169
  131. package/mcp-server/lib/topo.test.cjs +0 -158
  132. package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
  133. package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
  134. package/mcp-server/tests/concurrency-update.test.cjs +0 -96
  135. package/mcp-server/tests/concurrency.test.cjs +0 -112
  136. package/mcp-server/tests/db-ops.test.cjs +0 -121
  137. package/mcp-server/tests/init-project.test.cjs +0 -393
  138. package/mcp-server/tests/migrate.test.cjs +0 -232
  139. package/mcp-server/tests/projector.test.cjs +0 -162
  140. package/mcp-server/tests/server-contract.test.cjs +0 -1467
  141. package/mcp-server/tests/staleness.test.cjs +0 -190
  142. package/mcp-server/tests/state-db.test.cjs +0 -106
  143. package/mcp-server/tests/state-ops.test.cjs +0 -449
  144. package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
  145. package/mcp-server/tests/telemetry.test.cjs +0 -219
  146. package/orchestrator/planner/plan-builder.test.cjs +0 -165
  147. package/orchestrator/tests/auto-merge.test.cjs +0 -216
  148. package/orchestrator/tests/daemon.test.cjs +0 -311
  149. package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
  150. package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
  151. package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
  152. package/orchestrator/tests/recovery.test.cjs +0 -190
  153. package/orchestrator/tests/session-runner.test.cjs +0 -314
  154. package/orchestrator/tests/skill-miner.test.cjs +0 -147
  155. package/orchestrator/tests/worktree-manager.test.cjs +0 -148
  156. package/skills/ultra-research/references/architecture.md +0 -38
  157. package/skills/ultra-research/references/problem-product.md +0 -39
  158. package/skills/ultra-research/references/quality-delivery.md +0 -34
  159. package/skills/ultra-research/references/synthesis.md +0 -38
  160. package/skills/ultra-research/references/users-scope.md +0 -40
  161. package/ultra-tools/legacy-memory.test.cjs +0 -80
  162. package/ultra-tools/status-command.test.cjs +0 -181
  163. 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
  });