ultra-builder-pro-cli 0.20.0 → 0.22.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 (109) hide show
  1. package/.ultra-template/docs/research/README.md +26 -5
  2. package/.ultra-template/specs/discovery.md +1 -1
  3. package/.ultra-template/tasks/tasks.json +10 -3
  4. package/.ultra-template/templates/task-context.md +19 -0
  5. package/CHANGELOG.md +106 -0
  6. package/README.md +215 -41
  7. package/adapters/_shared/codex-assets.cjs +69 -11
  8. package/adapters/_shared/interaction-contract.cjs +36 -3
  9. package/adapters/_shared/runtime-assets.cjs +41 -1
  10. package/adapters/claude.js +7 -5
  11. package/adapters/codex.js +1 -1
  12. package/adapters/kimi.js +2 -8
  13. package/adapters/opencode.js +43 -30
  14. package/bin/orchestrator.js +64 -24
  15. package/docs/AGENT-CONTEXT.md +89 -49
  16. package/docs/ARCHITECTURE.md +112 -45
  17. package/docs/ARTIFACT-AUTHORITY.md +274 -0
  18. package/docs/COMMIT-HASH-BACKFILL.md +33 -90
  19. package/docs/DECISIONS.md +14 -6
  20. package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
  21. package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
  22. package/docs/ROADMAP.md +20 -6
  23. package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
  24. package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
  25. package/docs/WORKFLOW-LIFECYCLE.md +105 -50
  26. package/hooks/README.md +10 -8
  27. package/hooks/active_task_context.py +105 -18
  28. package/hooks/context_spine.py +16 -4
  29. package/hooks/health_check.py +5 -4
  30. package/hooks/pre_stop_check.py +5 -2
  31. package/hooks/runtime_paths.py +396 -0
  32. package/hooks/subagent_tracker.py +5 -2
  33. package/hooks/workflow_checkpoint.py +16 -3
  34. package/hooks/workflow_context.py +11 -3
  35. package/hooks/workflow_resume.py +7 -4
  36. package/mcp-server/breadcrumb.cjs +20 -2
  37. package/mcp-server/lib/archive-journal.cjs +1026 -111
  38. package/mcp-server/lib/archive-mutation-worker.py +365 -0
  39. package/mcp-server/lib/artifact-registry.cjs +1127 -0
  40. package/mcp-server/lib/baseline-workflow.cjs +69 -28
  41. package/mcp-server/lib/change-packet.cjs +818 -0
  42. package/mcp-server/lib/change-workflow.cjs +932 -176
  43. package/mcp-server/lib/context-paths.cjs +142 -0
  44. package/mcp-server/lib/context-spine.cjs +511 -45
  45. package/mcp-server/lib/decision-dialogue.cjs +345 -3
  46. package/mcp-server/lib/delivery-transaction.cjs +471 -0
  47. package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
  48. package/mcp-server/lib/doctor.cjs +401 -11
  49. package/mcp-server/lib/git-bootstrap.cjs +458 -47
  50. package/mcp-server/lib/init-project.cjs +240 -95
  51. package/mcp-server/lib/plan-publication-worker.cjs +830 -0
  52. package/mcp-server/lib/plan-store.cjs +700 -5
  53. package/mcp-server/lib/project-breadcrumb.cjs +23 -4
  54. package/mcp-server/lib/projector.cjs +211 -42
  55. package/mcp-server/lib/runtime-paths.cjs +1687 -0
  56. package/mcp-server/lib/safe-project-file.cjs +385 -0
  57. package/mcp-server/lib/spec-learning.cjs +28 -12
  58. package/mcp-server/lib/state-authority.cjs +19 -3
  59. package/mcp-server/lib/state-db.cjs +263 -9
  60. package/mcp-server/lib/state-ops.cjs +254 -50
  61. package/mcp-server/lib/task-ledger.cjs +1342 -0
  62. package/mcp-server/lib/telemetry.cjs +12 -3
  63. package/mcp-server/lib/workflow-state.cjs +610 -58
  64. package/mcp-server/server.cjs +423 -42
  65. package/orchestrator/daemon.cjs +2 -10
  66. package/orchestrator/parallel-orchestrator.cjs +314 -50
  67. package/orchestrator/planner/plan-builder.cjs +1 -1
  68. package/orchestrator/recovery.cjs +125 -7
  69. package/orchestrator/session-close-journal-worker.cjs +5 -0
  70. package/orchestrator/session-close-journal.cjs +1014 -0
  71. package/orchestrator/session-runner.cjs +679 -69
  72. package/orchestrator/worktree-manager.cjs +186 -35
  73. package/package.json +2 -3
  74. package/skills/cc-collab/SKILL.md +4 -1
  75. package/skills/codex-collab/SKILL.md +5 -2
  76. package/skills/ultra-change/SKILL.md +34 -8
  77. package/skills/ultra-deliver/SKILL.md +26 -11
  78. package/skills/ultra-dev/SKILL.md +52 -10
  79. package/skills/ultra-doctor/SKILL.md +16 -3
  80. package/skills/ultra-init/SKILL.md +22 -11
  81. package/skills/ultra-plan/SKILL.md +58 -18
  82. package/skills/ultra-research/SKILL.md +37 -35
  83. package/skills/ultra-review/SKILL.md +14 -8
  84. package/skills/ultra-status/SKILL.md +18 -9
  85. package/skills/ultra-test/SKILL.md +11 -5
  86. package/skills/ultra-think/SKILL.md +6 -2
  87. package/skills/ultra-think/references/decision-dialogue.md +17 -6
  88. package/skills/ultra-verify/SKILL.md +4 -1
  89. package/spec/cli-protocol.md +21 -7
  90. package/spec/mcp-tools.yaml +629 -44
  91. package/spec/schemas/change-delta.v1.schema.json +153 -0
  92. package/spec/schemas/context-file.v4.5.schema.json +1 -1
  93. package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
  94. package/spec/schemas/mcp-tools.schema.json +1 -1
  95. package/spec/schemas/state-db.sql +51 -14
  96. package/spec/schemas/task-ledger.v1.schema.json +217 -0
  97. package/spec/schemas/tasks.v4.5.schema.json +1 -1
  98. package/ultra-tools/cli.cjs +3 -2
  99. package/ultra-tools/commands/db.cjs +76 -25
  100. package/ultra-tools/commands/migrate.cjs +1000 -82
  101. package/ultra-tools/commands/session.cjs +34 -8
  102. package/ultra-tools/commands/status.cjs +32 -6
  103. package/ultra-tools/commands/system.cjs +129 -22
  104. package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
  105. package/commands/learn.md +0 -10
  106. package/orchestrator/skill-miner.cjs +0 -133
  107. package/output-styles/coding-vibes.md +0 -67
  108. package/output-styles/structural-thinking.md +0 -61
  109. package/skills/learn/SKILL.md +0 -58
