ultra-builder-pro-cli 0.25.0 → 0.26.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 (241) hide show
  1. package/.ultra-template/contexts/TEMPLATE.md +87 -0
  2. package/.ultra-template/decisions/.gitkeep +1 -0
  3. package/.ultra-template/evidence/.gitkeep +1 -0
  4. package/.ultra-template/north-star.md +46 -0
  5. package/.ultra-template/research/.gitkeep +1 -0
  6. package/.ultra-template/specs/discovery.md +2 -2
  7. package/.ultra-template/tasks.json +3 -0
  8. package/.ultra-template/test-report.json +27 -0
  9. package/CHANGELOG.md +49 -0
  10. package/README.md +217 -579
  11. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  12. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  13. package/adapters/_shared/file-ops.cjs +76 -2
  14. package/adapters/_shared/host-profile.cjs +97 -0
  15. package/adapters/_shared/plugin-core.cjs +204 -0
  16. package/adapters/_shared/runtime-assets.cjs +31 -96
  17. package/adapters/claude.js +92 -291
  18. package/adapters/codex.js +309 -842
  19. package/adapters/grok.js +143 -1469
  20. package/adapters/kimi.js +124 -1053
  21. package/adapters/opencode.js +230 -742
  22. package/bin/delegate-worker.cjs +323 -0
  23. package/bin/delegate.cjs +456 -0
  24. package/bin/install.js +38 -11
  25. package/docs/ARCHITECTURE.md +122 -216
  26. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  27. package/docs/DECISIONS.md +75 -87
  28. package/docs/PHILOSOPHY.md +177 -0
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
  30. package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
  31. package/docs/SKILL-AUTHORING.md +94 -73
  32. package/docs/WORKFLOW-LIFECYCLE.md +93 -189
  33. package/hooks/README.md +17 -37
  34. package/hooks/_common.py +113 -0
  35. package/hooks/adapters/codex.py +9 -16
  36. package/hooks/adapters/grok.py +5 -5
  37. package/hooks/adapters/kimi.py +8 -14
  38. package/hooks/block_dangerous_commands.py +95 -0
  39. package/hooks/compact_context.py +65 -0
  40. package/hooks/mid_workflow_recall.py +25 -0
  41. package/hooks/post_edit_guard.py +105 -0
  42. package/hooks/session_context.py +30 -0
  43. package/package.json +17 -52
  44. package/skills/ultra-change/SKILL.md +97 -50
  45. package/skills/ultra-change/references/change-contract.md +40 -1
  46. package/skills/ultra-delegate/SKILL.md +82 -0
  47. package/skills/ultra-delegate/references/delegation-contract.md +51 -0
  48. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  49. package/skills/ultra-deliver/SKILL.md +61 -42
  50. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  51. package/skills/ultra-dev/SKILL.md +96 -62
  52. package/skills/ultra-dev/references/debugging.md +22 -0
  53. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  54. package/skills/ultra-grilling/SKILL.md +79 -0
  55. package/skills/ultra-init/SKILL.md +94 -37
  56. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  57. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  58. package/skills/ultra-plan/SKILL.md +88 -68
  59. package/skills/ultra-research/SKILL.md +62 -48
  60. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  61. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  62. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  63. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  64. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  65. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  66. package/skills/ultra-research/references/10-user-personas.md +3 -2
  67. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  68. package/skills/ultra-research/references/20-user-stories.md +4 -3
  69. package/skills/ultra-research/references/21-features-scope.md +3 -2
  70. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  71. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  72. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  73. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  74. package/skills/ultra-research/references/40-deployment.md +3 -3
  75. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  76. package/skills/ultra-research/references/99-synthesis.md +7 -7
  77. package/skills/ultra-review/SKILL.md +59 -32
  78. package/skills/ultra-review/references/code.md +17 -0
  79. package/skills/ultra-review/references/comments.md +12 -0
  80. package/skills/ultra-review/references/design.md +12 -0
  81. package/skills/ultra-review/references/errors.md +13 -0
  82. package/skills/ultra-review/references/spec.md +13 -0
  83. package/skills/ultra-review/references/tests.md +12 -0
  84. package/skills/ultra-review/references/worker-packet.md +40 -0
  85. package/skills/ultra-review/scripts/review_wait.py +26 -15
  86. package/skills/ultra-status/SKILL.md +61 -36
  87. package/skills/ultra-tdd/SKILL.md +81 -0
  88. package/skills/ultra-tdd/references/templates/README.md +20 -0
  89. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  90. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  91. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  92. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  93. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  94. package/skills/ultra-tdd/references/test-execution.md +18 -0
  95. package/skills/ultra-test/SKILL.md +70 -30
  96. package/skills/ultra-test/references/export-syntax.md +18 -0
  97. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  98. package/skills/ultra-think/SKILL.md +49 -27
  99. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  100. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  101. package/.ultra-template/docs/research/README.md +0 -55
  102. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  103. package/.ultra-template/reports/templates/test-report.json +0 -18
  104. package/.ultra-template/tasks/tasks.json +0 -15
  105. package/.ultra-template/templates/task-context.md +0 -19
  106. package/adapters/_shared/codex-assets.cjs +0 -725
  107. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  108. package/adapters/_shared/interaction-contract.cjs +0 -129
  109. package/adapters/_shared/path-rewrite.cjs +0 -79
  110. package/adapters/_shared/runtime-native.cjs +0 -132
  111. package/adapters/_shared/settings-merge.cjs +0 -76
  112. package/agents/code-reviewer.md +0 -42
  113. package/agents/debugger.md +0 -40
  114. package/agents/review-code.md +0 -49
  115. package/agents/review-comments.md +0 -38
  116. package/agents/review-coordinator.md +0 -56
  117. package/agents/review-design.md +0 -40
  118. package/agents/review-errors.md +0 -43
  119. package/agents/review-spec.md +0 -39
  120. package/agents/review-tests.md +0 -40
  121. package/agents/tdd-runner.md +0 -38
  122. package/bin/orchestrator.js +0 -383
  123. package/commands/ultra-change.md +0 -10
  124. package/commands/ultra-deliver.md +0 -10
  125. package/commands/ultra-dev.md +0 -10
  126. package/commands/ultra-doctor.md +0 -10
  127. package/commands/ultra-init.md +0 -10
  128. package/commands/ultra-plan.md +0 -10
  129. package/commands/ultra-research.md +0 -10
  130. package/commands/ultra-review.md +0 -10
  131. package/commands/ultra-status.md +0 -10
  132. package/commands/ultra-test.md +0 -10
  133. package/commands/ultra-think.md +0 -10
  134. package/docs/AGENT-CONTEXT.md +0 -191
  135. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  136. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  137. package/docs/ROADMAP.md +0 -101
  138. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  139. package/hooks/active_task_context.py +0 -152
  140. package/hooks/context_envelope.py +0 -100
  141. package/hooks/health_check.py +0 -196
  142. package/hooks/pre_stop_check.py +0 -66
  143. package/hooks/runtime_paths.py +0 -396
  144. package/hooks/subagent_tracker.py +0 -88
  145. package/hooks/workflow_checkpoint.py +0 -76
  146. package/hooks/workflow_context.py +0 -48
  147. package/hooks/workflow_resume.py +0 -84
  148. package/mcp-server/hook-context.cjs +0 -38
  149. package/mcp-server/hook-event.cjs +0 -34
  150. package/mcp-server/lib/archive-journal.cjs +0 -1119
  151. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  152. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  153. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  154. package/mcp-server/lib/canonical-json.cjs +0 -19
  155. package/mcp-server/lib/change-authority.cjs +0 -26
  156. package/mcp-server/lib/change-packet.cjs +0 -790
  157. package/mcp-server/lib/change-workflow.cjs +0 -65
  158. package/mcp-server/lib/context-envelope.cjs +0 -581
  159. package/mcp-server/lib/context-paths.cjs +0 -142
  160. package/mcp-server/lib/decision-records.cjs +0 -423
  161. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  162. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  163. package/mcp-server/lib/doctor.cjs +0 -627
  164. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  165. package/mcp-server/lib/init-project.cjs +0 -1021
  166. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  167. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  168. package/mcp-server/lib/managed-file-write.cjs +0 -172
  169. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  170. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  171. package/mcp-server/lib/plan-store.cjs +0 -794
  172. package/mcp-server/lib/prd-parser.cjs +0 -83
  173. package/mcp-server/lib/pricing.cjs +0 -44
  174. package/mcp-server/lib/project-context.cjs +0 -178
  175. package/mcp-server/lib/projector.cjs +0 -386
  176. package/mcp-server/lib/provider-refs.cjs +0 -42
  177. package/mcp-server/lib/runtime-paths.cjs +0 -1687
  178. package/mcp-server/lib/runtime-state.cjs +0 -222
  179. package/mcp-server/lib/safe-project-file.cjs +0 -385
  180. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  181. package/mcp-server/lib/state-authority.cjs +0 -95
  182. package/mcp-server/lib/state-db.cjs +0 -1580
  183. package/mcp-server/lib/state-ops.cjs +0 -1428
  184. package/mcp-server/lib/task-contract.cjs +0 -404
  185. package/mcp-server/lib/task-expander.cjs +0 -105
  186. package/mcp-server/lib/task-ledger.cjs +0 -1964
  187. package/mcp-server/lib/telemetry.cjs +0 -85
  188. package/mcp-server/lib/topo.cjs +0 -131
  189. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  190. package/mcp-server/lib/worker-packet.cjs +0 -358
  191. package/mcp-server/server.cjs +0 -437
  192. package/orchestrator/auto-merge.cjs +0 -128
  193. package/orchestrator/daemon.cjs +0 -264
  194. package/orchestrator/dispatch-rules.cjs +0 -78
  195. package/orchestrator/parallel-orchestrator.cjs +0 -903
  196. package/orchestrator/planner/plan-builder.cjs +0 -125
  197. package/orchestrator/recovery.cjs +0 -188
  198. package/orchestrator/session-close-journal-worker.cjs +0 -5
  199. package/orchestrator/session-close-journal.cjs +0 -1014
  200. package/orchestrator/session-runner.cjs +0 -1153
  201. package/orchestrator/worktree-manager.cjs +0 -282
  202. package/settings.json +0 -7
  203. package/skills/cc-collab/SKILL.md +0 -42
  204. package/skills/code-review-expert/SKILL.md +0 -43
  205. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  206. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  207. package/skills/code-review-expert/references/removal-plan.md +0 -24
  208. package/skills/codex-collab/SKILL.md +0 -49
  209. package/skills/codex-collab/evals/evals.json +0 -35
  210. package/skills/integration-rules/SKILL.md +0 -32
  211. package/skills/security-rules/SKILL.md +0 -38
  212. package/skills/testing-rules/SKILL.md +0 -32
  213. package/skills/ultra-doctor/SKILL.md +0 -41
  214. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  215. package/skills/ultra-verify/SKILL.md +0 -46
  216. package/skills/ultra-verify/evals/evals.json +0 -46
  217. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  218. package/skills/ultra-verify/references/evidence-status.md +0 -21
  219. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  220. package/spec/cli-protocol.md +0 -102
  221. package/spec/mcp-tools.yaml +0 -312
  222. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  223. package/spec/schemas/change-delta.v1.schema.json +0 -153
  224. package/spec/schemas/command-manifest.schema.json +0 -29
  225. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  226. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  227. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  228. package/spec/schemas/mcp-tools.schema.json +0 -136
  229. package/spec/schemas/skill-manifest.schema.json +0 -24
  230. package/spec/schemas/state-db.sql +0 -641
  231. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  232. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  233. package/spec/schemas/test-report.v1.schema.json +0 -225
  234. package/ultra-tools/cli.cjs +0 -129
  235. package/ultra-tools/commands/db.cjs +0 -226
  236. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  237. package/ultra-tools/commands/migrate.cjs +0 -1386
  238. package/ultra-tools/commands/session.cjs +0 -213
  239. package/ultra-tools/commands/status.cjs +0 -559
  240. package/ultra-tools/commands/system.cjs +0 -498
  241. package/ultra-tools/commands/task.cjs +0 -105
