ultra-builder-pro-cli 0.23.0 → 0.24.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 (91) hide show
  1. package/.ultra-template/docs/research/README.md +4 -4
  2. package/CHANGELOG.md +45 -0
  3. package/README.md +21 -18
  4. package/adapters/_shared/codex-assets.cjs +121 -65
  5. package/adapters/_shared/interaction-contract.cjs +7 -1
  6. package/adapters/_shared/provenance.cjs +7 -2
  7. package/adapters/_shared/runtime-assets.cjs +3 -2
  8. package/adapters/_shared/runtime-native.cjs +132 -0
  9. package/adapters/claude.js +60 -30
  10. package/adapters/codex.js +178 -81
  11. package/adapters/grok.js +372 -0
  12. package/adapters/kimi.js +7 -3
  13. package/adapters/opencode.js +210 -32
  14. package/agents/code-reviewer.md +4 -2
  15. package/agents/debugger.md +4 -1
  16. package/agents/review-code.md +7 -3
  17. package/agents/review-comments.md +7 -4
  18. package/agents/review-coordinator.md +11 -7
  19. package/agents/review-design.md +8 -4
  20. package/agents/review-errors.md +8 -4
  21. package/agents/review-spec.md +8 -5
  22. package/agents/review-tests.md +7 -4
  23. package/agents/tdd-runner.md +5 -2
  24. package/bin/install.js +9 -4
  25. package/bin/orchestrator.js +4 -13
  26. package/docs/AGENT-CONTEXT.md +141 -229
  27. package/docs/ARCHITECTURE.md +203 -494
  28. package/docs/ARTIFACT-AUTHORITY.md +26 -22
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
  30. package/docs/ROADMAP.md +85 -110
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
  32. package/docs/SKILL-AUTHORING.md +3 -3
  33. package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
  34. package/docs/WORKFLOW-LIFECYCLE.md +174 -448
  35. package/hooks/README.md +11 -8
  36. package/hooks/active_task_context.py +16 -16
  37. package/hooks/adapters/grok.py +134 -0
  38. package/hooks/{context_spine.py → context_envelope.py} +26 -22
  39. package/hooks/health_check.py +19 -29
  40. package/hooks/pre_stop_check.py +22 -12
  41. package/hooks/subagent_tracker.py +1 -1
  42. package/hooks/workflow_checkpoint.py +14 -13
  43. package/hooks/workflow_context.py +11 -12
  44. package/hooks/workflow_resume.py +14 -13
  45. package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
  46. package/mcp-server/lib/baseline-workflow.cjs +36 -38
  47. package/mcp-server/lib/canonical-json.cjs +19 -0
  48. package/mcp-server/lib/change-workflow.cjs +596 -44
  49. package/mcp-server/lib/context-envelope.cjs +508 -0
  50. package/mcp-server/lib/decision-records.cjs +337 -0
  51. package/mcp-server/lib/doctor.cjs +91 -33
  52. package/mcp-server/lib/init-project.cjs +78 -39
  53. package/mcp-server/lib/managed-file-write.cjs +116 -0
  54. package/mcp-server/lib/plan-checkpoint.cjs +177 -0
  55. package/mcp-server/lib/project-context.cjs +178 -0
  56. package/mcp-server/lib/stage-checkpoints.cjs +309 -0
  57. package/mcp-server/lib/state-db.cjs +155 -24
  58. package/mcp-server/lib/task-ledger.cjs +522 -13
  59. package/mcp-server/lib/ultra-facade.cjs +630 -442
  60. package/mcp-server/lib/worker-packet.cjs +358 -0
  61. package/mcp-server/server.cjs +200 -1103
  62. package/orchestrator/daemon.cjs +27 -9
  63. package/orchestrator/parallel-orchestrator.cjs +111 -76
  64. package/orchestrator/session-runner.cjs +135 -34
  65. package/package.json +10 -3
  66. package/skills/ultra-change/SKILL.md +9 -8
  67. package/skills/ultra-deliver/SKILL.md +3 -3
  68. package/skills/ultra-dev/SKILL.md +18 -11
  69. package/skills/ultra-doctor/SKILL.md +2 -2
  70. package/skills/ultra-init/SKILL.md +6 -5
  71. package/skills/ultra-init/references/brownfield-adoption.md +11 -8
  72. package/skills/ultra-plan/SKILL.md +10 -8
  73. package/skills/ultra-research/SKILL.md +12 -7
  74. package/skills/ultra-review/SKILL.md +9 -6
  75. package/skills/ultra-review/references/unified-schema.md +2 -0
  76. package/skills/ultra-review/scripts/review_wait.py +8 -0
  77. package/skills/ultra-status/SKILL.md +2 -2
  78. package/skills/ultra-test/SKILL.md +4 -2
  79. package/skills/ultra-think/SKILL.md +7 -7
  80. package/skills/ultra-think/references/interaction-boundary.md +56 -0
  81. package/spec/cli-protocol.md +80 -199
  82. package/spec/mcp-tools.yaml +37 -2804
  83. package/spec/schemas/state-db.sql +107 -4
  84. package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
  85. package/ultra-tools/commands/status.cjs +369 -243
  86. package/mcp-server/lib/context-spine.cjs +0 -1208
  87. package/mcp-server/lib/decision-dialogue.cjs +0 -1128
  88. package/mcp-server/lib/project-breadcrumb.cjs +0 -173
  89. package/mcp-server/lib/spec-learning.cjs +0 -284
  90. package/mcp-server/lib/workflow-state.cjs +0 -3129
  91. package/skills/ultra-think/references/decision-dialogue.md +0 -155
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env python3
2
- """Re-inject live DB authority after compaction; checkpoints are advisory only."""
2
+ """Re-inject the live Context Envelope after compaction; checkpoints are advisory."""
3
3
 
