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,83 +0,0 @@
1
- 'use strict';
2
-
3
- // Validate and normalize a task graph derived from a PRD by the current host.
4
- // The MCP runtime is deliberately deterministic: model judgment stays with
5
- // the host that already owns the user interaction, while this module owns the
6
- // machine contract before state.db is mutated.
7
-
8
- const Ajv = require('ajv/dist/2020');
9
- const TASK_LIST_SCHEMA = Object.freeze({
10
- type: 'object',
11
- required: ['tasks'],
12
- properties: {
13
- tasks: {
14
- type: 'array',
15
- minItems: 1,
16
- items: {
17
- type: 'object',
18
- required: ['id', 'title', 'type', 'priority'],
19
- properties: {
20
- id: { type: 'string', pattern: '^[a-zA-Z0-9_\\-]+$' },
21
- title: { type: 'string', minLength: 3, maxLength: 200 },
22
- type: { type: 'string', minLength: 1, maxLength: 80 },
23
- priority: { type: 'string', minLength: 1, maxLength: 80 },
24
- complexity: { type: 'integer', minimum: 1, maximum: 10 },
25
- deps: { type: 'array', items: { type: 'string' } },
26
- files_modified: { type: 'array', items: { type: 'string' } },
27
- },
28
- },
29
- },
30
- },
31
- });
32
-
33
- const ajv = new Ajv({ allErrors: true, strict: false });
34
- const validateTaskList = ajv.compile(TASK_LIST_SCHEMA);
35
-
36
- class PrdParseError extends Error {
37
- constructor(code, message, cause) {
38
- super(message);
39
- this.name = 'PrdParseError';
40
- this.code = code;
41
- if (cause) this.cause = cause;
42
- }
43
- }
44
-
45
- function parsePrd(tasks, { tag, changeId } = {}) {
46
- if (!Array.isArray(tasks) || tasks.length === 0) {
47
- throw new PrdParseError('NO_INPUT', 'at least one host-derived task is required');
48
- }
49
- const input = { tasks };
50
- if (!validateTaskList(input)) {
51
- throw new PrdParseError(
52
- 'PARSE_FAILED',
53
- `task graph failed schema: ${ajv.errorsText(validateTaskList.errors)}`,
54
- );
55
- }
56
-
57
- const ids = new Set();
58
- const normalized = tasks.map((t) => {
59
- if (ids.has(t.id)) {
60
- throw new PrdParseError('PARSE_FAILED', `duplicate task id "${t.id}" in task graph`);
61
- }
62
- ids.add(t.id);
63
- return {
64
- id: t.id,
65
- title: t.title,
66
- type: t.type,
67
- priority: t.priority,
68
- complexity: t.complexity ?? null,
69
- deps: Array.isArray(t.deps) ? t.deps : [],
70
- files_modified: Array.isArray(t.files_modified) ? t.files_modified : [],
71
- tag: tag || null,
72
- change_id: changeId || null,
73
- };
74
- });
75
-
76
- return { tasks: normalized };
77
- }
78
-
79
- module.exports = {
80
- parsePrd,
81
- PrdParseError,
82
- TASK_LIST_SCHEMA,
83
- };
@@ -1,44 +0,0 @@
1
- 'use strict';
2
-
3
- // Phase 6.2 — Runtime × model pricing table (USD per token).
4
- //
5
- // Last updated: 2026-04-17. Public rate cards change — re-check before
6
- // trusting the cost panel for billing. Numbers below are per token (not
7
- // per million), so a 1000-token input at 3e-6 costs $0.003.
8
- //
9
- // Sources (2026-04):
10
- // Claude: https://www.anthropic.com/pricing
11
- // OpenAI: https://platform.openai.com/docs/pricing
12
- //
13
- // Pricing is exact-model only. A runtime label is not a billable model and
14
- // host applications can route the same runtime through different providers.
15
- // Missing or unknown models therefore return null instead of a fabricated
16
- // estimate.
17
-
18
- const PRICING = Object.freeze({
19
- claude: Object.freeze({
20
- 'claude-opus-4-7': { input: 15e-6, output: 75e-6 },
21
- 'claude-sonnet-4-6': { input: 3e-6, output: 15e-6 },
22
- 'claude-haiku-4-5': { input: 0.8e-6, output: 4e-6 },
23
- }),
24
- codex: Object.freeze({
25
- 'gpt-5.4': { input: 2e-6, output: 10e-6 },
26
- }),
27
- opencode: Object.freeze({}),
28
- kimi: Object.freeze({}),
29
- });
30
-
31
- function computeCost(runtime, model, tokens_input, tokens_output) {
32
- const r = PRICING[runtime];
33
- if (!r) return null;
34
- const p = model ? r[model] : null;
35
- if (!p) return null;
36
- const ti = Number(tokens_input) || 0;
37
- const to = Number(tokens_output) || 0;
38
- return ti * p.input + to * p.output;
39
- }
40
-
41
- module.exports = {
42
- PRICING,
43
- computeCost,
44
- };
@@ -1,178 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const Database = require('better-sqlite3');
7
-
8
- const contextEnvelope = require('./context-envelope.cjs');
9
- const { EXPECTED_VERSION, REQUIRED_TABLES } = require('./state-db.cjs');
10
- const runtimePaths = require('./runtime-paths.cjs');
11
-
12
- function diagnosticEnvelope(rootDir, runtime, code, details = {}) {
13
- return {
14
- schema_version: '1.0',
15
- digest: null,
16
- detail: 'summary',
17
- envelope: {
18
- project: {
19
- root: '.',
20
- runtime,
21
- health: 'needs_attention',
22
- },
23
- git: null,
24
- baseline: null,
25
- change: null,
26
- task: null,
27
- decisions: [],
28
- checkpoints: [],
29
- evidence: [],
30
- execution: { stage: 'project', session: null },
31
- team_checkpoint: null,
32
- diagnostics: {
33
- warnings: [],
34
- needs_attention: [{
35
- code,
36
- severity: 'needs_attention',
37
- ...details,
38
- }],
39
- hard_conflicts: [],
40
- },
41
- },
42
- bytes: 0,
43
- root: path.resolve(rootDir),
44
- };
45
- }
46
-
47
- function tableNames(db) {
48
- return new Set(db.prepare(
49
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
50
- ).all().map((row) => row.name));
51
- }
52
-
53
- function latestSchemaVersion(db) {
54
- return db.prepare(
55
- 'SELECT version FROM schema_version ORDER BY applied_at DESC, rowid DESC LIMIT 1',
56
- ).get()?.version || null;
57
- }
58
-
59
- function readProjectContextEnvelope(rootDir, {
60
- runtime = 'hook',
61
- stage = 'project',
62
- scope = {},
63
- } = {}) {
64
- const root = path.resolve(rootDir || process.cwd());
65
- if (!fs.existsSync(path.join(root, '.ultra'))) return null;
66
- const dbPath = runtimePaths.locateStateDb(root);
67
- if (!fs.existsSync(dbPath)) {
68
- return diagnosticEnvelope(root, runtime, 'STATE_DB_MISSING');
69
- }
70
-
71
- let db;
72
- try {
73
- db = new Database(dbPath, { readonly: true, fileMustExist: true });
74
- db.pragma('query_only = ON');
75
- db.pragma('busy_timeout = 1000');
76
- const tables = tableNames(db);
77
- if (!tables.has('schema_version')) {
78
- return diagnosticEnvelope(root, runtime, 'STATE_SCHEMA_MIGRATION_REQUIRED', {
79
- version: null,
80
- });
81
- }
82
- const version = latestSchemaVersion(db);
83
- if (version !== EXPECTED_VERSION) {
84
- return diagnosticEnvelope(root, runtime, 'STATE_SCHEMA_MIGRATION_REQUIRED', {
85
- version,
86
- expected: EXPECTED_VERSION,
87
- });
88
- }
89
- const missing = REQUIRED_TABLES.filter((name) => !tables.has(name));
90
- if (missing.length > 0) {
91
- return diagnosticEnvelope(root, runtime, 'STATE_SCHEMA_INCOMPLETE', {
92
- missing_tables: missing,
93
- });
94
- }
95
- return {
96
- ...contextEnvelope.buildEnvelope(
97
- db,
98
- { detail: 'summary', stage, scope },
99
- { rootDir: root, runtime },
100
- ),
101
- root,
102
- };
103
- } catch (error) {
104
- return diagnosticEnvelope(root, runtime, 'STATE_DB_UNREADABLE', {
105
- cause: error.code || error.message,
106
- });
107
- } finally {
108
- if (db) db.close();
109
- }
110
- }
111
-
112
- function compact(value, maximum = 240) {
113
- const normalized = String(value || '').replace(/\s+/g, ' ').trim();
114
- if (normalized.length <= maximum) return normalized;
115
- return `${normalized.slice(0, maximum - 3).trimEnd()}...`;
116
- }
117
-
118
- function renderProjectContextEnvelope(rootDir, context) {
119
- if (!context?.envelope) return '';
120
- const root = path.resolve(rootDir || context.root || process.cwd());
121
- const envelope = context.envelope;
122
- const baseline = envelope.baseline || {};
123
- const change = envelope.change || {};
124
- const task = envelope.task || {};
125
- const lines = [
126
- '[Ultra Context Envelope]',
127
- `Project: ${root}`,
128
- `Stage: ${envelope.execution?.stage || 'project'}`,
129
- `Baseline: ${baseline.id || 'none'} (${
130
- baseline.mode ? `${baseline.mode}/` : ''
131
- }${baseline.status || 'none'})`,
132
- `Change: ${change.id || 'none'} (${change.status || 'none'})`,
133
- `Task: ${task.id || 'none'} (${task.status || 'none'})`,
134
- ];
135
- for (const decision of (envelope.decisions || []).slice(0, 5)) {
136
- lines.push(
137
- `Accepted decision: ${decision.id || 'unknown'} — ${
138
- compact(decision.decision || decision.summary || decision.question || 'recorded')
139
- }`,
140
- );
141
- }
142
- for (const checkpoint of (envelope.checkpoints || []).slice(0, 5)) {
143
- lines.push(
144
- `Checkpoint: ${checkpoint.stage || 'unknown'} ${
145
- checkpoint.status || 'recorded'
146
- } (${checkpoint.id || 'unknown'})`,
147
- );
148
- }
149
- const warnings = envelope.diagnostics?.warnings || [];
150
- const attention = envelope.diagnostics?.needs_attention || [];
151
- const conflicts = envelope.diagnostics?.hard_conflicts || [];
152
- if (warnings.length > 0) {
153
- lines.push(`Warnings: ${warnings.map((item) => item.code).join(', ')}`);
154
- }
155
- if (attention.length > 0) {
156
- lines.push(`Needs attention: ${attention.map((item) => item.code).join(', ')}`);
157
- }
158
- if (conflicts.length > 0) {
159
- lines.push(`Hard conflicts: ${conflicts.map((item) => item.code).join(', ')}`);
160
- }
161
- if (envelope.team_checkpoint) {
162
- lines.push(
163
- `Team checkpoint: ${envelope.team_checkpoint.status || 'unknown'} ${
164
- envelope.team_checkpoint.state_digest || ''
165
- }`.trim(),
166
- );
167
- }
168
- lines.push(
169
- 'Authority: digest-bound .ultra documents plus checkout-local .ultra/.runtime/state.db.',
170
- 'Hooks are observational. Use ultra.context for the complete live envelope and public Ultra tools for changes.',
171
- );
172
- return lines.join('\n');
173
- }
174
-
175
- module.exports = {
176
- readProjectContextEnvelope,
177
- renderProjectContextEnvelope,
178
- };
@@ -1,386 +0,0 @@
1
- 'use strict';
2
-
3
- // Projector: regenerates the read-only file views from .ultra/.runtime/state.db.
4
- //
5
- // state.db owns lifecycle and artifact bindings (D32); registered digest-bound
6
- // files own semantic/evidence bodies. Live tasks.json and contexts/task-*.md are
7
- // checkout-local projections — humans may read them, never write them. The projector
8
- // is trigger-based: state-ops calls projectAll() after each successful
9
- // write transaction. Projection output passes the v4.5 schemas under
10
- // spec/schemas/ — see mcp-server/tests/projector.test.cjs for the
11
- // round-trip check.
12
-
13
- const fs = require('node:fs');
14
- const path = require('node:path');
15
- const crypto = require('node:crypto');
16
-
17
- const ops = require('./state-ops.cjs');
18
- const artifacts = require('./artifact-registry.cjs');
19
- const contextPaths = require('./context-paths.cjs');
20
-
21
- const SCHEMA_VERSION = '4.5';
22
- const SOURCE_TAG = '.ultra/.runtime/state.db';
23
-
24
- // Frozen SELECTs — values bind through @placeholders.
25
- const TASK_PROJECTION_COLUMNS = 'id, title, type, priority, complexity, estimated_days, status, deps, files_modified, session_id, stale, tag, trace_to, outcome, slice_kind, public_seam, verification_command, acceptance_json, context_refs_json, docs_impact_json, ownership_json, context_file, completion_commit, created_at, updated_at';
26
- const LIST_TASKS_FOR_PROJECTION_SQL = `SELECT ${TASK_PROJECTION_COLUMNS} FROM tasks ORDER BY created_at ASC`;
27
- const READ_TASK_FOR_PROJECTION_SQL = `SELECT ${TASK_PROJECTION_COLUMNS} FROM tasks WHERE id = @id`;
28
- const CONTRACT_START = '<!-- ultra:task-contract:start -->';
29
- const CONTRACT_END = '<!-- ultra:task-contract:end -->';
30
-
31
- function rowToProjection(row) {
32
- if (!row) return null;
33
- const out = { ...row };
34
- for (const k of ['deps', 'files_modified']) {
35
- if (typeof out[k] === 'string') {
36
- try { out[k] = JSON.parse(out[k]); } catch { out[k] = null; }
37
- }
38
- if (out[k] === null || out[k] === undefined) delete out[k];
39
- }
40
- const jsonFields = {
41
- acceptance_json: ['acceptance', []],
42
- context_refs_json: ['context_refs', []],
43
- docs_impact_json: ['docs_impact', { status: 'unknown', files: [], rationale: null }],
44
- ownership_json: ['ownership', {}],
45
- };
46
- for (const [column, [field, fallback]] of Object.entries(jsonFields)) {
47
- try { out[field] = typeof out[column] === 'string' ? JSON.parse(out[column]) : fallback; }
48
- catch { out[field] = fallback; }
49
- delete out[column];
50
- }
51
- if (out.stale !== undefined && out.stale !== null) out.stale = Boolean(out.stale);
52
- for (const k of Object.keys(out)) {
53
- if (out[k] === null || out[k] === undefined) delete out[k];
54
- }
55
- return out;
56
- }
57
-
58
- function defaultPaths(rootDir) {
59
- return {
60
- tasksJson: path.join(rootDir, '.ultra', '.runtime', 'projections', 'tasks.json'),
61
- contextsDir: path.join(rootDir, '.ultra', '.runtime', 'projections', 'contexts'),
62
- };
63
- }
64
-
65
- function writeAtomic(file, contents) {
66
- fs.mkdirSync(path.dirname(file), { recursive: true });
67
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
68
- fs.writeFileSync(tmp, contents);
69
- fs.renameSync(tmp, file);
70
- }
71
-
72
- function dbContentTimestamp(db) {
73
- const row = db.prepare("SELECT MAX(latest) AS ts FROM (SELECT MAX(updated_at) AS latest FROM tasks UNION ALL SELECT MAX(ts) AS latest FROM events UNION ALL SELECT MAX(applied_at) AS latest FROM schema_version)").get();
74
- return (row && row.ts) || new Date(0).toISOString();
75
- }
76
-
77
- function projectTasks(db, { tasksJson } = {}, opts = {}) {
78
- const rootDir = opts.rootDir;
79
- const target = tasksJson || defaultPaths(rootDir || '.').tasksJson;
80
- const rows = db.prepare(LIST_TASKS_FOR_PROJECTION_SQL).all();
81
- // generated_at = max(content timestamps) so identical state → identical
82
- // bytes → idempotent commits (PLAN Phase 2.6 / Phase 2.8 chore commit).
83
- const payload = {
84
- schema_version: SCHEMA_VERSION,
85
- generated_at: dbContentTimestamp(db),
86
- source: SOURCE_TAG,
87
- tasks: rows.map(rowToProjection),
88
- };
89
- writeAtomic(target, JSON.stringify(payload, null, 2) + '\n');
90
- return { path: target, count: rows.length };
91
- }
92
-
93
- function buildContextDoc(taskRow) {
94
- const headerLines = [
95
- '---',
96
- `task_id: ${taskRow.id}`,
97
- `title: ${escapeYaml(taskRow.title)}`,
98
- `status: ${taskRow.status}`,
99
- ];
100
- if (taskRow.priority) headerLines.push(`priority: ${taskRow.priority}`);
101
- if (taskRow.type) headerLines.push(`type: ${taskRow.type}`);
102
- if (taskRow.session_id) headerLines.push(`session_id: ${taskRow.session_id}`);
103
- if (taskRow.completion_commit) headerLines.push(`completion_commit: ${taskRow.completion_commit}`);
104
- if (taskRow.stale) headerLines.push('stale: true');
105
- headerLines.push(`schema_version: ${SCHEMA_VERSION}`);
106
- headerLines.push(`generated_by: ${contextPaths.GENERATED_BY}`);
107
- headerLines.push(`generated_at: ${taskRow.updated_at || new Date().toISOString()}`);
108
- headerLines.push('---', '');
109
-
110
- const body = [];
111
- if (taskRow.stale) {
112
- body.push([
113
- `> ⚠️ STALE since ${taskRow.updated_at || new Date().toISOString()}`,
114
- '> Specification evidence may have changed. Recompile the task context before continuing.',
115
- '',
116
- ].join('\n'));
117
- }
118
- body.push(buildTaskContract(taskRow));
119
- return headerLines.join('\n') + body.join('');
120
- }
121
-
122
- function buildTaskContract(task) {
123
- const acceptance = Array.isArray(task.acceptance) ? task.acceptance : [];
124
- const refs = Array.isArray(task.context_refs) ? task.context_refs : [];
125
- const docs = task.docs_impact || { status: 'unknown', files: [], rationale: null };
126
- const ownership = task.ownership || {};
127
- const lines = [
128
- CONTRACT_START,
129
- '## Execution Contract (generated from state.db)',
130
- '',
131
- `- Outcome: ${task.outcome || 'unresolved'}`,
132
- `- Slice: ${task.slice_kind || 'unresolved'}`,
133
- `- Public seam: ${task.public_seam || 'unresolved'}`,
134
- `- Verification: ${task.verification_command || 'unresolved'}`,
135
- `- Trace: ${task.trace_to || 'unresolved'}`,
136
- `- Owner: ${ownership.owner || 'unresolved'}`,
137
- `- Documentation: ${docs.status || 'unknown'}${docs.rationale ? ` — ${docs.rationale}` : ''}`,
138
- '',
139
- '### Acceptance',
140
- '',
141
- ...(acceptance.length > 0
142
- ? acceptance.map((item) => `- ${item.id}: ${item.criterion} — verify with \`${item.verification}\``)
143
- : ['- unresolved']),
144
- '',
145
- '### Context References',
146
- '',
147
- ...(refs.length > 0
148
- ? refs.map((item) => `- \`${item.ref}\` — ${item.reason}`)
149
- : ['- unresolved']),
150
- '',
151
- CONTRACT_END,
152
- '',
153
- ];
154
- return lines.join('\n');
155
- }
156
-
157
- function escapeYaml(str) {
158
- if (str == null) return '';
159
- if (/[:#\[\]\{\}&\*!|>'"%@`]/.test(str) || /^\s|\s$/.test(str)) {
160
- return JSON.stringify(str);
161
- }
162
- return str;
163
- }
164
-
165
- function legacyContextParseError(message) {
166
- const error = new Error(message);
167
- error.code = 'LEGACY_CONTEXT_PARSE_ERROR';
168
- return error;
169
- }
170
-
171
- function extractAuthoredContextBytes(contents) {
172
- const original = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
173
- let body = original.toString('utf8');
174
- const frontmatter = /^(?:\uFEFF)?---(?:\r\n|\n)[\s\S]*?(?:\r\n|\n)---(?:(?:\r\n|\n)|$)/.exec(body);
175
- if (frontmatter) body = body.slice(frontmatter[0].length);
176
- const staleBanner = /^> ⚠️ STALE[^\r\n]*(?:(?:\r\n|\n)>[^\r\n]*)*(?:(?:\r\n|\n){1,2}|$)/.exec(body);
177
- if (staleBanner) body = body.slice(staleBanner[0].length);
178
-
179
- let cursor = 0;
180
- while (true) {
181
- const start = body.indexOf(CONTRACT_START, cursor);
182
- if (start === -1) break;
183
- const markerEnd = body.indexOf(CONTRACT_END, start + CONTRACT_START.length);
184
- if (markerEnd === -1) {
185
- throw legacyContextParseError(
186
- 'legacy task context has an unterminated generated execution contract',
187
- );
188
- }
189
- let end = markerEnd + CONTRACT_END.length;
190
- if (body.startsWith('\r\n', end)) end += 2;
191
- else if (body.startsWith('\n', end)) end += 1;
192
- const removeFrom = body.slice(0, start).trim() === '' ? 0 : start;
193
- body = body.slice(0, removeFrom) + body.slice(end);
194
- cursor = removeFrom;
195
- }
196
- return body.trim() === '' ? null : Buffer.from(body, 'utf8');
197
- }
198
-
199
- function promoteAuthoredContext(db, taskId, target, rootDir) {
200
- if (!fs.existsSync(target.file)) return null;
201
- const stat = fs.lstatSync(target.file);
202
- if (!stat.isFile() || stat.isSymbolicLink()) {
203
- throw new contextPaths.ContextPathError(
204
- `task context must be a regular file: ${target.relative}`,
205
- );
206
- }
207
- const sourceBytes = fs.readFileSync(target.file);
208
- const bytes = extractAuthoredContextBytes(sourceBytes);
209
- if (!bytes) return null;
210
- const digest = crypto.createHash('sha256').update(bytes).digest('hex');
211
- const sourceDigest = crypto.createHash('sha256').update(sourceBytes).digest('hex');
212
- const safeTaskId = String(taskId).replace(/[^a-zA-Z0-9_-]+/g, '-');
213
- const relative = `.ultra/docs/migration/legacy-context/${safeTaskId}-${digest.slice(0, 12)}.md`;
214
- const file = path.resolve(rootDir, ...relative.split('/'));
215
- const existed = fs.existsSync(file);
216
- if (existed) {
217
- const existingDigest = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
218
- if (existingDigest !== digest) {
219
- throw new Error(`legacy context promotion digest conflict: ${relative}`);
220
- }
221
- } else {
222
- writeAtomic(file, bytes);
223
- }
224
- try {
225
- const recorded = artifacts.recordArtifact(db, {
226
- owner_type: 'task',
227
- owner_id: taskId,
228
- kind: 'legacy_context_findings',
229
- path: relative,
230
- content_digest: digest,
231
- source_refs: [{
232
- type: 'external',
233
- id: `legacy-context:${target.relative}`,
234
- relation: 'imported_from',
235
- }],
236
- consumer_refs: [{ type: 'task', id: taskId, relation: 'consumed_by' }],
237
- provenance: {
238
- writer: 'ultra-projector',
239
- operation: 'legacy-context-promotion',
240
- },
241
- metadata: {
242
- original_path: target.relative,
243
- original_digest: sourceDigest,
244
- authored_body_digest: digest,
245
- },
246
- }, { rootDir });
247
- return { path: relative, digest, artifact_id: recorded.artifact.id };
248
- } catch (error) {
249
- if (!existed) fs.rmSync(file, { force: true });
250
- throw error;
251
- }
252
- }
253
-
254
- function projectContext(db, taskId, { contextsDir } = {}, opts = {}) {
255
- const row = db.prepare(READ_TASK_FOR_PROJECTION_SQL).get({ id: taskId });
256
- if (!row) return null;
257
- const projection = rowToProjection(row);
258
- const rootDir = path.resolve(opts.rootDir || '.');
259
- if (contextsDir) {
260
- const supplied = path.resolve(contextsDir);
261
- const canonical = defaultPaths(rootDir).contextsDir;
262
- if (supplied !== canonical) {
263
- throw new contextPaths.ContextPathError(
264
- `custom context projection roots are not allowed: ${contextsDir}`,
265
- );
266
- }
267
- }
268
- const target = contextPaths.resolveContextPath(rootDir, projection.context_file, { taskId });
269
- const legacyValue = String(projection.context_file || '')
270
- .replaceAll('\\', '/')
271
- .replace(/^\.\//, '');
272
- const legacyDefault = `${contextPaths.LEGACY_CONTEXT_ROOT_RELATIVE}/${
273
- path.posix.basename(target.relative)
274
- }`;
275
- const legacyTarget = contextPaths.resolveLegacyContextPath(
276
- rootDir,
277
- legacyValue.startsWith(`${contextPaths.LEGACY_CONTEXT_ROOT_RELATIVE}/`)
278
- ? legacyValue
279
- : legacyDefault,
280
- { taskId },
281
- );
282
- const legacyPromoted = promoteAuthoredContext(db, taskId, legacyTarget, rootDir);
283
- if (fs.existsSync(legacyTarget.file)
284
- && contextPaths.isGeneratedContextContents(fs.readFileSync(legacyTarget.file, 'utf8'))) {
285
- fs.rmSync(legacyTarget.file, { force: true });
286
- } else if (legacyPromoted) {
287
- fs.rmSync(legacyTarget.file, { force: true });
288
- }
289
- const promoted = promoteAuthoredContext(db, taskId, target, rootDir) || legacyPromoted;
290
- const next = buildContextDoc(projection);
291
- writeAtomic(target.file, next);
292
- return { path: target.file, task_id: taskId, promoted };
293
- }
294
-
295
- function listContextFiles(directory) {
296
- if (!fs.existsSync(directory)) return [];
297
- const output = [];
298
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
299
- const file = path.join(directory, entry.name);
300
- if (entry.isSymbolicLink()) continue;
301
- if (entry.isDirectory()) output.push(...listContextFiles(file));
302
- else if (entry.isFile()) output.push(file);
303
- }
304
- return output;
305
- }
306
-
307
- function pruneGeneratedGhostsIn(db, rootDir, contextsDir, expected) {
308
- const pruned = [];
309
- for (const file of listContextFiles(contextsDir)) {
310
- const relative = path.relative(rootDir, file).split(path.sep).join('/');
311
- if (expected.has(relative) || path.basename(file) === 'TEMPLATE.md') continue;
312
- const contents = fs.readFileSync(file);
313
- if (!contextPaths.isGeneratedContextContents(contents)) continue;
314
- const staged = `${file}.prune-${process.pid}-${Date.now()}`;
315
- fs.renameSync(file, staged);
316
- try {
317
- ops.appendEvent(db, {
318
- type: 'projection_pruned',
319
- payload: { path: relative, reason: 'task_missing' },
320
- });
321
- fs.rmSync(staged, { force: true });
322
- pruned.push(relative);
323
- } catch (error) {
324
- fs.renameSync(staged, file);
325
- throw error;
326
- }
327
- }
328
- return pruned;
329
- }
330
-
331
- function pruneGeneratedGhosts(db, rootDir, expected) {
332
- const runtime = pruneGeneratedGhostsIn(
333
- db,
334
- rootDir,
335
- defaultPaths(rootDir).contextsDir,
336
- expected,
337
- );
338
- const legacy = pruneGeneratedGhostsIn(
339
- db,
340
- rootDir,
341
- path.join(rootDir, ...contextPaths.LEGACY_CONTEXT_ROOT_RELATIVE.split('/')),
342
- new Set(),
343
- );
344
- return [...runtime, ...legacy].sort();
345
- }
346
-
347
- function projectAll(db, { rootDir = '.', tasksJson, contextsDir } = {}) {
348
- const resolvedRoot = path.resolve(rootDir);
349
- const paths = defaultPaths(resolvedRoot);
350
- const tasksTarget = tasksJson || paths.tasksJson;
351
- const contextsTarget = contextsDir || paths.contextsDir;
352
- if (path.resolve(contextsTarget) !== paths.contextsDir) {
353
- throw new contextPaths.ContextPathError(
354
- `custom context projection roots are not allowed: ${contextsTarget}`,
355
- );
356
- }
357
-
358
- const rows = db.prepare("SELECT id, context_file FROM tasks").all();
359
- const expected = new Set(rows.map((row) => (
360
- contextPaths.resolveContextPath(resolvedRoot, row.context_file, { taskId: row.id }).relative
361
- )));
362
- const tasksResult = projectTasks(db, { tasksJson: tasksTarget });
363
- const contextResults = [];
364
- for (const r of rows) {
365
- contextResults.push(projectContext(
366
- db, r.id, { contextsDir: contextsTarget }, { rootDir: resolvedRoot },
367
- ));
368
- }
369
- const pruned = pruneGeneratedGhosts(db, resolvedRoot, expected);
370
- return {
371
- tasks_json: tasksResult,
372
- contexts: contextResults.filter(Boolean),
373
- pruned,
374
- };
375
- }
376
-
377
- module.exports = {
378
- SCHEMA_VERSION,
379
- defaultPaths,
380
- projectTasks,
381
- projectContext,
382
- projectAll,
383
- rowToProjection,
384
- extractAuthoredContextBytes,
385
- promoteAuthoredContext,
386
- };