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,790 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('node:crypto');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const Ajv = require('ajv/dist/2020');
8
-
9
- const ops = require('./state-ops.cjs');
10
- const artifacts = require('./artifact-registry.cjs');
11
- const baselines = require('./baseline-workflow.cjs');
12
- const deltaSchema = require('../../spec/schemas/change-delta.v1.schema.json');
13
- const documentationSchema = require(
14
- '../../spec/schemas/documentation-reconciliation.v1.schema.json',
15
- );
16
-
17
- const ajv = new Ajv({ allErrors: true, strict: false });
18
- const validateDeltaSchema = ajv.compile(deltaSchema);
19
- const validateDocumentationSchema = ajv.compile(documentationSchema);
20
- const ID = /^[a-zA-Z0-9_-]+$/;
21
-
22
- class ChangePacketError extends Error {
23
- constructor(code, message, details = undefined) {
24
- super(message);
25
- this.name = 'ChangePacketError';
26
- this.code = code;
27
- if (details !== undefined) this.details = details;
28
- }
29
- }
30
-
31
- function nowIso() {
32
- return new Date().toISOString();
33
- }
34
-
35
- function digestBytes(bytes) {
36
- return crypto.createHash('sha256').update(bytes).digest('hex');
37
- }
38
-
39
- function digestFile(file) {
40
- return digestBytes(fs.readFileSync(file));
41
- }
42
-
43
- function canonical(value) {
44
- if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
45
- if (value && typeof value === 'object') {
46
- return `{${Object.keys(value).sort().map(
47
- (key) => `${JSON.stringify(key)}:${canonical(value[key])}`,
48
- ).join(',')}}`;
49
- }
50
- return JSON.stringify(value);
51
- }
52
-
53
- function sameValue(left, right) {
54
- return canonical(left) === canonical(right);
55
- }
56
-
57
- function safeFile(rootDir, relative) {
58
- const normalized = artifacts.normalizeRelativePath(relative);
59
- const root = path.resolve(rootDir);
60
- const file = path.resolve(root, normalized);
61
- if (file !== root && !file.startsWith(`${root}${path.sep}`)) {
62
- throw new ChangePacketError('VALIDATION_ERROR', `path escapes project root: ${relative}`);
63
- }
64
- return { relative: normalized, file };
65
- }
66
-
67
- function requireInside(rootDir, relative, prefix, field) {
68
- const resolved = safeFile(rootDir, relative);
69
- const normalizedPrefix = artifacts.normalizeRelativePath(prefix);
70
- if (resolved.relative !== normalizedPrefix
71
- && !resolved.relative.startsWith(`${normalizedPrefix}/`)) {
72
- throw new ChangePacketError(
73
- 'CHANGE_OVERLAY_PATH_INVALID',
74
- `${field} must stay inside ${normalizedPrefix}`,
75
- );
76
- }
77
- return resolved;
78
- }
79
-
80
- function deliveryConsumer() {
81
- return { type: 'external', id: 'ultra-deliver', relation: 'consumed_by' };
82
- }
83
-
84
- function readCurrentArtifact(db, changeId, kind) {
85
- const rows = db.prepare(
86
- `SELECT id FROM artifacts
87
- WHERE owner_type = 'change' AND owner_id = ? AND kind = ?
88
- AND status <> 'archived'
89
- ORDER BY updated_at DESC, rowid DESC`,
90
- ).all(changeId, kind);
91
- if (rows.length !== 1) return null;
92
- const artifact = artifacts.getArtifact(db, { id: rows[0].id });
93
- return artifact.status === 'current' ? artifact : null;
94
- }
95
-
96
- function exactBaselineAnchor(db, change, input) {
97
- const baseline = baselines.readBaseline(db);
98
- if (!baseline || baseline.status !== 'ready') {
99
- throw new ChangePacketError(
100
- 'BASELINE_NOT_READY',
101
- 'typed Change delta requires one approved ready baseline',
102
- );
103
- }
104
- const anchor = input.baseline_anchor;
105
- if (!anchor || anchor.baseline_id !== baseline.id
106
- || (anchor.repository_revision || null) !== (baseline.repository_revision || null)) {
107
- throw new ChangePacketError(
108
- 'CHANGE_DELTA_BASELINE_CONFLICT',
109
- 'delta baseline id or repository revision is stale',
110
- {
111
- expected_baseline_id: baseline.id,
112
- expected_repository_revision: baseline.repository_revision || null,
113
- },
114
- );
115
- }
116
- const expected = [...baseline.spec_refs]
117
- .map((item) => ({ path: artifacts.normalizeRelativePath(item.path), digest: item.digest }))
118
- .sort((left, right) => left.path.localeCompare(right.path));
119
- const actual = [...(anchor.specs || [])]
120
- .map((item) => ({ path: artifacts.normalizeRelativePath(item.path), digest: item.digest }))
121
- .sort((left, right) => left.path.localeCompare(right.path));
122
- if (!sameValue(expected, actual)) {
123
- throw new ChangePacketError(
124
- 'CHANGE_DELTA_BASELINE_CONFLICT',
125
- 'delta specification anchors do not match the current baseline',
126
- { expected, actual },
127
- );
128
- }
129
- for (const spec of expected) {
130
- const resolved = safeFile(input.root_dir, spec.path);
131
- if (!fs.existsSync(resolved.file) || digestFile(resolved.file) !== spec.digest) {
132
- throw new ChangePacketError(
133
- 'CHANGE_DELTA_BASELINE_CONFLICT',
134
- `baseline specification bytes are stale: ${spec.path}`,
135
- );
136
- }
137
- }
138
- return baseline;
139
- }
140
-
141
- function normalizeDelta(db, change, input, rootDir) {
142
- const candidate = {
143
- $schema: 'ultra-change-delta-v1',
144
- change_id: change.id,
145
- baseline_anchor: input.baseline_anchor,
146
- decisions: input.decisions || [],
147
- non_goals: input.non_goals || [],
148
- acceptance: input.acceptance || [],
149
- documentation_impact: input.documentation_impact,
150
- unknowns: input.unknowns || [],
151
- ...(input.no_semantic_change_reason === undefined
152
- ? {}
153
- : { no_semantic_change_reason: input.no_semantic_change_reason }),
154
- mutations: input.mutations || [],
155
- };
156
- if (!validateDeltaSchema(candidate)) {
157
- throw new ChangePacketError(
158
- 'CHANGE_DELTA_INVALID',
159
- `change delta does not satisfy its schema: ${ajv.errorsText(validateDeltaSchema.errors)}`,
160
- validateDeltaSchema.errors,
161
- );
162
- }
163
- const baseline = exactBaselineAnchor(db, change, {
164
- ...candidate,
165
- root_dir: rootDir,
166
- });
167
- if (!sameValue(candidate.non_goals, change.contract.non_goals)
168
- || !sameValue(candidate.acceptance, change.contract.acceptance)
169
- || !sameValue(candidate.documentation_impact, change.docs_impact)) {
170
- throw new ChangePacketError(
171
- 'CHANGE_DELTA_CONTRACT_MISMATCH',
172
- 'delta non-goals, acceptance, and documentation impact must match accepted Change authority',
173
- );
174
- }
175
- const acceptanceIds = new Set(change.contract.acceptance.map((item) => item.id));
176
- const documentationPaths = new Set(change.docs_impact.files || []);
177
- const mutationIds = new Set();
178
- const targetPaths = new Set();
179
- const overlayRoot = `${change.artifact_root}/delta`;
180
- const normalizedMutations = candidate.mutations.map((mutation) => {
181
- if (!ID.test(mutation.id) || mutationIds.has(mutation.id)) {
182
- throw new ChangePacketError(
183
- 'CHANGE_DELTA_INVALID', `duplicate or invalid mutation id: ${mutation.id}`,
184
- );
185
- }
186
- mutationIds.add(mutation.id);
187
- const target = safeFile(rootDir, mutation.target_path);
188
- if (targetPaths.has(target.relative)) {
189
- throw new ChangePacketError(
190
- 'CHANGE_DELTA_INVALID', `duplicate mutation target: ${target.relative}`,
191
- );
192
- }
193
- targetPaths.add(target.relative);
194
- const overlay = requireInside(
195
- rootDir, mutation.overlay_path, overlayRoot, 'mutations.overlay_path',
196
- );
197
- if (!fs.existsSync(overlay.file) || !fs.statSync(overlay.file).isFile()) {
198
- throw new ChangePacketError(
199
- 'CHANGE_DELTA_PAYLOAD_MISSING', `delta overlay file is missing: ${overlay.relative}`,
200
- );
201
- }
202
- const actualAfter = digestFile(overlay.file);
203
- if (actualAfter !== mutation.after_digest) {
204
- throw new ChangePacketError(
205
- 'CHANGE_DELTA_PAYLOAD_STALE',
206
- `delta overlay digest is stale: ${overlay.relative}`,
207
- );
208
- }
209
- const targetExists = fs.existsSync(target.file);
210
- const actualBefore = targetExists ? digestFile(target.file) : null;
211
- if ((mutation.action === 'add' && (targetExists || mutation.before_digest !== null))
212
- || (mutation.action === 'update'
213
- && (!targetExists || mutation.before_digest !== actualBefore))) {
214
- throw new ChangePacketError(
215
- 'CHANGE_DELTA_BASELINE_CONFLICT',
216
- `delta before-state is stale: ${target.relative}`,
217
- { expected: mutation.before_digest, actual: actualBefore },
218
- );
219
- }
220
- const baselineRef = baseline.spec_refs.find((item) => (
221
- artifacts.normalizeRelativePath(item.path) === target.relative
222
- ));
223
- if (mutation.action === 'update'
224
- && (!baselineRef || baselineRef.digest !== mutation.before_digest)) {
225
- throw new ChangePacketError(
226
- 'CHANGE_DELTA_BASELINE_CONFLICT',
227
- `delta target is not anchored by the current baseline: ${target.relative}`,
228
- );
229
- }
230
- if (mutation.acceptance_refs.some((id) => !acceptanceIds.has(id))) {
231
- throw new ChangePacketError(
232
- 'CHANGE_DELTA_ACCEPTANCE_INVALID',
233
- `delta mutation ${mutation.id} references unknown acceptance`,
234
- );
235
- }
236
- if (mutation.documentation_refs.some((item) => !documentationPaths.has(item))) {
237
- throw new ChangePacketError(
238
- 'CHANGE_DELTA_DOCUMENTATION_INVALID',
239
- `delta mutation ${mutation.id} references undeclared documentation`,
240
- );
241
- }
242
- return {
243
- ...mutation,
244
- target_path: target.relative,
245
- overlay_path: overlay.relative,
246
- };
247
- });
248
- const documented = new Set(normalizedMutations.flatMap((item) => item.documentation_refs));
249
- if ([...documentationPaths].some((item) => !documented.has(item))) {
250
- throw new ChangePacketError(
251
- 'CHANGE_DELTA_DOCUMENTATION_INVALID',
252
- 'every required documentation path must be traced from at least one mutation',
253
- );
254
- }
255
- return {
256
- ...candidate,
257
- baseline_anchor: {
258
- ...candidate.baseline_anchor,
259
- specs: [...candidate.baseline_anchor.specs]
260
- .sort((left, right) => left.path.localeCompare(right.path)),
261
- },
262
- decisions: [...candidate.decisions].sort((left, right) => left.id.localeCompare(right.id)),
263
- non_goals: [...candidate.non_goals].sort(),
264
- acceptance: [...candidate.acceptance].sort((left, right) => left.id.localeCompare(right.id)),
265
- unknowns: [...candidate.unknowns].sort((left, right) => left.id.localeCompare(right.id)),
266
- mutations: normalizedMutations.sort((left, right) => left.id.localeCompare(right.id)),
267
- };
268
- }
269
-
270
- function atomicWrite(file, bytes) {
271
- fs.mkdirSync(path.dirname(file), { recursive: true });
272
- const previous = fs.existsSync(file) ? fs.readFileSync(file) : null;
273
- const temporary = `${file}.tmp-${process.pid}-${crypto.randomUUID()}`;
274
- fs.writeFileSync(temporary, bytes);
275
- fs.renameSync(temporary, file);
276
- return () => {
277
- if (previous === null) fs.rmSync(file, { force: true });
278
- else fs.writeFileSync(file, previous);
279
- };
280
- }
281
-
282
- function renderProgress(change, { delta = null, reconciliation = null } = {}) {
283
- return [
284
- `# Progress: ${change.title}`,
285
- '',
286
- `Change: \`${change.id}\``,
287
- `Status: \`${change.status}\``,
288
- `Delta: ${delta ? `\`${delta.id}\` @ \`${delta.digest}\`` : 'not recorded'}`,
289
- `Documentation reconciliation: ${reconciliation
290
- ? `\`${reconciliation.id}\` @ \`${reconciliation.digest}\``
291
- : 'not recorded'}`,
292
- '',
293
- 'This file is a deterministic projection of registered Change authorities.',
294
- '',
295
- ].join('\n');
296
- }
297
-
298
- function writeProgressInTx(db, change, rootDir, { delta = null, reconciliation = null } = {}) {
299
- const relative = `${change.artifact_root}/progress.md`;
300
- const resolved = safeFile(rootDir, relative);
301
- const rollback = atomicWrite(
302
- resolved.file,
303
- renderProgress(change, { delta, reconciliation }),
304
- );
305
- try {
306
- const sources = [
307
- { type: 'change', id: change.id, relation: 'projected_from' },
308
- ...(delta ? [{ type: 'artifact', id: delta.id, relation: 'projected_from' }] : []),
309
- ...(reconciliation
310
- ? [{ type: 'artifact', id: reconciliation.id, relation: 'projected_from' }]
311
- : []),
312
- ];
313
- const result = artifacts.recordArtifactInTx(db, {
314
- id: `change-${change.id}-progress`,
315
- owner_type: 'change',
316
- owner_id: change.id,
317
- kind: 'progress_projection',
318
- path: relative,
319
- source_refs: sources,
320
- consumer_refs: [],
321
- provenance: { writer: 'change-packet', generated: true },
322
- metadata: { projection: true, terminal_role: true },
323
- }, { rootDir });
324
- return { result, rollback };
325
- } catch (error) {
326
- rollback();
327
- throw error;
328
- }
329
- }
330
-
331
- function recordDelta(db, change, input, { rootDir = process.cwd() } = {}) {
332
- if (!change || !['active', 'blocked'].includes(change.status)) {
333
- throw new ChangePacketError(
334
- 'CHANGE_NOT_MUTABLE',
335
- `change ${change?.id || '(missing)'} cannot accept delta authority`,
336
- );
337
- }
338
- const delta = normalizeDelta(db, change, input, rootDir);
339
- const relative = `${change.artifact_root}/delta/change-delta.json`;
340
- const resolved = safeFile(rootDir, relative);
341
- const serialized = `${JSON.stringify(delta, null, 2)}\n`;
342
- const rollbackDelta = atomicWrite(resolved.file, serialized);
343
- let rollbackProgress = null;
344
- try {
345
- const result = ops.tx(db, () => {
346
- const consumer = deliveryConsumer();
347
- const payloads = delta.mutations.map((mutation) => (
348
- artifacts.recordArtifactInTx(db, {
349
- id: `change-${change.id}-delta-${mutation.id}`,
350
- owner_type: 'change',
351
- owner_id: change.id,
352
- kind: 'delta_payload',
353
- path: mutation.overlay_path,
354
- content_digest: mutation.after_digest,
355
- source_refs: [{ type: 'change', id: change.id, relation: 'proposed_by' }],
356
- consumer_refs: [consumer],
357
- provenance: { writer: 'change.delta', mutation_id: mutation.id },
358
- metadata: {
359
- target_path: mutation.target_path,
360
- action: mutation.action,
361
- before_digest: mutation.before_digest,
362
- after_digest: mutation.after_digest,
363
- },
364
- }, { rootDir }).artifact
365
- ));
366
- const recorded = artifacts.recordArtifactInTx(db, {
367
- id: `change-${change.id}-delta`,
368
- owner_type: 'change',
369
- owner_id: change.id,
370
- kind: 'change_delta',
371
- path: relative,
372
- content_digest: digestBytes(serialized),
373
- source_refs: [
374
- { type: 'change', id: change.id, relation: 'normalized_from' },
375
- {
376
- type: 'baseline',
377
- id: delta.baseline_anchor.baseline_id,
378
- relation: 'deltas_from',
379
- },
380
- ...payloads.map((artifact) => ({
381
- type: 'artifact', id: artifact.id, relation: 'contains_payload',
382
- })),
383
- ],
384
- consumer_refs: [consumer],
385
- provenance: {
386
- writer: 'change.delta',
387
- recorded_at: nowIso(),
388
- baseline_revision: delta.baseline_anchor.repository_revision,
389
- },
390
- metadata: {
391
- mutation_ids: delta.mutations.map((item) => item.id),
392
- acceptance_ids: delta.acceptance.map((item) => item.id),
393
- },
394
- }, { rootDir });
395
- const progress = writeProgressInTx(
396
- db,
397
- change,
398
- rootDir,
399
- { delta: recorded.artifact },
400
- );
401
- rollbackProgress = progress.rollback;
402
- ops.appendEventInTx(db, {
403
- type: 'change_delta_recorded',
404
- change_id: change.id,
405
- payload: {
406
- artifact_id: recorded.artifact.id,
407
- digest: recorded.artifact.digest,
408
- mutation_ids: delta.mutations.map((item) => item.id),
409
- },
410
- });
411
- return {
412
- delta,
413
- artifact: recorded.artifact,
414
- payload_artifacts: payloads,
415
- progress_artifact: progress.result.artifact,
416
- };
417
- });
418
- return result;
419
- } catch (error) {
420
- if (rollbackProgress) rollbackProgress();
421
- rollbackDelta();
422
- throw error;
423
- }
424
- }
425
-
426
- function normalizeDocumentation(db, change, input, rootDir) {
427
- const candidate = {
428
- $schema: 'ultra-documentation-reconciliation-v1',
429
- change_id: change.id,
430
- delta_artifact_id: input.delta_artifact_id,
431
- delta_digest: input.delta_digest,
432
- ...(input.no_change_reason === undefined
433
- ? {}
434
- : { no_change_reason: input.no_change_reason }),
435
- documents: input.documents || [],
436
- };
437
- if (!validateDocumentationSchema(candidate)) {
438
- throw new ChangePacketError(
439
- 'DOCUMENTATION_RECONCILIATION_INVALID',
440
- `documentation reconciliation does not satisfy its schema: ${ajv.errorsText(validateDocumentationSchema.errors)}`,
441
- validateDocumentationSchema.errors,
442
- );
443
- }
444
- const deltaArtifact = artifacts.getArtifact(db, { id: candidate.delta_artifact_id });
445
- if (deltaArtifact.owner_type !== 'change' || deltaArtifact.owner_id !== change.id
446
- || deltaArtifact.kind !== 'change_delta' || deltaArtifact.status !== 'current'
447
- || deltaArtifact.digest !== candidate.delta_digest) {
448
- throw new ChangePacketError(
449
- 'DOCUMENTATION_DELTA_AUTHORITY_INVALID',
450
- 'documentation reconciliation must bind the current Change delta digest',
451
- );
452
- }
453
- const deltaFile = safeFile(rootDir, deltaArtifact.path);
454
- if (!fs.existsSync(deltaFile.file) || digestFile(deltaFile.file) !== deltaArtifact.digest) {
455
- throw new ChangePacketError(
456
- 'DOCUMENTATION_DELTA_AUTHORITY_INVALID',
457
- 'documentation reconciliation delta bytes are stale',
458
- );
459
- }
460
- const delta = JSON.parse(fs.readFileSync(deltaFile.file, 'utf8'));
461
- const deltaIds = new Set(delta.mutations.map((item) => item.id));
462
- const acceptanceIds = new Set(delta.acceptance.map((item) => item.id));
463
- const requiredPaths = new Set(change.docs_impact.status === 'required'
464
- ? change.docs_impact.files
465
- : []);
466
- const actualPaths = new Set();
467
- const documentationRoot = `${change.artifact_root}/documentation`;
468
- const documents = candidate.documents.map((document) => {
469
- if (actualPaths.has(document.path)) {
470
- throw new ChangePacketError(
471
- 'DOCUMENTATION_RECONCILIATION_INVALID',
472
- `duplicate documentation path: ${document.path}`,
473
- );
474
- }
475
- const target = safeFile(rootDir, document.path);
476
- actualPaths.add(target.relative);
477
- const overlay = requireInside(
478
- rootDir, document.overlay_path, documentationRoot, 'documents.overlay_path',
479
- );
480
- if (!fs.existsSync(overlay.file) || !fs.statSync(overlay.file).isFile()
481
- || digestFile(overlay.file) !== document.after_digest) {
482
- throw new ChangePacketError(
483
- 'DOCUMENTATION_EVIDENCE_STALE',
484
- `documentation overlay is missing or stale: ${overlay.relative}`,
485
- );
486
- }
487
- const targetExists = fs.existsSync(target.file);
488
- const actualBefore = targetExists ? digestFile(target.file) : null;
489
- if ((document.action === 'add' && (targetExists || document.before_digest !== null))
490
- || (document.action === 'update'
491
- && (!targetExists || actualBefore !== document.before_digest))) {
492
- throw new ChangePacketError(
493
- 'DOCUMENTATION_BASELINE_CONFLICT',
494
- `documentation before-state is stale: ${target.relative}`,
495
- );
496
- }
497
- if (document.delta_refs.some((id) => !deltaIds.has(id))
498
- || document.acceptance_refs.some((id) => !acceptanceIds.has(id))) {
499
- throw new ChangePacketError(
500
- 'DOCUMENTATION_TRACE_INVALID',
501
- `documentation trace references are invalid: ${target.relative}`,
502
- );
503
- }
504
- if (document.consumers.length === 0
505
- && String(document.no_consumer_reason || '').trim().length < 3) {
506
- throw new ChangePacketError(
507
- 'DOCUMENTATION_ORPHAN_UNRESOLVED',
508
- `documentation path has no verified consumer: ${target.relative}`,
509
- );
510
- }
511
- return {
512
- ...document,
513
- path: target.relative,
514
- overlay_path: overlay.relative,
515
- };
516
- });
517
- if (!sameValue([...requiredPaths].sort(), [...actualPaths].sort())) {
518
- throw new ChangePacketError(
519
- 'DOCUMENTATION_RECONCILIATION_INCOMPLETE',
520
- 'documentation reconciliation must cover the exact declared documentation impact',
521
- { required: [...requiredPaths].sort(), actual: [...actualPaths].sort() },
522
- );
523
- }
524
- return {
525
- reconciliation: {
526
- ...candidate,
527
- documents: documents.sort((left, right) => left.path.localeCompare(right.path)),
528
- },
529
- deltaArtifact,
530
- };
531
- }
532
-
533
- function recordDocumentationReconciliation(
534
- db,
535
- change,
536
- input,
537
- { rootDir = process.cwd() } = {},
538
- ) {
539
- if (!change || !['active', 'blocked'].includes(change.status)) {
540
- throw new ChangePacketError(
541
- 'CHANGE_NOT_MUTABLE',
542
- `change ${change?.id || '(missing)'} cannot accept documentation reconciliation`,
543
- );
544
- }
545
- const normalized = normalizeDocumentation(db, change, input, rootDir);
546
- const relative = `${change.artifact_root}/documentation/reconciliation.json`;
547
- const resolved = safeFile(rootDir, relative);
548
- const serialized = `${JSON.stringify(normalized.reconciliation, null, 2)}\n`;
549
- const rollbackReconciliation = atomicWrite(resolved.file, serialized);
550
- let rollbackProgress = null;
551
- try {
552
- return ops.tx(db, () => {
553
- const fallbackConsumer = deliveryConsumer();
554
- const payloads = normalized.reconciliation.documents.map((document, index) => (
555
- artifacts.recordArtifactInTx(db, {
556
- id: `change-${change.id}-documentation-${index + 1}`,
557
- owner_type: 'change',
558
- owner_id: change.id,
559
- kind: 'documentation_payload',
560
- path: document.overlay_path,
561
- content_digest: document.after_digest,
562
- source_refs: [{
563
- type: 'artifact',
564
- id: normalized.deltaArtifact.id,
565
- relation: 'documents',
566
- }],
567
- consumer_refs: document.consumers.length > 0
568
- ? document.consumers
569
- : [fallbackConsumer],
570
- provenance: { writer: 'change.documentation_reconcile', target_path: document.path },
571
- metadata: {
572
- target_path: document.path,
573
- action: document.action,
574
- before_digest: document.before_digest,
575
- after_digest: document.after_digest,
576
- orphan_disposition: document.consumers.length > 0
577
- ? 'consumers_verified'
578
- : 'no_consumer_reason',
579
- },
580
- }, { rootDir }).artifact
581
- ));
582
- const recorded = artifacts.recordArtifactInTx(db, {
583
- id: `change-${change.id}-documentation-reconciliation`,
584
- owner_type: 'change',
585
- owner_id: change.id,
586
- kind: 'documentation_reconciliation',
587
- path: relative,
588
- content_digest: digestBytes(serialized),
589
- source_refs: [
590
- {
591
- type: 'artifact',
592
- id: normalized.deltaArtifact.id,
593
- relation: 'reconciles',
594
- },
595
- ...payloads.map((artifact) => ({
596
- type: 'artifact', id: artifact.id, relation: 'contains_document',
597
- })),
598
- ],
599
- consumer_refs: [fallbackConsumer],
600
- provenance: { writer: 'change.documentation_reconcile', recorded_at: nowIso() },
601
- metadata: {
602
- document_paths: normalized.reconciliation.documents.map((item) => item.path),
603
- orphan_check: 'pass',
604
- },
605
- }, { rootDir });
606
- const progress = writeProgressInTx(db, change, rootDir, {
607
- delta: normalized.deltaArtifact,
608
- reconciliation: recorded.artifact,
609
- });
610
- rollbackProgress = progress.rollback;
611
- ops.appendEventInTx(db, {
612
- type: 'documentation_reconciliation_recorded',
613
- change_id: change.id,
614
- payload: {
615
- artifact_id: recorded.artifact.id,
616
- digest: recorded.artifact.digest,
617
- document_paths: normalized.reconciliation.documents.map((item) => item.path),
618
- },
619
- });
620
- return {
621
- reconciliation: normalized.reconciliation,
622
- artifact: recorded.artifact,
623
- payload_artifacts: payloads,
624
- progress_artifact: progress.result.artifact,
625
- };
626
- });
627
- } catch (error) {
628
- if (rollbackProgress) rollbackProgress();
629
- rollbackReconciliation();
630
- throw error;
631
- }
632
- }
633
-
634
- function readDeltaAuthority(db, changeId) {
635
- return readCurrentArtifact(db, changeId, 'change_delta');
636
- }
637
-
638
- function readDocumentationAuthority(db, changeId) {
639
- return readCurrentArtifact(db, changeId, 'documentation_reconciliation');
640
- }
641
-
642
- function readAuthorityJson(db, changeId, kind, schema, rootDir) {
643
- const artifact = readCurrentArtifact(db, changeId, kind);
644
- if (!artifact) {
645
- throw new ChangePacketError(
646
- kind === 'change_delta'
647
- ? 'CHANGE_DELTA_AUTHORITY_MISSING'
648
- : 'DOCUMENTATION_RECONCILIATION_MISSING',
649
- `change ${changeId} requires one current ${kind} authority`,
650
- );
651
- }
652
- const resolved = safeFile(rootDir, artifact.path);
653
- if (!fs.existsSync(resolved.file) || !fs.statSync(resolved.file).isFile()
654
- || digestFile(resolved.file) !== artifact.digest) {
655
- throw new ChangePacketError(
656
- kind === 'change_delta'
657
- ? 'CHANGE_DELTA_AUTHORITY_STALE'
658
- : 'DOCUMENTATION_RECONCILIATION_STALE',
659
- `${kind} bytes do not match registered authority`,
660
- );
661
- }
662
- let value;
663
- try { value = JSON.parse(fs.readFileSync(resolved.file, 'utf8')); }
664
- catch (error) {
665
- throw new ChangePacketError(
666
- kind === 'change_delta'
667
- ? 'CHANGE_DELTA_INVALID'
668
- : 'DOCUMENTATION_RECONCILIATION_INVALID',
669
- `${kind} is not valid JSON: ${error.message}`,
670
- );
671
- }
672
- const validator = schema === 'delta' ? validateDeltaSchema : validateDocumentationSchema;
673
- if (!validator(value)) {
674
- throw new ChangePacketError(
675
- schema === 'delta'
676
- ? 'CHANGE_DELTA_INVALID'
677
- : 'DOCUMENTATION_RECONCILIATION_INVALID',
678
- `${kind} no longer satisfies its schema: ${ajv.errorsText(validator.errors)}`,
679
- validator.errors,
680
- );
681
- }
682
- if (value.change_id !== changeId) {
683
- throw new ChangePacketError(
684
- 'CHANGE_PACKET_AUTHORITY_MISMATCH',
685
- `${kind} belongs to ${value.change_id}, not ${changeId}`,
686
- );
687
- }
688
- return { artifact, value };
689
- }
690
-
691
- function loadDelta(db, changeId, { rootDir = process.cwd() } = {}) {
692
- return readAuthorityJson(db, changeId, 'change_delta', 'delta', rootDir);
693
- }
694
-
695
- function loadDocumentationReconciliation(
696
- db,
697
- changeId,
698
- { rootDir = process.cwd() } = {},
699
- ) {
700
- return readAuthorityJson(
701
- db,
702
- changeId,
703
- 'documentation_reconciliation',
704
- 'documentation',
705
- rootDir,
706
- );
707
- }
708
-
709
- function deliveryEntries(db, change, { rootDir = process.cwd() } = {}) {
710
- const delta = loadDelta(db, change.id, { rootDir });
711
- const documentation = loadDocumentationReconciliation(db, change.id, { rootDir });
712
- if (
713
- documentation.value.delta_artifact_id !== delta.artifact.id
714
- || documentation.value.delta_digest !== delta.artifact.digest
715
- ) {
716
- throw new ChangePacketError(
717
- 'DOCUMENTATION_DELTA_AUTHORITY_INVALID',
718
- 'documentation reconciliation is not bound to the current Change delta',
719
- );
720
- }
721
- const entries = [
722
- ...delta.value.mutations.map((item) => ({
723
- kind: 'baseline_specification',
724
- id: item.id,
725
- target_path: item.target_path,
726
- overlay_path: item.overlay_path,
727
- action: item.action,
728
- before_digest: item.before_digest,
729
- after_digest: item.after_digest,
730
- })),
731
- ...documentation.value.documents.map((item, index) => ({
732
- kind: 'documentation',
733
- id: `documentation-${index + 1}`,
734
- target_path: item.path,
735
- overlay_path: item.overlay_path,
736
- action: item.action,
737
- before_digest: item.before_digest,
738
- after_digest: item.after_digest,
739
- })),
740
- ];
741
- const targets = new Set();
742
- for (const entry of entries) {
743
- if (targets.has(entry.target_path)) {
744
- throw new ChangePacketError(
745
- 'CHANGE_PACKET_TARGET_CONFLICT',
746
- `multiple delivery entries target ${entry.target_path}`,
747
- );
748
- }
749
- targets.add(entry.target_path);
750
- const target = safeFile(rootDir, entry.target_path);
751
- const overlay = safeFile(rootDir, entry.overlay_path);
752
- const actualBefore = fs.existsSync(target.file) ? digestFile(target.file) : null;
753
- const actualAfter = fs.existsSync(overlay.file) ? digestFile(overlay.file) : null;
754
- if (actualBefore !== entry.before_digest) {
755
- throw new ChangePacketError(
756
- entry.kind === 'documentation'
757
- ? 'DOCUMENTATION_BASELINE_CONFLICT'
758
- : 'CHANGE_DELTA_BASELINE_CONFLICT',
759
- `delivery before-state changed: ${entry.target_path}`,
760
- { expected: entry.before_digest, actual: actualBefore },
761
- );
762
- }
763
- if (actualAfter !== entry.after_digest) {
764
- throw new ChangePacketError(
765
- entry.kind === 'documentation'
766
- ? 'DOCUMENTATION_EVIDENCE_STALE'
767
- : 'CHANGE_DELTA_PAYLOAD_STALE',
768
- `delivery overlay changed: ${entry.overlay_path}`,
769
- { expected: entry.after_digest, actual: actualAfter },
770
- );
771
- }
772
- }
773
- return {
774
- delta,
775
- documentation,
776
- entries,
777
- baseline_updates: delta.value.mutations.map((item) => item.target_path),
778
- };
779
- }
780
-
781
- module.exports = {
782
- ChangePacketError,
783
- recordDelta,
784
- recordDocumentationReconciliation,
785
- readDeltaAuthority,
786
- readDocumentationAuthority,
787
- loadDelta,
788
- loadDocumentationReconciliation,
789
- deliveryEntries,
790
- };