4
4
  import json
5
5
  import sys
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
8
 
9
- from context_spine import (
10
- ContextSpineError,
9
+ from context_envelope import (
10
+ ContextEnvelopeError,
11
11
  find_root_for_hook,
12
- read_breadcrumb,
13
- render_breadcrumb,
12
+ read_context_envelope,
13
+ render_context_envelope,
14
14
  )
15
15
 
16
16
 
@@ -35,10 +35,11 @@ def _checkpoint_advisory(root: Path):
35
35
  return None
36
36
  if (
37
37
  not isinstance(value, dict)
38
- or value.get("schema") != 2
39
- or not isinstance(value.get("breadcrumb"), dict)
38
+ or value.get("schema") != 3
39
+ or not isinstance(value.get("context"), dict)
40
40
  or not isinstance(value.get("rendered"), str)
41
41
  or not value["rendered"].strip()
42
+ or value.get("context_digest") != value["context"].get("digest")
42
43
  ):
43
44
  print(f"[workflow_resume] invalid checkpoint schema in {file}", file=sys.stderr)
44
45
  return None
@@ -46,7 +47,7 @@ def _checkpoint_advisory(root: Path):
46
47
  "[Ultra checkpoint advisory]",
47
48
  "Live .ultra/.runtime/state.db could not be read; this checkpoint is not authority.",
48
49
  value["rendered"],
49
- "Run ultra-doctor before mutation.",
50
+ "Run ultra.doctor before mutation.",
50
51
  ])
51
52
 
52
53
 
@@ -66,11 +67,11 @@ def main() -> None:
66
67
  return
67
68
  text = None
68
69
  try:
69
- breadcrumb = read_breadcrumb(root)
70
- if breadcrumb and breadcrumb.get("workflow"):
71
- text = render_breadcrumb(root, breadcrumb)
72
- except ContextSpineError as exc:
73
- print(f"[workflow_resume] cannot inspect Context Spine: {exc}", file=sys.stderr)
70
+ envelope = read_context_envelope(root)
71
+ if envelope:
72
+ text = render_context_envelope(root, envelope)
73
+ except ContextEnvelopeError as exc:
74
+ print(f"[workflow_resume] cannot inspect Context Envelope: {exc}", file=sys.stderr)
74
75
  text = _checkpoint_advisory(root)
75
76
  if not text:
76
77
  print(json.dumps({}))
