ultra-builder-pro-cli 0.25.0 → 0.26.0

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