@@ -59,6 +59,49 @@ function gitBranch(rootDir) {
59
59
  return result.status === 0 ? (result.stdout.trim() || null) : null;
60
60
  }
61
61
 
62
+ function gitCommitIsAncestor(rootDir, ancestor, descendant) {
63
+ if (!ancestor || !descendant) return false;
64
+ const result = spawnSync(
65
+ 'git', ['merge-base', '--is-ancestor', ancestor, descendant],
66
+ { cwd: rootDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
67
+ );
68
+ return result.status === 0;
69
+ }
70
+
71
+ function hashScopedWorkingTree(rootDir, relativePaths) {
72
+ const root = path.resolve(rootDir);
73
+ const digest = crypto.createHash('sha256');
74
+ for (const relative of [...new Set(relativePaths)].sort()) {
75
+ const file = path.resolve(root, relative);
76
+ if (file !== root && !file.startsWith(`${root}${path.sep}`)) {
77
+ throw new BaselineWorkflowError(
78
+ 'BASELINE_SCOPE_INVALID',
79
+ `Git returned a path outside the repository: ${relative}`,
80
+ );
81
+ }
82
+ digest.update(relative).update('\0');
83
+ const stat = fs.lstatSync(file, { throwIfNoEntry: false });
84
+ if (!stat) {
85
+ digest.update('missing\0');
86
+ } else if (stat.isSymbolicLink()) {
87
+ digest.update('symlink\0').update(fs.readlinkSync(file)).update('\0');
88
+ } else if (stat.isFile()) {
89
+ digest.update(stat.mode & 0o111 ? 'file+x\0' : 'file\0');
90
+ digest.update(fs.readFileSync(file)).update('\0');
91
+ } else if (stat.isDirectory()) {
92
+ const submoduleHead = spawnSync('git', ['-C', file, 'rev-parse', 'HEAD'], {
93
+ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
94
+ });
95
+ digest.update('directory\0');
96
+ if (submoduleHead.status === 0) digest.update(submoduleHead.stdout.trim());
97
+ digest.update('\0');
98
+ } else {
99
+ digest.update(`special:${stat.mode}\0`);
100
+ }
101
+ }
102
+ return digest.digest('hex');
103
+ }
104
+
62
105
  function gitWorktreeSnapshot(rootDir, scope = ['.']) {
63
106
  const repositoryRoot = gitRepositoryRoot(rootDir);
64
107
  if (!repositoryRoot) {
@@ -76,40 +119,24 @@ function gitWorktreeSnapshot(rootDir, scope = ['.']) {
76
119
  'git', ['status', '--porcelain=v1', '-z', '--untracked-files=all', ...args],
77
120
  { cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
78
121
  );
79
- const diff = head
80
- ? spawnSync(
81
- 'git', ['diff', '--binary', '--no-ext-diff', 'HEAD', ...args],
82
- { cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
83
- )
84
- : { status: 0, stdout: Buffer.alloc(0) };
85
- const untracked = spawnSync(
86
- 'git', ['ls-files', '--others', '--exclude-standard', '-z', ...args],
122
+ const inventory = spawnSync(
123
+ 'git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z',
124
+ '--', ...pathspecs, ':(exclude).ultra', ':(exclude).ultra/**'],
87
125
  { cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
88
126
  );
89
- if (status.status !== 0 || diff.status !== 0 || untracked.status !== 0) {
127
+ if (status.status !== 0 || inventory.status !== 0) {
90
128
  return {
91
129
  head, branch: gitBranch(rootDir), state: 'unavailable', digest: null, files: [],
92
130
  };
93
131
  }
94
132
  const statusText = status.stdout.toString('utf8');
95
133
  const files = statusText.split('\0').filter(Boolean);
96
- const digest = crypto.createHash('sha256');
97
- digest.update(head || 'unborn').update('\0').update(gitBranch(rootDir) || '').update('\0');
98
- digest.update(status.stdout).update(diff.stdout);
99
- for (const relative of untracked.stdout.toString('utf8').split('\0').filter(Boolean).sort()) {
100
- digest.update(relative).update('\0');
101
- const file = path.resolve(rootDir, relative);
102
- if (file.startsWith(`${path.resolve(rootDir)}${path.sep}`)
103
- && fs.existsSync(file) && fs.statSync(file).isFile()) {
104
- digest.update(fs.readFileSync(file));
105
- }
106
- digest.update('\0');
107
- }
134
+ const scopedFiles = inventory.stdout.toString('utf8').split('\0').filter(Boolean);
108
135
  return {
109
136
  head,
110
137
  branch: gitBranch(rootDir),
111
138
  state: head ? (files.length > 0 ? 'dirty' : 'clean') : 'unborn',
112
- digest: digest.digest('hex'),
139
+ digest: hashScopedWorkingTree(rootDir, scopedFiles),
113
140
  files,
114
141
  };
115
142
  }
@@ -693,11 +720,14 @@ function storedReadyInvariantBlockers(baseline) {
693
720
 
694
721
  function completedBaselineResearch(db, baseline, rootDir) {
695
722
  const acceptedMode = baseline.mode === 'greenfield' ? 'full' : 'adoption';
696
- const row = db.prepare(
723
+ const rows = db.prepare(
697
724
  `SELECT id FROM workflow_runs
698
725
  WHERE kind = 'research' AND baseline_id = ? AND mode = ? AND status = 'completed'
699
- ORDER BY completed_at DESC, rowid DESC LIMIT 1`,
700
- ).get(baseline.id, acceptedMode);
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
+ ));
701
731
  return row ? workflows.readWorkflow(db, row.id, { rootDir }) : null;
702
732
  }
703
733
 
@@ -738,7 +768,12 @@ function convergenceBlockers(db, baseline, input, rootDir) {
738
768
  else if (baseline.repository_revision !== expected) blockers.add('BASELINE_REVISION_MISMATCH');
739
769
  const snapshot = gitWorktreeSnapshot(rootDir, baseline.scope);
740
770
  if (snapshot.state === 'unborn') blockers.add('BASELINE_GIT_HEAD_REQUIRED');
741
- if (snapshot.head && expected && snapshot.head !== expected) blockers.add('BASELINE_HEAD_STALE');
771
+ if (snapshot.head && expected && snapshot.head !== expected
772
+ && (!gitCommitIsAncestor(rootDir, expected, snapshot.head)
773
+ || (baseline.worktree_digest && snapshot.digest
774
+ && baseline.worktree_digest !== snapshot.digest))) {
775
+ blockers.add('BASELINE_HEAD_STALE');
776
+ }
742
777
  if (baseline.repository_branch && snapshot.branch
743
778
  && baseline.repository_branch !== snapshot.branch) blockers.add('BASELINE_BRANCH_STALE');
744
779
  if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) blockers.add('BASELINE_SCOPE_MISSING');
@@ -854,7 +889,11 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
854
889
  blockers.push('BASELINE_GIT_HEAD_REQUIRED');
855
890
  }
856
891
  if (snapshot.head && baseline.repository_revision && snapshot.head !== baseline.repository_revision) {
857
- blockers.push('BASELINE_HEAD_STALE');
892
+ if (!gitCommitIsAncestor(rootDir, baseline.repository_revision, snapshot.head)
893
+ || (baseline.worktree_digest && snapshot.digest
894
+ && baseline.worktree_digest !== snapshot.digest)) {
895
+ blockers.push('BASELINE_HEAD_STALE');
896
+ }
858
897
  } else if (!snapshot.head && baseline.repository_revision?.startsWith('workspace:')) {
859
898
  const revision = workspaceRevision({
860
899
  scope: baseline.scope, specs: baseline.spec_refs, evidence: baseline.evidence,
@@ -883,6 +922,7 @@ function inferSpecKind(file) {
883
922
 
884
923
  function reconcileBaseline(db, {
885
924
  baseline_updates = [], change_id = null, reconciliation = null,
925
+ accept_delivery_worktree = false,
886
926
  } = {}, {
887
927
  rootDir = process.cwd(), emitEvent = true,
888
928
  } = {}) {
@@ -926,11 +966,12 @@ function reconcileBaseline(db, {
926
966
  ops.tx(db, () => {
927
967
  db.prepare(
928
968
  `UPDATE baselines SET repository_revision = ?, repository_branch = ?, worktree_state = ?,
929
- worktree_digest = ?, worktree_files_json = ?, worktree_accepted = 0,
969
+ worktree_digest = ?, worktree_files_json = ?, worktree_accepted = ?,
930
970
  spec_refs_json = ?, evidence_json = ?, gaps_json = ?, unknowns_json = ?,
931
971
  updated_at = ? WHERE id = ?`,
932
972
  ).run(
933
973
  revision, snapshot.branch, snapshot.state, snapshot.digest, JSON.stringify(snapshot.files),
974
+ accept_delivery_worktree && snapshot.state === 'dirty' ? 1 : 0,
934
975
  JSON.stringify(specs), JSON.stringify(evidence), JSON.stringify(gaps),
935
976
  JSON.stringify(unknowns), ts, current.id,
936
977
  );