@@ -13,18 +13,16 @@ function main() {
13
13
  const requested = path.resolve(process.argv[discover ? 3 : 2] || process.cwd());
14
14
  const rootDir = discover ? runtime.findProjectRoot(requested) : requested;
15
15
  if (!rootDir) {
16
- process.stdout.write(`${JSON.stringify({
17
- root: null,
18
- breadcrumb: null,
19
- text: null,
20
- })}\n`);
16
+ process.stdout.write(`${JSON.stringify({ root: null, context: null, text: null })}\n`);
21
17
  return;
22
18
  }
23
- const breadcrumb = runtime.readProjectBreadcrumb(rootDir);
19
+ const context = runtime.readProjectContextEnvelope(rootDir, {
20
+ runtime: process.env.UBP_HOOK_RUNTIME || 'hook',
21
+ });
24
22
  process.stdout.write(`${JSON.stringify({
25
23
  root: rootDir,
26
- breadcrumb,
27
- text: breadcrumb ? runtime.renderProjectBreadcrumb(rootDir, breadcrumb) : null,
24
+ context,
25
+ text: context ? runtime.renderProjectContextEnvelope(rootDir, context) : null,
28
26
  })}\n`);
29
27
  }
30
28
 
@@ -32,7 +30,7 @@ if (require.main === module) {
32
30
  try {
33
31
  main();
34
32
  } catch (error) {
35
- process.stderr.write(`${error.code || 'BREADCRUMB_FAILED'}: ${error.message}\n`);
33
+ process.stderr.write(`${error.code || 'CONTEXT_ENVELOPE_FAILED'}: ${error.message}\n`);
36
34
  process.exitCode = 2;
37
35
  }
38
36
  }
@@ -7,7 +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
+ const checkpoints = require('./stage-checkpoints.cjs');
11
11
 
12
12
  const BASELINE_ID = /^[a-zA-Z0-9_-]+$/;
13
13
  const MODES = new Set(['greenfield', 'brownfield']);
@@ -718,47 +718,44 @@ function storedReadyInvariantBlockers(baseline) {
718
718
  return [...new Set(blockers)];
719
719
  }
720
720
 
