ultra-builder-pro-cli 0.25.1 → 0.26.1

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 (243) hide show
  1. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  2. package/.ultra-template/contexts/TEMPLATE.md +94 -0
  3. package/.ultra-template/decisions/.gitkeep +1 -0
  4. package/.ultra-template/evidence/.gitkeep +1 -0
  5. package/.ultra-template/north-star.md +46 -0
  6. package/.ultra-template/research/.gitkeep +1 -0
  7. package/.ultra-template/specs/discovery.md +2 -2
  8. package/.ultra-template/tasks.json +3 -0
  9. package/.ultra-template/test-report.json +27 -0
  10. package/CHANGELOG.md +107 -1
  11. package/README.md +217 -579
  12. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  13. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  14. package/adapters/_shared/file-ops.cjs +76 -2
  15. package/adapters/_shared/host-profile.cjs +97 -0
  16. package/adapters/_shared/plugin-core.cjs +204 -0
  17. package/adapters/_shared/runtime-assets.cjs +31 -96
  18. package/adapters/claude.js +92 -291
  19. package/adapters/codex.js +309 -842
  20. package/adapters/grok.js +143 -1469
  21. package/adapters/kimi.js +124 -1053
  22. package/adapters/opencode.js +230 -742
  23. package/bin/delegate-worker.cjs +328 -0
  24. package/bin/delegate.cjs +463 -0
  25. package/bin/install.js +38 -11
  26. package/docs/ARCHITECTURE.md +125 -216
  27. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  28. package/docs/DECISIONS.md +77 -87
  29. package/docs/PHILOSOPHY.md +208 -0
  30. package/docs/PLUGIN-ISOLATION-CONTRACT.md +65 -39
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +140 -155
  32. package/docs/SKILL-AUTHORING.md +94 -73
  33. package/docs/WORKFLOW-LIFECYCLE.md +97 -189
  34. package/hooks/README.md +19 -37
  35. package/hooks/_common.py +113 -0
  36. package/hooks/adapters/codex.py +9 -16
  37. package/hooks/adapters/grok.py +5 -5
  38. package/hooks/adapters/kimi.py +8 -14
  39. package/hooks/block_dangerous_commands.py +201 -0
  40. package/hooks/compact_context.py +65 -0
  41. package/hooks/mid_workflow_recall.py +25 -0
  42. package/hooks/post_edit_guard.py +105 -0
  43. package/hooks/session_context.py +30 -0
  44. package/package.json +17 -52
  45. package/skills/ultra-change/SKILL.md +115 -50
  46. package/skills/ultra-change/references/change-contract.md +68 -3
  47. package/skills/ultra-delegate/SKILL.md +89 -0
  48. package/skills/ultra-delegate/references/delegation-contract.md +59 -0
  49. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  50. package/skills/ultra-deliver/SKILL.md +98 -42
  51. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  52. package/skills/ultra-dev/SKILL.md +115 -62
  53. package/skills/ultra-dev/references/debugging.md +22 -0
  54. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  55. package/skills/ultra-grilling/SKILL.md +118 -0
  56. package/skills/ultra-grilling/references/reframing.md +72 -0
  57. package/skills/ultra-init/SKILL.md +102 -37
  58. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  59. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  60. package/skills/ultra-plan/SKILL.md +101 -68
  61. package/skills/ultra-research/SKILL.md +68 -48
  62. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  63. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  64. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  65. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  66. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  67. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  68. package/skills/ultra-research/references/10-user-personas.md +3 -2
  69. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  70. package/skills/ultra-research/references/20-user-stories.md +4 -3
  71. package/skills/ultra-research/references/21-features-scope.md +3 -2
  72. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  73. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  74. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  75. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  76. package/skills/ultra-research/references/40-deployment.md +3 -3
  77. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  78. package/skills/ultra-research/references/99-synthesis.md +7 -7
  79. package/skills/ultra-review/SKILL.md +84 -32
  80. package/skills/ultra-review/references/code.md +17 -0
  81. package/skills/ultra-review/references/comments.md +12 -0
  82. package/skills/ultra-review/references/design.md +12 -0
  83. package/skills/ultra-review/references/errors.md +13 -0
  84. package/skills/ultra-review/references/spec.md +13 -0
  85. package/skills/ultra-review/references/tests.md +12 -0
  86. package/skills/ultra-review/references/worker-packet.md +40 -0
  87. package/skills/ultra-review/scripts/review_wait.py +26 -15
  88. package/skills/ultra-status/SKILL.md +61 -36
  89. package/skills/ultra-tdd/SKILL.md +81 -0
  90. package/skills/ultra-tdd/references/templates/README.md +20 -0
  91. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  92. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  93. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  94. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  95. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  96. package/skills/ultra-tdd/references/test-execution.md +18 -0
  97. package/skills/ultra-test/SKILL.md +75 -30
  98. package/skills/ultra-test/references/export-syntax.md +18 -0
  99. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  100. package/skills/ultra-think/SKILL.md +49 -27
  101. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  102. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  103. package/.ultra-template/docs/research/README.md +0 -55
  104. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  105. package/.ultra-template/reports/templates/test-report.json +0 -18
  106. package/.ultra-template/tasks/tasks.json +0 -15
  107. package/.ultra-template/templates/task-context.md +0 -19
  108. package/adapters/_shared/codex-assets.cjs +0 -725
  109. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  110. package/adapters/_shared/interaction-contract.cjs +0 -129
  111. package/adapters/_shared/path-rewrite.cjs +0 -79
  112. package/adapters/_shared/runtime-native.cjs +0 -132
  113. package/adapters/_shared/settings-merge.cjs +0 -76
  114. package/agents/code-reviewer.md +0 -42
  115. package/agents/debugger.md +0 -40
  116. package/agents/review-code.md +0 -49
  117. package/agents/review-comments.md +0 -38
  118. package/agents/review-coordinator.md +0 -56
  119. package/agents/review-design.md +0 -40
  120. package/agents/review-errors.md +0 -43
  121. package/agents/review-spec.md +0 -39
  122. package/agents/review-tests.md +0 -40
  123. package/agents/tdd-runner.md +0 -38
  124. package/bin/orchestrator.js +0 -383
  125. package/commands/ultra-change.md +0 -10
  126. package/commands/ultra-deliver.md +0 -10
  127. package/commands/ultra-dev.md +0 -10
  128. package/commands/ultra-doctor.md +0 -10
  129. package/commands/ultra-init.md +0 -10
  130. package/commands/ultra-plan.md +0 -10
  131. package/commands/ultra-research.md +0 -10
  132. package/commands/ultra-review.md +0 -10
  133. package/commands/ultra-status.md +0 -10
  134. package/commands/ultra-test.md +0 -10
  135. package/commands/ultra-think.md +0 -10
  136. package/docs/AGENT-CONTEXT.md +0 -191
  137. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  138. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  139. package/docs/ROADMAP.md +0 -101
  140. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  141. package/hooks/active_task_context.py +0 -152
  142. package/hooks/context_envelope.py +0 -100
  143. package/hooks/health_check.py +0 -196
  144. package/hooks/pre_stop_check.py +0 -66
  145. package/hooks/runtime_paths.py +0 -396
  146. package/hooks/subagent_tracker.py +0 -88
  147. package/hooks/workflow_checkpoint.py +0 -76
  148. package/hooks/workflow_context.py +0 -48
  149. package/hooks/workflow_resume.py +0 -84
  150. package/mcp-server/hook-context.cjs +0 -38
  151. package/mcp-server/hook-event.cjs +0 -34
  152. package/mcp-server/lib/archive-journal.cjs +0 -1119
  153. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  154. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  155. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  156. package/mcp-server/lib/canonical-json.cjs +0 -19
  157. package/mcp-server/lib/change-authority.cjs +0 -26
  158. package/mcp-server/lib/change-packet.cjs +0 -790
  159. package/mcp-server/lib/change-workflow.cjs +0 -65
  160. package/mcp-server/lib/context-envelope.cjs +0 -581
  161. package/mcp-server/lib/context-paths.cjs +0 -142
  162. package/mcp-server/lib/decision-records.cjs +0 -423
  163. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  164. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  165. package/mcp-server/lib/doctor.cjs +0 -627
  166. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  167. package/mcp-server/lib/init-project.cjs +0 -1021
  168. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  169. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  170. package/mcp-server/lib/managed-file-write.cjs +0 -172
  171. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  172. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  173. package/mcp-server/lib/plan-store.cjs +0 -794
  174. package/mcp-server/lib/prd-parser.cjs +0 -83
  175. package/mcp-server/lib/pricing.cjs +0 -44
  176. package/mcp-server/lib/project-context.cjs +0 -178
  177. package/mcp-server/lib/projector.cjs +0 -386
  178. package/mcp-server/lib/provider-refs.cjs +0 -42
  179. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  180. package/mcp-server/lib/runtime-state.cjs +0 -222
  181. package/mcp-server/lib/safe-project-file.cjs +0 -385
  182. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  183. package/mcp-server/lib/state-authority.cjs +0 -95
  184. package/mcp-server/lib/state-db.cjs +0 -1580
  185. package/mcp-server/lib/state-ops.cjs +0 -1428
  186. package/mcp-server/lib/task-contract.cjs +0 -404
  187. package/mcp-server/lib/task-expander.cjs +0 -105
  188. package/mcp-server/lib/task-ledger.cjs +0 -1964
  189. package/mcp-server/lib/telemetry.cjs +0 -85
  190. package/mcp-server/lib/topo.cjs +0 -131
  191. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  192. package/mcp-server/lib/worker-packet.cjs +0 -358
  193. package/mcp-server/server.cjs +0 -437
  194. package/orchestrator/auto-merge.cjs +0 -128
  195. package/orchestrator/daemon.cjs +0 -264
  196. package/orchestrator/dispatch-rules.cjs +0 -78
  197. package/orchestrator/parallel-orchestrator.cjs +0 -903
  198. package/orchestrator/planner/plan-builder.cjs +0 -125
  199. package/orchestrator/recovery.cjs +0 -188
  200. package/orchestrator/session-close-journal-worker.cjs +0 -5
  201. package/orchestrator/session-close-journal.cjs +0 -1014
  202. package/orchestrator/session-runner.cjs +0 -1153
  203. package/orchestrator/worktree-manager.cjs +0 -282
  204. package/settings.json +0 -7
  205. package/skills/cc-collab/SKILL.md +0 -42
  206. package/skills/code-review-expert/SKILL.md +0 -43
  207. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  208. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  209. package/skills/code-review-expert/references/removal-plan.md +0 -24
  210. package/skills/codex-collab/SKILL.md +0 -49
  211. package/skills/codex-collab/evals/evals.json +0 -35
  212. package/skills/integration-rules/SKILL.md +0 -32
  213. package/skills/security-rules/SKILL.md +0 -38
  214. package/skills/testing-rules/SKILL.md +0 -32
  215. package/skills/ultra-doctor/SKILL.md +0 -41
  216. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  217. package/skills/ultra-verify/SKILL.md +0 -46
  218. package/skills/ultra-verify/evals/evals.json +0 -46
  219. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  220. package/skills/ultra-verify/references/evidence-status.md +0 -21
  221. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  222. package/spec/cli-protocol.md +0 -102
  223. package/spec/mcp-tools.yaml +0 -312
  224. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  225. package/spec/schemas/change-delta.v1.schema.json +0 -153
  226. package/spec/schemas/command-manifest.schema.json +0 -29
  227. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  228. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  229. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  230. package/spec/schemas/mcp-tools.schema.json +0 -136
  231. package/spec/schemas/skill-manifest.schema.json +0 -24
  232. package/spec/schemas/state-db.sql +0 -641
  233. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  234. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  235. package/spec/schemas/test-report.v1.schema.json +0 -225
  236. package/ultra-tools/cli.cjs +0 -129
  237. package/ultra-tools/commands/db.cjs +0 -226
  238. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  239. package/ultra-tools/commands/migrate.cjs +0 -1386
  240. package/ultra-tools/commands/session.cjs +0 -213
  241. package/ultra-tools/commands/status.cjs +0 -559
  242. package/ultra-tools/commands/system.cjs +0 -498
  243. 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
- };