@@ -1,1021 +0,0 @@
1
- 'use strict';
2
-
3
- // Implementation for MCP tool `task.init_project` and the CLI fallback
4
- // `ultra-tools task init-project`. Copies the bundled templates/.ultra/ skeleton
5
- // to the target project root and records project metadata in authoritative state.
6
- //
7
- // Error contract matches spec/mcp-tools.yaml#task.init_project:
8
- // ULTRA_DIR_EXISTS | TEMPLATE_MISSING | TARGET_NOT_DIR | IO_ERROR
9
- //
10
- // Not a pure function — touches the filesystem. Kept in lib/ for parity with
11
- // state-ops/projector modules; Phase 4+ may move cross-tool helpers to
12
- // mcp-server/tools/ if the catalogue grows.
13
-
14
- const fs = require('node:fs');
15
- const path = require('node:path');
16
- const Database = require('better-sqlite3');
17
-
18
- const { EXPECTED_VERSION, initStateDb } = require('./state-db.cjs');
19
- const { assertStateAuthority, inspectProjection } = require('./state-authority.cjs');
20
- const taskLedger = require('./task-ledger.cjs');
21
- const baselines = require('./baseline-workflow.cjs');
22
- const gitBootstrap = require('./git-bootstrap.cjs');
23
- const runtimePaths = require('./runtime-paths.cjs');
24
- const runtimeState = require('./runtime-state.cjs');
25
- const ops = require('./state-ops.cjs');
26
- const stageCheckpoints = require('./stage-checkpoints.cjs');
27
-
28
- const REPO_ROOT = process.env.UBP_RUNTIME_ROOT
29
- ? path.resolve(process.env.UBP_RUNTIME_ROOT)
30
- : path.resolve(__dirname, '..', '..');
31
- const SOURCE_TEMPLATE = path.join(REPO_ROOT, 'templates', '.ultra');
32
- const PACKAGED_TEMPLATE = path.join(REPO_ROOT, '.ultra-template');
33
- const DEFAULT_TEMPLATE = fs.existsSync(SOURCE_TEMPLATE) ? SOURCE_TEMPLATE : PACKAGED_TEMPLATE;
34
-
35
- class InitProjectError extends Error {
36
- constructor(code, message, { retriable = false, cause, details } = {}) {
37
- super(message);
38
- this.name = 'InitProjectError';
39
- this.code = code;
40
- this.retriable = retriable;
41
- if (cause) this.cause = cause;
42
- if (details) this.details = details;
43
- }
44
- }
45
-
46
- function resolveTargetDir(target) {
47
- if (!target) throw new InitProjectError('TARGET_NOT_DIR', 'target_dir must be a non-empty string');
48
- return path.isAbsolute(target) ? target : path.resolve(process.cwd(), target);
49
- }
50
-
51
- function ensureTargetDir(absTarget) {
52
- if (!fs.existsSync(absTarget)) {
53
- try { fs.mkdirSync(absTarget, { recursive: true }); }
54
- catch (err) { throw new InitProjectError('IO_ERROR', `cannot create target_dir: ${err.message}`, { retriable: true, cause: err }); }
55
- return;
56
- }
57
- const stat = fs.statSync(absTarget);
58
- if (!stat.isDirectory()) {
59
- throw new InitProjectError('TARGET_NOT_DIR', `target_dir exists but is not a directory: ${absTarget}`);
60
- }
61
- }
62
-
63
- function ensureTemplate(sourceOverride) {
64
- const source = sourceOverride ? path.resolve(sourceOverride) : DEFAULT_TEMPLATE;
65
- if (!fs.existsSync(source) || !fs.statSync(source).isDirectory()) {
66
- throw new InitProjectError('TEMPLATE_MISSING', `source_template not found: ${source}`);
67
- }
68
- return source;
69
- }
70
-
71
- function timestampSlug(date = new Date()) {
72
- const pad = (n) => String(n).padStart(2, '0');
73
- return [
74
- date.getFullYear(),
75
- pad(date.getMonth() + 1),
76
- pad(date.getDate()),
77
- ].join('') + '-' + [
78
- pad(date.getHours()),
79
- pad(date.getMinutes()),
80
- pad(date.getSeconds()),
81
- ].join('');
82
- }
83
-
84
- function walkRelative(root) {
85
- const out = [];
86
- function walk(dir) {
87
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
88
- if (entry.name === '.DS_Store') continue;
89
- const full = path.join(dir, entry.name);
90
- const rel = path.relative(root, full);
91
- if (entry.isDirectory()) walk(full);
92
- else if (entry.isFile()) out.push(rel);
93
- }
94
- }
95
- walk(root);
96
- return out.sort();
97
- }
98
-
99
- function copyTemplate(source, dest) {
100
- const files = walkRelative(source);
101
- for (const rel of files) {
102
- const from = path.join(source, rel);
103
- const to = path.join(dest, rel);
104
- fs.mkdirSync(path.dirname(to), { recursive: true });
105
- fs.copyFileSync(from, to);
106
- }
107
- return files;
108
- }
109
-
110
- function copyMissingTemplate(source, dest) {
111
- const copied = [];
112
- for (const rel of walkRelative(source)) {
113
- const to = path.join(dest, rel);
114
- if (fs.existsSync(to)) continue;
115
- fs.mkdirSync(path.dirname(to), { recursive: true });
116
- fs.copyFileSync(path.join(source, rel), to);
117
- copied.push(rel);
118
- }
119
- return copied;
120
- }
121
-
122
- function validateTasksTemplate(ultraDir) {
123
- const tasksFile = path.join(ultraDir, 'tasks', 'tasks.json');
124
- if (!fs.existsSync(tasksFile)) return;
125
- let data;
126
- try { data = JSON.parse(fs.readFileSync(tasksFile, 'utf8')); }
127
- catch (err) { throw new InitProjectError('IO_ERROR', `tasks.json malformed: ${err.message}`, { cause: err }); }
128
- if (!data || typeof data !== 'object' || !Array.isArray(data.tasks)) {
129
- throw new InitProjectError('IO_ERROR', 'tasks.json template must contain a tasks array');
130
- }
131
- }
132
-
133
- const IGNORED_ADOPTION_ENTRIES = new Set([
134
- '.DS_Store', '.git', '.gitignore', '.ultra', 'node_modules', 'vendor', '.venv', 'venv',
135
- ]);
136
-
137
- const IGNORED_SCAN_DIRS = new Set([
138
- ...IGNORED_ADOPTION_ENTRIES, 'dist', 'build', 'coverage', '.next', '.turbo', 'target',
139
- ]);
140
- const MANIFEST_NAMES = new Set([
141
- 'package.json', 'pyproject.toml', 'requirements.txt', 'Cargo.toml', 'go.mod', 'go.work',
142
- 'pom.xml', 'build.gradle', 'build.gradle.kts', 'Gemfile', 'composer.json',
143
- ]);
144
- const SOURCE_EXTENSIONS = new Set([
145
- '.astro', '.c', '.cc', '.cpp', '.cs', '.css', '.dart', '.ex', '.exs', '.fs', '.fsx',
146
- '.go', '.graphql', '.gql', '.hcl', '.html', '.java', '.js', '.jsx', '.kt', '.kts',
147
- '.less', '.lua', '.mjs', '.move', '.php', '.proto', '.py', '.r', '.R', '.rb', '.rs',
148
- '.sass', '.scala', '.scss', '.sh', '.sol', '.svelte', '.swift', '.tf', '.ts', '.tsx',
149
- '.vue',
150
- ]);
151
- function scanRepositoryFiles(rootDir, { maxFiles = 5000, maxDepth = 8 } = {}) {
152
- const files = [];
153
- function walk(dir, depth) {
154
- if (files.length >= maxFiles || depth > maxDepth) return;
155
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
156
- if (files.length >= maxFiles) break;
157
- if (entry.name === '.DS_Store') continue;
158
- const full = path.join(dir, entry.name);
159
- const rel = path.relative(rootDir, full).split(path.sep).join('/');
160
- if (entry.isDirectory()) {
161
- if (!IGNORED_SCAN_DIRS.has(entry.name) && !entry.name.startsWith('.ultra.backup.')) {
162
- walk(full, depth + 1);
163
- }
164
- } else if (entry.isFile()) files.push(rel);
165
- }
166
- }
167
- walk(rootDir, 0);
168
- return { files: files.sort(), truncated: files.length >= maxFiles };
169
- }
170
-
171
- function expandWorkspacePattern(rootDir, pattern) {
172
- const normalized = String(pattern || '').trim().replace(/^['"]|['"]$/g, '').replace(/\\/g, '/');
173
- if (!normalized) return [];
174
- if (!normalized.includes('*')) {
175
- const candidate = path.join(rootDir, normalized);
176
- return fs.existsSync(candidate) && fs.statSync(candidate).isDirectory() ? [normalized] : [];
177
- }
178
- if (!normalized.endsWith('/*') || normalized.slice(0, -2).includes('*')) return [];
179
- const parentRel = normalized.slice(0, -2);
180
- const parent = path.join(rootDir, parentRel);
181
- if (!fs.existsSync(parent) || !fs.statSync(parent).isDirectory()) return [];
182
- return fs.readdirSync(parent, { withFileTypes: true })
183
- .filter((entry) => entry.isDirectory())
184
- .map((entry) => `${parentRel}/${entry.name}`);
185
- }
186
-
187
- function packageProfile(rootDir) {
188
- const file = path.join(rootDir, 'package.json');
189
- if (!fs.existsSync(file)) return { workspacePatterns: [], verificationCommands: [] };
190
- try {
191
- const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
192
- const rawWorkspaces = Array.isArray(pkg.workspaces)
193
- ? pkg.workspaces
194
- : (Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : []);
195
- const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
196
- const verificationCommands = ['build', 'test', 'lint', 'typecheck']
197
- .filter((name) => typeof scripts[name] === 'string' && scripts[name].trim())
198
- .map((name) => name === 'test' ? 'npm test' : `npm run ${name}`);
199
- return { workspacePatterns: rawWorkspaces, verificationCommands };
200
- } catch {
201
- return { workspacePatterns: [], verificationCommands: [] };
202
- }
203
- }
204
-
205
- const JAVASCRIPT_TECHNOLOGIES = Object.freeze([
206
- { packages: ['next'], name: 'Next.js', capability: 'frontend' },
207
- { packages: ['react'], name: 'React', capability: 'frontend' },
208
- { packages: ['vue', 'nuxt'], name: 'Vue', capability: 'frontend' },
209
- { packages: ['svelte', '@sveltejs/kit'], name: 'Svelte', capability: 'frontend' },
210
- { packages: ['@angular/core'], name: 'Angular', capability: 'frontend' },
211
- { packages: ['express'], name: 'Express', capability: 'backend' },
212
- { packages: ['fastify'], name: 'Fastify', capability: 'backend' },
213
- { packages: ['@nestjs/core'], name: 'NestJS', capability: 'backend' },
214
- { packages: ['hono'], name: 'Hono', capability: 'backend' },
215
- { packages: ['koa'], name: 'Koa', capability: 'backend' },
216
- { packages: ['commander', 'yargs', 'oclif'], name: 'Node CLI', capability: 'cli' },
217
- { packages: ['@prisma/client', 'prisma'], name: 'Prisma' },
218
- { packages: ['typescript'], name: 'TypeScript' },
219
- { packages: ['vitest'], name: 'Vitest' },
220
- { packages: ['jest'], name: 'Jest' },
221
- { packages: ['playwright', '@playwright/test'], name: 'Playwright' },
222
- ]);
223
-
224
- function readTextBounded(file, maxBytes = 256 * 1024) {
225
- try {
226
- const stat = fs.statSync(file);
227
- if (!stat.isFile() || stat.size > maxBytes) return '';
228
- return fs.readFileSync(file, 'utf8');
229
- } catch {
230
- return '';
231
- }
232
- }
233
-
234
- function detectTechnologyProfile(rootDir, files) {
235
- const technologies = new Set();
236
- const capabilities = new Set();
237
- const packageFiles = files.filter((file) => path.basename(file) === 'package.json').slice(0, 100);
238
- for (const relative of packageFiles) {
239
- let pkg;
240
- try { pkg = JSON.parse(readTextBounded(path.join(rootDir, relative))); }
241
- catch { continue; }
242
- technologies.add('Node.js');
243
- const dependencies = new Set([
244
- ...Object.keys(pkg.dependencies || {}),
245
- ...Object.keys(pkg.devDependencies || {}),
246
- ...Object.keys(pkg.peerDependencies || {}),
247
- ]);
248
- if (pkg.bin && (typeof pkg.bin === 'string' || typeof pkg.bin === 'object')) {
249
- capabilities.add('cli');
250
- }
251
- for (const technology of JAVASCRIPT_TECHNOLOGIES) {
252
- if (!technology.packages.some((name) => dependencies.has(name))) continue;
253
- technologies.add(technology.name);
254
- if (technology.capability) capabilities.add(technology.capability);
255
- }
256
- }
257
-
258
- if (files.some((file) => file === 'tsconfig.json' || /\.(ts|tsx)$/.test(file))) {
259
- technologies.add('TypeScript');
260
- }
261
- if (files.some((file) => /(^|\/)index\.html$/.test(file))) {
262
- technologies.add('HTML');
263
- capabilities.add('frontend');
264
- }
265
- if (files.some((file) => /\.(css|scss|sass|less)$/.test(file))) technologies.add('CSS');
266
-
267
- const pythonFiles = files.filter((file) => (
268
- ['pyproject.toml', 'requirements.txt'].includes(path.basename(file)) || file.endsWith('.py')
269
- ));
270
- if (pythonFiles.length > 0) {
271
- technologies.add('Python');
272
- const declarations = pythonFiles
273
- .filter((file) => ['pyproject.toml', 'requirements.txt'].includes(path.basename(file)))
274
- .map((file) => readTextBounded(path.join(rootDir, file)).toLowerCase())
275
- .join('\n');
276
- for (const [needle, label, capability] of [
277
- ['fastapi', 'FastAPI', 'backend'], ['django', 'Django', 'backend'],
278
- ['flask', 'Flask', 'backend'], ['typer', 'Typer', 'cli'], ['click', 'Click', 'cli'],
279
- ['pytest', 'Pytest', null],
280
- ]) {
281
- if (!declarations.includes(needle)) continue;
282
- technologies.add(label);
283
- if (capability) capabilities.add(capability);
284
- }
285
- }
286
- if (files.some((file) => ['go.mod', 'go.work'].includes(path.basename(file)) || file.endsWith('.go'))) {
287
- technologies.add('Go');
288
- }
289
- if (files.some((file) => path.basename(file) === 'Cargo.toml' || file.endsWith('.rs'))) {
290
- technologies.add('Rust');
291
- const cargo = files.filter((file) => path.basename(file) === 'Cargo.toml')
292
- .map((file) => readTextBounded(path.join(rootDir, file)).toLowerCase()).join('\n');
293
- if (cargo.includes('clap')) {
294
- technologies.add('Clap');
295
- capabilities.add('cli');
296
- }
297
- }
298
- if (files.some((file) => ['pom.xml', 'build.gradle', 'build.gradle.kts'].includes(path.basename(file)))) {
299
- technologies.add('JVM');
300
- }
301
- if (files.some((file) => path.basename(file) === 'Gemfile' || file.endsWith('.rb'))) {
302
- technologies.add('Ruby');
303
- }
304
- if (files.some((file) => path.basename(file) === 'composer.json' || file.endsWith('.php'))) {
305
- technologies.add('PHP');
306
- }
307
-
308
- let projectType = 'other';
309
- if (capabilities.has('frontend') && capabilities.has('backend')) projectType = 'fullstack';
310
- else if (capabilities.has('frontend')) projectType = 'web';
311
- else if (capabilities.has('backend')) projectType = 'api';
312
- else if (capabilities.has('cli')) projectType = 'cli';
313
- return {
314
- detected_project_type: projectType,
315
- detected_stack: technologies.size > 0 ? [...technologies].join(', ') : null,
316
- technology_signals: [...technologies],
317
- capability_signals: [...capabilities].sort(),
318
- };
319
- }
320
-
321
- function classifyRepository(absTarget) {
322
- const { files, truncated } = scanRepositoryFiles(absTarget);
323
- const packageData = packageProfile(absTarget);
324
- const manifestSignals = files.filter((file) => MANIFEST_NAMES.has(path.basename(file))).slice(0, 100);
325
- const testSignals = files.filter((file) => {
326
- const lower = file.toLowerCase();
327
- return /(^|\/)(test|tests|__tests__|spec)(\/|$)/.test(lower)
328
- || /\.(test|spec)\.[^.]+$/.test(lower);
329
- }).slice(0, 100);
330
- const deploymentSignals = files.filter((file) => {
331
- const lower = file.toLowerCase();
332
- return /(^|\/)(dockerfile|railway\.json|railway\.toml|vercel\.json|fly\.toml)$/.test(lower)
333
- || lower.startsWith('.github/workflows/')
334
- || /(^|\/)docker-compose[^/]*\.ya?ml$/.test(lower);
335
- }).slice(0, 100);
336
- const databaseSignals = files.filter((file) => {
337
- const lower = file.toLowerCase();
338
- return /(^|\/)(migrations?|prisma|schema)(\/|$)/.test(lower)
339
- || /(^|\/)schema\.(sql|prisma)$/.test(lower);
340
- }).slice(0, 100);
341
- const sourceSignals = files.filter((file) => {
342
- const lower = file.toLowerCase();
343
- if (testSignals.includes(file) || /(^|\/)[^/]+\.config\.(c|m)?(j|t)s$/.test(lower)) return false;
344
- const ext = path.extname(file);
345
- if (!SOURCE_EXTENSIONS.has(ext)) return false;
346
- return true;
347
- }).slice(0, 200);
348
- const workspaceRoots = [...new Set(packageData.workspacePatterns
349
- .flatMap((pattern) => expandWorkspacePattern(absTarget, pattern)))]
350
- .sort();
351
- const workspaceMarkers = [];
352
- if (packageData.workspacePatterns.length > 0) workspaceMarkers.push('package.json#workspaces');
353
- for (const marker of ['pnpm-workspace.yaml', 'lerna.json', 'nx.json', 'turbo.json', 'go.work']) {
354
- if (files.includes(marker)) workspaceMarkers.push(marker);
355
- }
356
- if (workspaceRoots.length === 0 && files.some((file) => /^packages\/[^/]+\/package\.json$/.test(file))) {
357
- workspaceRoots.push(...files
358
- .filter((file) => /^packages\/[^/]+\/package\.json$/.test(file))
359
- .map((file) => path.posix.dirname(file)));
360
- workspaceRoots.sort();
361
- }
362
- const reasons = [];
363
- if (sourceSignals.length > 0) reasons.push('SOURCE_PRESENT');
364
- if (testSignals.length > 0) reasons.push('TESTS_PRESENT');
365
- if (deploymentSignals.length > 0) reasons.push('DEPLOYMENT_PRESENT');
366
- if (databaseSignals.length > 0) reasons.push('PERSISTED_STATE_PRESENT');
367
- if (workspaceMarkers.length > 0 || workspaceRoots.length > 0) reasons.push('MONOREPO_PRESENT');
368
- if (truncated) reasons.push('SCAN_TRUNCATED');
369
- if (reasons.length === 0) reasons.push('SKELETON_ONLY');
370
- const mode = sourceSignals.length > 0 || testSignals.length > 0
371
- || deploymentSignals.length > 0 || databaseSignals.length > 0 || truncated
372
- ? 'brownfield' : 'greenfield';
373
- const technologyProfile = detectTechnologyProfile(absTarget, files);
374
- return {
375
- mode,
376
- ...technologyProfile,
377
- reasons,
378
- repository_kind: workspaceMarkers.length > 0 || workspaceRoots.length > 0 ? 'monorepo' : 'single',
379
- workspace_markers: workspaceMarkers,
380
- workspace_roots: [...new Set(workspaceRoots)],
381
- manifest_signals: manifestSignals,
382
- source_signals: sourceSignals,
383
- test_signals: testSignals,
384
- deployment_signals: deploymentSignals,
385
- database_signals: databaseSignals,
386
- verification_commands: packageData.verificationCommands,
387
- scan_truncated: truncated,
388
- };
389
- }
390
-
391
- function detectProjectMode(absTarget, requested = 'auto') {
392
- if (!['auto', 'greenfield', 'brownfield'].includes(requested)) {
393
- throw new InitProjectError('VALIDATION_ERROR', `unsupported initialization mode: ${requested}`);
394
- }
395
- if (requested !== 'auto') return requested;
396
- return classifyRepository(absTarget).mode;
397
- }
398
-
399
- function rollbackInitialization(ultraDir, backupPath) {
400
- const errors = [];
401
- try {
402
- if (fs.existsSync(ultraDir)) fs.rmSync(ultraDir, { recursive: true, force: true });
403
- } catch (error) {
404
- errors.push(`partial cleanup failed: ${error.message}`);
405
- }
406
- if (backupPath) {
407
- try { fs.renameSync(backupPath, ultraDir); }
408
- catch (error) { errors.push(`backup restore failed: ${error.message}`); }
409
- }
410
- return errors;
411
- }
412
-
413
- function createResumeSnapshot(ultraDir, stateDbPath) {
414
- const token = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
415
- const runtimeDir = path.dirname(stateDbPath);
416
- const statePath = path.join(runtimeDir, `.resume-state-${token}.db`);
417
- const projectionDir = path.join(runtimeDir, `.resume-projections-${token}`);
418
- const projectionTargets = [
419
- path.join('tasks', 'tasks.json'),
420
- path.join('tasks', 'contexts'),
421
- path.join('.runtime', 'projections'),
422
- path.join('.runtime', 'backups', 'task-ledger'),
423
- ];
424
- let db;
425
- try {
426
- db = new Database(stateDbPath, { fileMustExist: true });
427
- db.prepare('VACUUM INTO ?').run(statePath);
428
- db.close();
429
- db = null;
430
- const projections = projectionTargets.map((relative) => {
431
- const source = path.join(ultraDir, relative);
432
- const existed = fs.existsSync(source);
433
- if (existed) {
434
- const destination = path.join(projectionDir, relative);
435
- fs.mkdirSync(path.dirname(destination), { recursive: true });
436
- fs.cpSync(source, destination, { recursive: true });
437
- }
438
- return { relative, existed };
439
- });
440
- return { statePath, projectionDir, projections, stateDbPath, ultraDir };
441
- } catch (error) {
442
- if (db?.open) db.close();
443
- for (const target of [statePath, projectionDir]) {
444
- try { fs.rmSync(target, { recursive: true, force: true }); } catch { /* best effort */ }
445
- }
446
- throw error;
447
- }
448
- }
449
-
450
- function discardResumeSnapshot(snapshot) {
451
- if (!snapshot) return [];
452
- const errors = [];
453
- for (const target of [snapshot.statePath, snapshot.projectionDir]) {
454
- try { fs.rmSync(target, { recursive: true, force: true }); }
455
- catch (error) { errors.push(`resume snapshot cleanup failed for ${target}: ${error.message}`); }
456
- }
457
- return errors;
458
- }
459
-
460
- function restoreResumeSnapshot(snapshot, {
461
- renameSync = fs.renameSync,
462
- } = {}) {
463
- if (!snapshot) return [];
464
- const errors = [];
465
- const runtimeDir = path.dirname(snapshot.stateDbPath);
466
- const liveStage = path.join(
467
- runtimeDir,
468
- `.resume-live-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
469
- );
470
- const movedLive = [];
471
- try {
472
- fs.mkdirSync(liveStage, { recursive: false });
473
- for (const suffix of ['', '-wal', '-shm']) {
474
- const live = `${snapshot.stateDbPath}${suffix}`;
475
- if (!fs.existsSync(live)) continue;
476
- const staged = path.join(liveStage, `state.db${suffix}`);
477
- renameSync(live, staged);
478
- movedLive.push({ live, staged });
479
- }
480
- renameSync(snapshot.statePath, snapshot.stateDbPath);
481
- } catch (error) {
482
- errors.push(`state.db restore failed: ${error.message}`);
483
- for (const item of [...movedLive].reverse()) {
484
- try {
485
- if (fs.existsSync(item.live)) {
486
- errors.push(`live state rollback target already exists: ${item.live}`);
487
- continue;
488
- }
489
- renameSync(item.staged, item.live);
490
- } catch (rollbackError) {
491
- errors.push(`live state rollback failed for ${item.live}: ${rollbackError.message}`);
492
- }
493
- }
494
- // The snapshot and staged live image are recovery evidence. Never destroy
495
- // either when publication or rollback is incomplete.
496
- try {
497
- if (fs.existsSync(liveStage) && fs.readdirSync(liveStage).length === 0) {
498
- fs.rmdirSync(liveStage);
499
- }
500
- } catch (cleanupError) {
501
- errors.push(`live state staging cleanup failed: ${cleanupError.message}`);
502
- }
503
- return errors;
504
- }
505
- try {
506
- fs.rmSync(liveStage, { recursive: true, force: true });
507
- } catch (error) {
508
- errors.push(`prior live state cleanup failed: ${error.message}`);
509
- }
510
- for (const item of snapshot.projections) {
511
- const target = path.join(snapshot.ultraDir, item.relative);
512
- try {
513
- fs.rmSync(target, { recursive: true, force: true });
514
- if (item.existed) {
515
- const source = path.join(snapshot.projectionDir, item.relative);
516
- fs.mkdirSync(path.dirname(target), { recursive: true });
517
- fs.cpSync(source, target, { recursive: true });
518
- }
519
- } catch (error) {
520
- errors.push(`projection restore failed for ${item.relative}: ${error.message}`);
521
- }
522
- }
523
- if (errors.length === 0) {
524
- errors.push(...discardResumeSnapshot(snapshot));
525
- }
526
- return errors;
527
- }
528
-
529
- function projectionMigrationError(projection) {
530
- const targets = { '4.4': '4.5', '4.5': EXPECTED_VERSION };
531
- const target = targets[projection.version];
532
- if (target) {
533
- return new InitProjectError(
534
- 'LEGACY_STATE_MIGRATION_REQUIRED',
535
- `state.db has no tasks but ${projection.path} contains ${projection.taskCount} v${projection.version} tasks; `
536
- + `run ultra-tools migrate --from=${projection.version} --to=${target} --source-dir <project-root> before resuming initialization`,
537
- { details: { projection_path: projection.path, projection_version: projection.version, legacy_task_count: projection.taskCount } },
538
- );
539
- }
540
- return new InitProjectError(
541
- 'STATE_AUTHORITY_CONFLICT',
542
- `state.db has no tasks but ${projection.path} contains ${projection.taskCount} tasks with unsupported projection version ${projection.version}`,
543
- { details: { projection_path: projection.path, projection_version: projection.version, legacy_task_count: projection.taskCount } },
544
- );
545
- }
546
-
547
- function ensureInitializationCheckpoint(db, baseline) {
548
- let initCheckpoint = stageCheckpoints.currentCheckpoint(
549
- db,
550
- 'init',
551
- { baseline_id: baseline.id },
552
- { includeDraft: false },
553
- );
554
- if (!initCheckpoint) {
555
- const draft = stageCheckpoints.saveDraft(db, {
556
- stage: 'init',
557
- scope: { baseline_id: baseline.id },
558
- payload: {
559
- project_name: baseline.project_name,
560
- repository_revision: baseline.repository_revision,
561
- scope: baseline.scope,
562
- },
563
- evidence: [{
564
- kind: 'authority',
565
- ref: '.ultra/.runtime/state.db',
566
- summary: 'State authority, scaffold, projection, and repository classification were verified.',
567
- }],
568
- diagnostics: [],
569
- idempotency_key: `init:${baseline.id}:draft`,
570
- });
571
- initCheckpoint = stageCheckpoints.acceptDraft(db, {
572
- id: draft.id,
573
- idempotency_key: `init:${baseline.id}:accept`,
574
- });
575
- }
576
- let researchCheckpoint = stageCheckpoints.currentCheckpoint(
577
- db,
578
- 'research',
579
- { baseline_id: baseline.id },
580
- { includeDraft: false },
581
- );
582
- const legacyResearch = db.prepare(
583
- `SELECT id, mode, status FROM workflow_runs
584
- WHERE kind = 'research' AND baseline_id = ?
585
- ORDER BY started_at DESC, rowid DESC LIMIT 1`,
586
- ).get(baseline.id);
587
- if (!researchCheckpoint && legacyResearch?.status === 'completed') {
588
- const draft = stageCheckpoints.saveDraft(db, {
589
- stage: 'research',
590
- scope: { baseline_id: baseline.id },
591
- payload: {
592
- migration_source: 'workflow_runs',
593
- legacy_run_id: legacyResearch.id,
594
- mode: legacyResearch.mode,
595
- },
596
- evidence: [{
597
- kind: 'legacy_workflow',
598
- ref: legacyResearch.id,
599
- summary: 'Completed legacy research provenance migrated during project resume.',
600
- }],
601
- diagnostics: [],
602
- idempotency_key: `research:${baseline.id}:legacy:${legacyResearch.id}:draft`,
603
- });
604
- researchCheckpoint = stageCheckpoints.acceptDraft(db, {
605
- id: draft.id,
606
- idempotency_key: `research:${baseline.id}:legacy:${legacyResearch.id}:accept`,
607
- });
608
- }
609
- const compatibilityOnly = baseline.mode === 'migrated';
610
- const provenanceComplete = !compatibilityOnly
611
- && baseline.status === 'ready'
612
- && initCheckpoint.status === 'accepted'
613
- && researchCheckpoint?.status === 'accepted';
614
- const readyWithoutProvenance = baseline.status === 'ready' && !provenanceComplete;
615
- const allowedTransitions = provenanceComplete
616
- ? ['ultra-change', 'ultra-research', 'ultra-status', 'ultra-doctor']
617
- : (readyWithoutProvenance
618
- ? ['ultra-doctor', 'ultra-research', 'ultra-status']
619
- : ['ultra-research', 'ultra-status', 'ultra-doctor']);
620
- return {
621
- init: {
622
- id: initCheckpoint.id,
623
- status: initCheckpoint.status,
624
- revision: initCheckpoint.revision,
625
- digest: initCheckpoint.digest,
626
- },
627
- research: researchCheckpoint ? {
628
- id: researchCheckpoint.id,
629
- status: researchCheckpoint.status,
630
- revision: researchCheckpoint.revision,
631
- digest: researchCheckpoint.digest,
632
- mode: researchCheckpoint.payload?.mode || null,
633
- } : null,
634
- provenance_status: compatibilityOnly
635
- ? 'compatibility_only'
636
- : (provenanceComplete ? 'complete' : (readyWithoutProvenance ? 'incomplete' : 'pending_research')),
637
- allowed_transitions: allowedTransitions,
638
- required_transition: readyWithoutProvenance
639
- ? 'ultra-doctor'
640
- : (compatibilityOnly ? 'ultra-research' : null),
641
- };
642
- }
643
-
644
- function resumeProject({
645
- absTarget, ultraDir, source, project_name, project_type, stack, resolvedMode,
646
- scope, repositoryProfile, projection, git_mode,
647
- }) {
648
- let locatedStatePath;
649
- try {
650
- locatedStatePath = runtimePaths.locateStateDb(absTarget, { env: {} });
651
- } catch (error) {
652
- throw new InitProjectError(
653
- error.code || 'RUNTIME_STATE_CONFLICT',
654
- error.message,
655
- { cause: error, details: error.details },
656
- );
657
- }
658
- const stateExisted = fs.existsSync(locatedStatePath);
659
- let runtimeLayout;
660
- let gitSetup;
661
- const stateDbPath = runtimePaths.pathsFor(absTarget).stateDbPath;
662
- let initialized;
663
- let copiedFiles = [];
664
- let baselineResult;
665
- let checkpointResult;
666
- let teamCheckpoint;
667
- let resumeSnapshot;
668
- const completeResume = () => {
669
- try {
670
- if (stateExisted) {
671
- // Snapshot the exact entry authority before initStateDb can migrate its
672
- // schema. A failed snapshot therefore cannot partially upgrade state.
673
- resumeSnapshot = createResumeSnapshot(ultraDir, stateDbPath);
674
- }
675
- initialized = initStateDb(stateDbPath);
676
- const { db } = initialized;
677
- if (projection && (
678
- projection.taskCount > 0 || projection.kind === taskLedger.LEDGER_KIND
679
- )) {
680
- teamCheckpoint = assertStateAuthority(db, absTarget, { importTeamLedger: true });
681
- }
682
- copiedFiles = copyMissingTemplate(source, ultraDir);
683
- validateTasksTemplate(ultraDir);
684
- repositoryProfile.git = gitSetup.result;
685
- baselineResult = baselines.readBaseline(db);
686
- if (!baselineResult) {
687
- baselineResult = baselines.startBaseline(db, {
688
- id: 'project-baseline', project_name, project_type, stack,
689
- mode: resolvedMode, repository_revision: baselines.gitHead(absTarget),
690
- scope: scope === undefined ? ['.'] : scope,
691
- classification: repositoryProfile,
692
- }, { rootDir: absTarget });
693
- } else {
694
- baselineResult = baselines.refreshInProgressBaseline(db, {
695
- id: baselineResult.id,
696
- project_name,
697
- project_type,
698
- stack,
699
- classification: repositoryProfile,
700
- }, { rootDir: absTarget });
701
- }
702
- checkpointResult = ensureInitializationCheckpoint(db, baselineResult);
703
- if (projection?.kind === 'legacy-task-projection') {
704
- const publication = taskLedger.publishTaskLedger(db, {
705
- rootDir: absTarget,
706
- reason: 'legacy_projection_upgraded',
707
- });
708
- teamCheckpoint = {
709
- generation: publication.ledger.generation,
710
- state_digest: publication.ledger.state_digest,
711
- imported: 0,
712
- deleted: 0,
713
- imported_changes: 0,
714
- imported_baseline: false,
715
- requires_plan_revalidation: false,
716
- requires_baseline_revalidation: false,
717
- already_current: true,
718
- published: publication.changed,
719
- migrated_legacy_projection: Boolean(publication.migrated_legacy_projection),
720
- legacy_backup_path: publication.legacy_backup_path || null,
721
- };
722
- }
723
- ops.appendEvent(db, {
724
- type: 'project_resumed',
725
- payload: {
726
- project_name, mode: baselineResult.mode, baseline_id: baselineResult.id,
727
- installed_missing_files: copiedFiles.length, git: gitSetup.result,
728
- },
729
- });
730
- const job = runtimeState.enqueueProjection(db, { tool_name: 'task.init_project' });
731
- const projectionResult = runtimeState.processProjectionJobs(
732
- db, { rootDir: absTarget, limit: 1 },
733
- );
734
- const projected = projectionResult.jobs.find((item) => item.id === job.id);
735
- if (!projected || projected.status !== 'completed') {
736
- throw new Error(
737
- `resume projection did not complete: ${projected?.error || 'projection job missing'}`,
738
- );
739
- }
740
- } catch (error) {
741
- if (initialized?.db?.open) initialized.db.close();
742
- const stateRollbackErrors = stateExisted
743
- ? restoreResumeSnapshot(resumeSnapshot)
744
- : [];
745
- const gitRollbackErrors = gitBootstrap.rollbackGitBootstrap(
746
- absTarget, gitSetup?.internal,
747
- );
748
- for (const rel of copiedFiles.reverse()) {
749
- try { fs.rmSync(path.join(ultraDir, rel), { force: true }); } catch { /* best effort */ }
750
- }
751
- if (!stateExisted) {
752
- for (const suffix of ['', '-wal', '-shm']) {
753
- try { fs.rmSync(`${stateDbPath}${suffix}`, { force: true }); } catch { /* best effort */ }
754
- }
755
- }
756
- const rollbackErrors = [...stateRollbackErrors, ...gitRollbackErrors];
757
- if (error instanceof taskLedger.TaskLedgerError
758
- || (error.code && [
759
- 'LEGACY_STATE_MIGRATION_REQUIRED',
760
- 'STATE_AUTHORITY_CONFLICT',
761
- 'STATE_PROJECTION_INVALID',
762
- ].includes(error.code))) {
763
- throw new InitProjectError(
764
- error.code,
765
- error.message,
766
- { cause: error, details: error.details },
767
- );
768
- }
769
- if (error instanceof gitBootstrap.GitBootstrapError) {
770
- throw new InitProjectError(
771
- error.code,
772
- `${error.message}${rollbackErrors.length ? `; ${rollbackErrors.join('; ')}` : ''}`,
773
- { cause: error },
774
- );
775
- }
776
- throw new InitProjectError(
777
- 'IO_ERROR',
778
- `project resume failed: ${error.message}; `
779
- + `${rollbackErrors.length ? 'resume rollback was incomplete' : 'existing authority and projections were restored'}`
780
- + `${rollbackErrors.length ? `; ${rollbackErrors.join('; ')}` : ''}`,
781
- { retriable: true, cause: error },
782
- );
783
- }
784
- };
785
-
786
- try {
787
- runtimeLayout = runtimePaths.ensureRuntimeState(absTarget, {
788
- admitStorageBoundary: () => {
789
- gitSetup = gitBootstrap.bootstrapGit(absTarget, { mode: git_mode });
790
- completeResume();
791
- return gitSetup;
792
- },
793
- });
794
- } catch (error) {
795
- if (error instanceof InitProjectError) throw error;
796
- throw new InitProjectError(
797
- error.code || 'RUNTIME_STATE_MIGRATION_FAILED',
798
- error.message,
799
- { cause: error, details: error.details },
800
- );
801
- }
802
- initialized.db.close();
803
- discardResumeSnapshot(resumeSnapshot);
804
- const result = {
805
- created_path: ultraDir,
806
- state_db_path: stateDbPath,
807
- status: 'resumed',
808
- mode: baselineResult.mode,
809
- baseline: {
810
- id: baselineResult.id,
811
- mode: baselineResult.mode,
812
- status: baselineResult.status,
813
- project_name: baselineResult.project_name,
814
- project_type: baselineResult.project_type,
815
- stack: baselineResult.stack,
816
- repository_revision: baselineResult.repository_revision,
817
- repository_branch: baselineResult.repository_branch,
818
- worktree_state: baselineResult.worktree_state,
819
- scope: baselineResult.scope,
820
- },
821
- copied_files: copiedFiles,
822
- repository_profile: repositoryProfile,
823
- git: gitSetup.result,
824
- checkpoint: checkpointResult,
825
- };
826
- if (teamCheckpoint) {
827
- result.team_checkpoint = {
828
- generation: teamCheckpoint.generation,
829
- state_digest: teamCheckpoint.state_digest,
830
- imported_tasks: teamCheckpoint.imported,
831
- deleted_tasks: teamCheckpoint.deleted,
832
- imported_changes: teamCheckpoint.imported_changes,
833
- imported_baseline: teamCheckpoint.imported_baseline,
834
- requires_plan_revalidation: teamCheckpoint.requires_plan_revalidation,
835
- requires_baseline_revalidation: teamCheckpoint.requires_baseline_revalidation,
836
- already_current: teamCheckpoint.already_current,
837
- published: Boolean(teamCheckpoint.published),
838
- migrated_legacy_projection: Boolean(teamCheckpoint.migrated_legacy_projection),
839
- legacy_backup_path: teamCheckpoint.legacy_backup_path || undefined,
840
- };
841
- }
842
- if (initialized.backup_path) result.migration_backup_path = initialized.backup_path;
843
- if (runtimeLayout.backupPath) {
844
- result.runtime_migration_backup_path = runtimeLayout.backupPath;
845
- }
846
- return result;
847
- }
848
-
849
- function initProject({
850
- target_dir,
851
- project_name,
852
- project_type,
853
- stack,
854
- mode = 'auto',
855
- scope,
856
- resume = false,
857
- overwrite = false,
858
- git_mode = 'auto',
859
- source_template,
860
- } = {}) {
861
- if (typeof project_name !== 'string' || project_name.length === 0) {
862
- throw new InitProjectError('VALIDATION_ERROR', 'project_name must be non-empty');
863
- }
864
- if (resume && overwrite) {
865
- throw new InitProjectError('VALIDATION_ERROR', 'resume and overwrite are mutually exclusive');
866
- }
867
- if (!gitBootstrap.GIT_MODES.has(git_mode)) {
868
- throw new InitProjectError(
869
- 'VALIDATION_ERROR',
870
- `git_mode must be one of: ${[...gitBootstrap.GIT_MODES].join(', ')}`,
871
- );
872
- }
873
-
874
- const absTarget = resolveTargetDir(target_dir);
875
- ensureTargetDir(absTarget);
876
- const repositoryProfile = classifyRepository(absTarget);
877
- const resolvedMode = mode === 'auto' ? repositoryProfile.mode : detectProjectMode(absTarget, mode);
878
- const resolvedProjectType = project_type || repositoryProfile.detected_project_type;
879
- const resolvedStack = stack || repositoryProfile.detected_stack || undefined;
880
- repositoryProfile.detected_mode = repositoryProfile.mode;
881
- repositoryProfile.mode = resolvedMode;
882
- repositoryProfile.selected_project_type = resolvedProjectType;
883
- repositoryProfile.selected_stack = resolvedStack || null;
884
- const source = ensureTemplate(source_template);
885
- const ultraDir = path.join(absTarget, '.ultra');
886
-
887
- if (fs.existsSync(ultraDir) && resume) {
888
- let projection;
889
- try { projection = inspectProjection(absTarget); }
890
- catch (error) {
891
- throw new InitProjectError(error.code || 'STATE_PROJECTION_INVALID', error.message, {
892
- cause: error, details: error.details,
893
- });
894
- }
895
- let locatedStatePath;
896
- try {
897
- locatedStatePath = runtimePaths.locateStateDb(absTarget, { env: {} });
898
- } catch (error) {
899
- throw new InitProjectError(
900
- error.code || 'RUNTIME_STATE_CONFLICT',
901
- error.message,
902
- { cause: error, details: error.details },
903
- );
904
- }
905
- if (projection && projection.taskCount > 0
906
- && projection.kind !== taskLedger.LEDGER_KIND
907
- && !fs.existsSync(locatedStatePath)) {
908
- throw projectionMigrationError(projection);
909
- }
910
- return resumeProject({
911
- absTarget, ultraDir, source, project_name, project_type: resolvedProjectType,
912
- stack: resolvedStack, resolvedMode,
913
- scope, repositoryProfile, projection, git_mode,
914
- });
915
- }
916
-
917
- let status = 'created';
918
- let backupPath;
919
- if (fs.existsSync(ultraDir)) {
920
- if (!overwrite) {
921
- throw new InitProjectError('ULTRA_DIR_EXISTS', `.ultra/ already exists at ${ultraDir}; pass overwrite=true to back up and recreate`);
922
- }
923
- backupPath = path.join(absTarget, `.ultra.backup.${timestampSlug()}`);
924
- try { fs.renameSync(ultraDir, backupPath); }
925
- catch (err) { throw new InitProjectError('IO_ERROR', `backup rename failed: ${err.message}`, { retriable: true, cause: err }); }
926
- status = 'overwritten';
927
- }
928
-
929
- let copiedFiles;
930
- let baselineResult;
931
- let checkpointResult;
932
- let gitSetup;
933
- try {
934
- fs.mkdirSync(ultraDir, { recursive: true });
935
- copiedFiles = copyTemplate(source, ultraDir);
936
- validateTasksTemplate(ultraDir);
937
- gitSetup = gitBootstrap.bootstrapGit(absTarget, { mode: git_mode });
938
- repositoryProfile.git = gitSetup.result;
939
- const runtimeLayout = runtimePaths.ensureRuntimeState(absTarget);
940
- const stateDbPath = runtimeLayout.stateDbPath;
941
- const { db } = initStateDb(stateDbPath);
942
- try {
943
- baselineResult = baselines.startBaseline(db, {
944
- id: 'project-baseline', project_name, project_type: resolvedProjectType,
945
- stack: resolvedStack,
946
- mode: resolvedMode, repository_revision: baselines.gitHead(absTarget),
947
- scope: scope === undefined ? ['.'] : scope,
948
- classification: repositoryProfile,
949
- }, { rootDir: absTarget });
950
- checkpointResult = ensureInitializationCheckpoint(db, baselineResult);
951
- ops.appendEvent(db, {
952
- type: 'project_initialized',
953
- payload: {
954
- project_name, mode: resolvedMode, baseline_id: baselineResult.id,
955
- git: gitSetup.result,
956
- },
957
- });
958
- const job = runtimeState.enqueueProjection(db, { tool_name: 'task.init_project' });
959
- const projection = runtimeState.processProjectionJobs(db, { rootDir: absTarget, limit: 1 });
960
- const projected = projection.jobs.find((item) => item.id === job.id);
961
- if (!projected || projected.status !== 'completed') {
962
- throw new Error(projected?.error || 'initial projection did not complete');
963
- }
964
- } finally {
965
- db.close();
966
- }
967
- } catch (err) {
968
- const rollbackErrors = rollbackInitialization(ultraDir, backupPath);
969
- rollbackErrors.push(...gitBootstrap.rollbackGitBootstrap(absTarget, gitSetup?.internal));
970
- const rollbackNote = rollbackErrors.length > 0
971
- ? `; rollback incomplete: ${rollbackErrors.join('; ')}`
972
- : '; prior state restored';
973
- if (err instanceof gitBootstrap.GitBootstrapError) {
974
- throw new InitProjectError(
975
- err.code,
976
- `project initialization failed: ${err.message}${rollbackNote}`,
977
- { cause: err },
978
- );
979
- }
980
- throw new InitProjectError(
981
- 'IO_ERROR',
982
- `project initialization failed: ${err.message}${rollbackNote}`,
983
- { retriable: true, cause: err },
984
- );
985
- }
986
-
987
- const stateDbPath = runtimePaths.pathsFor(absTarget).stateDbPath;
988
- const result = {
989
- created_path: ultraDir,
990
- state_db_path: stateDbPath,
991
- status,
992
- mode: resolvedMode,
993
- baseline: {
994
- id: baselineResult.id,
995
- mode: baselineResult.mode,
996
- status: baselineResult.status,
997
- project_name: baselineResult.project_name,
998
- project_type: baselineResult.project_type,
999
- stack: baselineResult.stack,
1000
- repository_revision: baselineResult.repository_revision,
1001
- repository_branch: baselineResult.repository_branch,
1002
- worktree_state: baselineResult.worktree_state,
1003
- scope: baselineResult.scope,
1004
- },
1005
- copied_files: copiedFiles,
1006
- repository_profile: repositoryProfile,
1007
- git: gitSetup.result,
1008
- checkpoint: checkpointResult,
1009
- };
1010
- if (backupPath) result.backup_path = backupPath;
1011
- return result;
1012
- }
1013
-
1014
- module.exports = {
1015
- initProject, InitProjectError, DEFAULT_TEMPLATE, detectProjectMode, classifyRepository,
1016
- _internal: {
1017
- createResumeSnapshot,
1018
- discardResumeSnapshot,
1019
- restoreResumeSnapshot,
1020
- },
1021
- };