721
- function completedBaselineResearch(db, baseline, rootDir) {
722
- const acceptedMode = baseline.mode === 'greenfield' ? 'full' : 'adoption';
723
- const rows = db.prepare(
724
- `SELECT id FROM workflow_runs
725
- WHERE kind = 'research' AND baseline_id = ? AND mode = ? AND status = 'completed'
726
- ORDER BY completed_at DESC, rowid DESC`,
727
- ).all(baseline.id, acceptedMode);
728
- const row = rows.find((candidate) => workflows.isConsumableWorkflowAuthority(
729
- workflows.readWorkflow(db, candidate.id, { rootDir }),
730
- ));
731
- return row ? workflows.readWorkflow(db, row.id, { rootDir }) : null;
721
+ function acceptedBaselineResearch(db, baseline) {
722
+ return checkpoints.currentCheckpoint(
723
+ db,
724
+ 'research',
725
+ { baseline_id: baseline.id },
726
+ { includeDraft: false },
727
+ );
732
728
  }
733
729
 
734
- function convergenceResearchBlockers(db, baseline, rootDir) {
735
- const run = completedBaselineResearch(db, baseline, rootDir);
736
- if (!run) return ['BASELINE_RESEARCH_INCOMPLETE'];
737
- return run.artifact_health.blockers.map((item) => `BASELINE_RESEARCH_${item}`);
730
+ function convergenceResearchBlockers(db, baseline) {
731
+ const checkpoint = acceptedBaselineResearch(db, baseline);
732
+ if (!checkpoint) return ['BASELINE_RESEARCH_INCOMPLETE'];
733
+ return [];
738
734
  }
739
735
 
740
- function storedResearchHealth(db, baseline, rootDir) {
741
- if (!baseline.research_run_id) {
736
+ function storedResearchHealth(db, baseline) {
737
+ const checkpointId = baseline.research_checkpoint_id || baseline.research_run_id;
738
+ if (!checkpointId) {
742
739
  return { blockers: ['BASELINE_RESEARCH_PROVENANCE_MISSING'], warnings: [] };
743
740
  }
744
- const run = workflows.readWorkflow(db, baseline.research_run_id, { rootDir });
745
- if (!run || run.kind !== 'research' || run.status !== 'completed') {
741
+ let checkpoint;
742
+ try {
743
+ checkpoint = checkpoints.readCheckpoint(db, checkpointId);
744
+ } catch (error) {
745
+ if (error?.code === 'CHECKPOINT_DIGEST_MISMATCH') {
746
+ return { blockers: ['BASELINE_RESEARCH_RECORD_INVALID'], warnings: [] };
747
+ }
748
+ throw error;
749
+ }
750
+ if (!checkpoint
751
+ || checkpoint.stage !== 'research'
752
+ || checkpoint.scope_type !== 'baseline'
753
+ || checkpoint.scope_id !== baseline.id
754
+ || checkpoint.status !== 'accepted'
755
+ || checkpoints.checkpointDigest(checkpoint) !== checkpoint.digest) {
746
756
  return { blockers: ['BASELINE_RESEARCH_RECORD_INVALID'], warnings: [] };
747
757
  }
748
- const livingSpecifications = new Set(
749
- (Array.isArray(baseline.spec_refs) ? baseline.spec_refs : [])
750
- .map((item) => item?.path).filter(Boolean),
751
- );
752
- const blockers = run.artifact_health.blockers.filter((item) => {
753
- const separator = item.indexOf(':');
754
- if (separator < 0) return true;
755
- const artifactPath = item.slice(separator + 1);
756
- return !livingSpecifications.has(artifactPath);
757
- });
758
- return {
759
- blockers: blockers.map((item) => `BASELINE_RESEARCH_${item}`),
760
- warnings: [],
761
- };
758
+ return { blockers: [], warnings: checkpoint.diagnostics || [] };
762
759
  }
763
760
 
764
761
  function convergenceBlockers(db, baseline, input, rootDir) {
@@ -780,7 +777,7 @@ function convergenceBlockers(db, baseline, input, rootDir) {
780
777
  for (const kind of ['discovery', 'product', 'architecture']) {
781
778
  if (!baseline.spec_refs.some((ref) => ref.kind === kind)) blockers.add(`BASELINE_SPEC_MISSING:${kind}`);
782
779
  }
783
- for (const blocker of convergenceResearchBlockers(db, baseline, rootDir)) blockers.add(blocker);
780
+ for (const blocker of convergenceResearchBlockers(db, baseline)) blockers.add(blocker);
784
781
  for (const blocker of storedSpecBlockers(baseline, rootDir)) blockers.add(blocker);
785
782
  for (const blocker of storedEvidenceBlockers(baseline, rootDir)) blockers.add(blocker);
786
783
  for (const blocker of storedScopeBlockers(baseline, rootDir)) blockers.add(blocker);
@@ -834,11 +831,12 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
834
831
  });
835
832
  return { ready: false, status: 'blocked', blockers, baseline: readBaseline(db, current.id) };
836
833
  }
837
- const research = completedBaselineResearch(db, current, rootDir);
834
+ const research = acceptedBaselineResearch(db, current);
838
835
  ops.tx(db, () => {
839
836
  db.prepare(
840
837
  `UPDATE baselines SET status = 'ready', approved_by = ?, approval_note = ?,
841
- worktree_accepted = ?, known_red_accepted = ?, research_run_id = ?,
838
+ worktree_accepted = ?, known_red_accepted = ?, research_checkpoint_id = ?,
839
+ research_run_id = NULL,
842
840
  converged_at = ?, updated_at = ? WHERE id = ?`,
843
841
  ).run(
844
842
  String(input.approved_by).trim(), String(input.approval_note).trim(),
@@ -874,7 +872,7 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
874
872
  status: 'fail', blockers: [`BASELINE_NOT_READY:${baseline.status}`], warnings: [], baseline,
875
873
  };
876
874
  }
877
- const research = storedResearchHealth(db, baseline, rootDir);
875
+ const research = storedResearchHealth(db, baseline);
878
876
  const blockers = [
879
877
  ...storedReadyInvariantBlockers(baseline),
880
878
  ...(Array.isArray(baseline.scope) ? storedScopeBlockers(baseline, rootDir) : []),
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ function canonicalJson(value) {
6
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
7
+ if (value && typeof value === 'object') {
8
+ return `{${Object.keys(value).sort().map(
9
+ (key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`,
10
+ ).join(',')}}`;
11
+ }
12
+ return JSON.stringify(value);
13
+ }
14
+
15
+ function digest(value) {
16
+ return crypto.createHash('sha256').update(canonicalJson(value)).digest('hex');
17
+ }
18
+
19
+ module.exports = { canonicalJson, digest };