ultra-builder-pro-cli 0.24.2 → 0.25.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 (40) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/adapters/claude.js +104 -0
  3. package/adapters/codex.js +363 -40
  4. package/adapters/grok.js +1254 -58
  5. package/adapters/kimi.js +682 -13
  6. package/adapters/opencode.js +132 -11
  7. package/docs/AGENT-CONTEXT.md +14 -0
  8. package/docs/ARTIFACT-AUTHORITY.md +13 -0
  9. package/docs/COMMIT-HASH-BACKFILL.md +25 -18
  10. package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
  11. package/mcp-server/lib/artifact-registry.cjs +181 -51
  12. package/mcp-server/lib/baseline-workflow.cjs +160 -23
  13. package/mcp-server/lib/change-workflow.cjs +21 -16
  14. package/mcp-server/lib/context-envelope.cjs +124 -51
  15. package/mcp-server/lib/decision-records.cjs +164 -78
  16. package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
  17. package/mcp-server/lib/legacy-change-workflow.cjs +294 -71
  18. package/mcp-server/lib/managed-file-write.cjs +56 -0
  19. package/mcp-server/lib/prd-parser.cjs +2 -2
  20. package/mcp-server/lib/stage-checkpoints.cjs +163 -9
  21. package/mcp-server/lib/state-db.cjs +186 -8
  22. package/mcp-server/lib/state-ops.cjs +214 -31
  23. package/mcp-server/lib/task-contract.cjs +404 -0
  24. package/mcp-server/lib/task-expander.cjs +2 -2
  25. package/mcp-server/lib/task-ledger.cjs +175 -62
  26. package/mcp-server/lib/ultra-facade.cjs +1786 -169
  27. package/mcp-server/lib/worker-packet.cjs +2 -2
  28. package/mcp-server/server.cjs +23 -4
  29. package/orchestrator/parallel-orchestrator.cjs +114 -27
  30. package/orchestrator/session-runner.cjs +25 -136
  31. package/package.json +1 -1
  32. package/skills/ultra-change/SKILL.md +6 -1
  33. package/skills/ultra-dev/SKILL.md +9 -4
  34. package/skills/ultra-plan/SKILL.md +13 -4
  35. package/skills/ultra-plan/references/semantic-preflight.md +6 -4
  36. package/spec/mcp-tools.yaml +114 -9
  37. package/spec/schemas/context-file.v4.5.schema.json +2 -2
  38. package/spec/schemas/state-db.sql +10 -6
  39. package/spec/schemas/task-ledger.v2.schema.json +23 -8
  40. package/spec/schemas/tasks.v4.5.schema.json +34 -12
@@ -0,0 +1,404 @@
1
+ 'use strict';
2
+
3
+ const TASK_DURABLE_STATUSES = Object.freeze([
4
+ 'pending',
5
+ 'completed',
6
+ 'blocked',
7
+ 'expanded',
8
+ ]);
9
+
10
+ const TASK_CONTRACT_DEFINE_FIELDS = Object.freeze([
11
+ 'id',
12
+ 'title',
13
+ 'type',
14
+ 'priority',
15
+ 'complexity',
16
+ 'estimated_days',
17
+ 'deps',
18
+ 'files_modified',
19
+ 'trace_to',
20
+ 'outcome',
21
+ 'slice_kind',
22
+ 'public_seam',
23
+ 'verification_command',
24
+ 'acceptance',
25
+ 'context_refs',
26
+ 'docs_impact',
27
+ 'ownership',
28
+ 'change_id',
29
+ 'parent_id',
30
+ ]);
31
+
32
+ const TASK_CONTRACT_PATCH_FIELDS = Object.freeze([
33
+ 'title',
34
+ 'type',
35
+ 'priority',
36
+ 'complexity',
37
+ 'estimated_days',
38
+ 'deps',
39
+ 'files_modified',
40
+ 'trace_to',
41
+ 'outcome',
42
+ 'slice_kind',
43
+ 'public_seam',
44
+ 'verification_command',
45
+ 'acceptance',
46
+ 'context_refs',
47
+ 'docs_impact',
48
+ 'ownership',
49
+ ]);
50
+
51
+ const DURABLE_TASK_FIELDS = Object.freeze([
52
+ 'id',
53
+ 'title',
54
+ 'type',
55
+ 'priority',
56
+ 'complexity',
57
+ 'estimated_days',
58
+ 'status',
59
+ 'deps',
60
+ 'files_modified',
61
+ 'stale',
62
+ 'trace_to',
63
+ 'outcome',
64
+ 'slice_kind',
65
+ 'public_seam',
66
+ 'verification_command',
67
+ 'acceptance',
68
+ 'context_refs',
69
+ 'docs_impact',
70
+ 'ownership',
71
+ 'change_id',
72
+ 'parent_id',
73
+ ]);
74
+
75
+ const DURABLE_TASK_ENTRY_FIELDS = Object.freeze([
76
+ ...DURABLE_TASK_FIELDS,
77
+ 'revision',
78
+ 'parent_digest',
79
+ 'digest',
80
+ ]);
81
+
82
+ const TASK_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
83
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
84
+
85
+ class TaskContractError extends Error {
86
+ constructor(message, field) {
87
+ super(message);
88
+ this.name = 'TaskContractError';
89
+ this.code = 'VALIDATION_ERROR';
90
+ if (field) this.details = { field };
91
+ }
92
+ }
93
+
94
+ function isObject(value) {
95
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
96
+ }
97
+
98
+ function fail(field, message) {
99
+ throw new TaskContractError(`${field} ${message}`, field);
100
+ }
101
+
102
+ function assertObject(value, field) {
103
+ if (!isObject(value)) fail(field, 'must be an object');
104
+ }
105
+
106
+ function assertKnownFields(value, allowed, field = 'task') {
107
+ assertObject(value, field);
108
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
109
+ if (unknown.length > 0) {
110
+ fail(`${field}.${unknown[0]}`, 'is not allowed');
111
+ }
112
+ }
113
+
114
+ function normalizeRequiredText(value, field, { minimum = 1, pattern = null } = {}) {
115
+ if (typeof value !== 'string') fail(field, 'must be a string');
116
+ const normalized = value.trim();
117
+ if (normalized.length < minimum) {
118
+ fail(field, `must contain at least ${minimum} characters`);
119
+ }
120
+ if (pattern && !pattern.test(normalized)) fail(field, 'has an invalid format');
121
+ return normalized;
122
+ }
123
+
124
+ function normalizeOptionalText(value, field) {
125
+ if (typeof value !== 'string') fail(field, 'must be a string');
126
+ return value.trim();
127
+ }
128
+
129
+ function normalizeNullableText(value, field) {
130
+ return value === null ? null : normalizeOptionalText(value, field);
131
+ }
132
+
133
+ function normalizeEnum(value, field, allowed, { nullable = false } = {}) {
134
+ if (value === null && nullable) return null;
135
+ if (typeof value !== 'string' || !allowed.includes(value)) {
136
+ fail(field, `must be one of ${allowed.join(', ')}${nullable ? ', or null' : ''}`);
137
+ }
138
+ return value;
139
+ }
140
+
141
+ function normalizeVocabulary(value, field, { nullable = false, maximum = 80 } = {}) {
142
+ if (value === null && nullable) return null;
143
+ const normalized = normalizeRequiredText(value, field);
144
+ if (normalized.length > maximum) {
145
+ fail(field, `must contain at most ${maximum} characters`);
146
+ }
147
+ return normalized;
148
+ }
149
+
150
+ function normalizeComplexity(value) {
151
+ if (value === null) return null;
152
+ if (!Number.isInteger(value) || value < 1 || value > 10) {
153
+ fail('task.complexity', 'must be an integer from 1 through 10, or null');
154
+ }
155
+ return value;
156
+ }
157
+
158
+ function normalizeEstimatedDays(value) {
159
+ if (value === null) return null;
160
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
161
+ fail('task.estimated_days', 'must be a finite number greater than zero, or null');
162
+ }
163
+ return value;
164
+ }
165
+
166
+ function normalizeStringArray(value, field) {
167
+ if (!Array.isArray(value)) fail(field, 'must be an array of non-empty strings');
168
+ return value.map((item, index) => (
169
+ normalizeRequiredText(item, `${field}[${index}]`)
170
+ ));
171
+ }
172
+
173
+ function normalizeAcceptance(value) {
174
+ if (!Array.isArray(value)) fail('task.acceptance', 'must be an array');
175
+ const ids = new Set();
176
+ return value.map((item, index) => {
177
+ const field = `task.acceptance[${index}]`;
178
+ assertKnownFields(item, ['id', 'criterion', 'verification'], field);
179
+ const id = normalizeRequiredText(item.id, `${field}.id`);
180
+ if (ids.has(id)) fail(`${field}.id`, 'must be unique');
181
+ ids.add(id);
182
+ return {
183
+ id,
184
+ criterion: normalizeRequiredText(item.criterion, `${field}.criterion`),
185
+ verification: normalizeRequiredText(item.verification, `${field}.verification`),
186
+ };
187
+ });
188
+ }
189
+
190
+ function normalizeContextRefs(value) {
191
+ if (!Array.isArray(value)) fail('task.context_refs', 'must be an array');
192
+ return value.map((item, index) => {
193
+ const field = `task.context_refs[${index}]`;
194
+ assertKnownFields(item, [
195
+ 'ref',
196
+ 'reason',
197
+ 'kind',
198
+ 'required',
199
+ 'freshness_policy',
200
+ 'expected_digest',
201
+ 'digest',
202
+ 'anchor',
203
+ 'scope',
204
+ ], field);
205
+ const expectedDigest = item.expected_digest ?? item.digest ?? null;
206
+ if (item.expected_digest !== undefined && item.digest !== undefined
207
+ && item.expected_digest !== item.digest) {
208
+ fail(`${field}.expected_digest`, 'must match digest when both are supplied');
209
+ }
210
+ if (expectedDigest !== null
211
+ && (typeof expectedDigest !== 'string' || !SHA256_PATTERN.test(expectedDigest))) {
212
+ fail(`${field}.expected_digest`, 'must be a lowercase sha256 digest');
213
+ }
214
+ if (item.required !== undefined && typeof item.required !== 'boolean') {
215
+ fail(`${field}.required`, 'must be a boolean');
216
+ }
217
+ const kind = item.kind === undefined
218
+ ? 'source'
219
+ : normalizeEnum(item.kind, `${field}.kind`, ['spec', 'source', 'test', 'docs', 'external']);
220
+ const freshnessPolicy = item.freshness_policy === undefined
221
+ ? (expectedDigest ? 'digest' : 'existence')
222
+ : normalizeEnum(
223
+ item.freshness_policy,
224
+ `${field}.freshness_policy`,
225
+ ['digest', 'existence', 'advisory'],
226
+ );
227
+ const normalized = {
228
+ ref: normalizeRequiredText(item.ref, `${field}.ref`),
229
+ reason: normalizeRequiredText(item.reason, `${field}.reason`),
230
+ kind,
231
+ required: item.required === undefined ? true : item.required,
232
+ freshness_policy: freshnessPolicy,
233
+ };
234
+ if (expectedDigest !== null) normalized.expected_digest = expectedDigest;
235
+ if (item.anchor !== undefined) {
236
+ normalized.anchor = normalizeRequiredText(item.anchor, `${field}.anchor`);
237
+ }
238
+ if (item.scope !== undefined) {
239
+ normalized.scope = normalizeRequiredText(item.scope, `${field}.scope`);
240
+ }
241
+ return normalized;
242
+ });
243
+ }
244
+
245
+ function normalizeDocsImpact(value) {
246
+ assertKnownFields(value, ['status', 'files', 'rationale'], 'task.docs_impact');
247
+ const status = value.status === undefined
248
+ ? 'unknown'
249
+ : normalizeEnum(value.status, 'task.docs_impact.status', ['unknown', 'required', 'none']);
250
+ const files = value.files === undefined
251
+ ? []
252
+ : normalizeStringArray(value.files, 'task.docs_impact.files');
253
+ let rationale = null;
254
+ if (value.rationale !== undefined) {
255
+ rationale = value.rationale === null
256
+ ? null
257
+ : normalizeOptionalText(value.rationale, 'task.docs_impact.rationale');
258
+ }
259
+ return { status, files, rationale };
260
+ }
261
+
262
+ function normalizeOwnership(value) {
263
+ assertKnownFields(value, ['owner', 'reviewers'], 'task.ownership');
264
+ const owner = value.owner === undefined
265
+ ? null
266
+ : normalizeNullableText(value.owner, 'task.ownership.owner');
267
+ const reviewers = value.reviewers === undefined
268
+ ? []
269
+ : normalizeStringArray(value.reviewers, 'task.ownership.reviewers');
270
+ return { owner, reviewers };
271
+ }
272
+
273
+ function normalizeField(field, value) {
274
+ switch (field) {
275
+ case 'id':
276
+ return normalizeRequiredText(value, 'task.id', { pattern: TASK_ID_PATTERN });
277
+ case 'title':
278
+ return normalizeRequiredText(value, 'task.title', { minimum: 3 });
279
+ case 'type':
280
+ return normalizeVocabulary(value, 'task.type');
281
+ case 'priority':
282
+ return normalizeVocabulary(value, 'task.priority');
283
+ case 'complexity':
284
+ return normalizeComplexity(value);
285
+ case 'estimated_days':
286
+ return normalizeEstimatedDays(value);
287
+ case 'deps':
288
+ case 'files_modified':
289
+ return normalizeStringArray(value, `task.${field}`);
290
+ case 'slice_kind':
291
+ return normalizeVocabulary(value, 'task.slice_kind', { nullable: true });
292
+ case 'trace_to':
293
+ case 'outcome':
294
+ case 'public_seam':
295
+ case 'verification_command':
296
+ return normalizeOptionalText(value, `task.${field}`);
297
+ case 'acceptance':
298
+ return normalizeAcceptance(value);
299
+ case 'context_refs':
300
+ return normalizeContextRefs(value);
301
+ case 'docs_impact':
302
+ return normalizeDocsImpact(value);
303
+ case 'ownership':
304
+ return normalizeOwnership(value);
305
+ case 'change_id':
306
+ case 'parent_id':
307
+ return normalizeRequiredText(value, `task.${field}`);
308
+ default:
309
+ fail(`task.${field}`, 'is not a Task Contract field');
310
+ }
311
+ }
312
+
313
+ function normalizeFields(value, allowed, required = []) {
314
+ assertKnownFields(value, allowed);
315
+ for (const field of required) {
316
+ if (!Object.prototype.hasOwnProperty.call(value, field)) {
317
+ fail(`task.${field}`, 'is required');
318
+ }
319
+ }
320
+ return Object.fromEntries(
321
+ Object.entries(value).map(([field, fieldValue]) => [
322
+ field,
323
+ normalizeField(field, fieldValue),
324
+ ]),
325
+ );
326
+ }
327
+
328
+ function normalizeTaskDefinition(value) {
329
+ return normalizeFields(
330
+ value,
331
+ TASK_CONTRACT_DEFINE_FIELDS,
332
+ ['id', 'title', 'type', 'priority'],
333
+ );
334
+ }
335
+
336
+ function normalizeTaskId(value) {
337
+ return normalizeField('id', value);
338
+ }
339
+
340
+ function normalizeTaskPatch(value) {
341
+ return normalizeFields(value, TASK_CONTRACT_PATCH_FIELDS);
342
+ }
343
+
344
+ function validateDurableTask(value, { metadata = true } = {}) {
345
+ assertKnownFields(value, DURABLE_TASK_ENTRY_FIELDS, 'task ledger entry');
346
+ for (const field of [
347
+ 'id',
348
+ 'title',
349
+ 'type',
350
+ 'priority',
351
+ 'status',
352
+ 'deps',
353
+ 'files_modified',
354
+ 'stale',
355
+ 'acceptance',
356
+ 'context_refs',
357
+ 'docs_impact',
358
+ 'ownership',
359
+ ...(metadata ? ['revision', 'parent_digest', 'digest'] : []),
360
+ ]) {
361
+ if (!Object.prototype.hasOwnProperty.call(value, field)) {
362
+ fail(`task.${field}`, 'is required');
363
+ }
364
+ }
365
+ for (const field of DURABLE_TASK_FIELDS) {
366
+ if (!Object.prototype.hasOwnProperty.call(value, field)) continue;
367
+ if (value[field] === null
368
+ && ['complexity', 'estimated_days', 'slice_kind'].includes(field)) {
369
+ fail(`task.${field}`, 'must be omitted instead of null in a durable ledger entry');
370
+ }
371
+ if (field === 'status') {
372
+ normalizeEnum(value.status, 'task.status', TASK_DURABLE_STATUSES);
373
+ } else if (field === 'stale') {
374
+ if (typeof value.stale !== 'boolean') fail('task.stale', 'must be a boolean');
375
+ } else {
376
+ normalizeField(field, value[field]);
377
+ }
378
+ }
379
+ if (metadata) {
380
+ if (!Number.isInteger(value.revision) || value.revision < 1) {
381
+ fail('task.revision', 'must be an integer greater than zero');
382
+ }
383
+ if (value.parent_digest !== null
384
+ && (typeof value.parent_digest !== 'string' || !SHA256_PATTERN.test(value.parent_digest))) {
385
+ fail('task.parent_digest', 'must be null or a lowercase sha256 digest');
386
+ }
387
+ if (typeof value.digest !== 'string' || !SHA256_PATTERN.test(value.digest)) {
388
+ fail('task.digest', 'must be a lowercase sha256 digest');
389
+ }
390
+ }
391
+ return value;
392
+ }
393
+
394
+ module.exports = {
395
+ DURABLE_TASK_FIELDS,
396
+ TASK_CONTRACT_DEFINE_FIELDS,
397
+ TASK_CONTRACT_PATCH_FIELDS,
398
+ TASK_DURABLE_STATUSES,
399
+ TaskContractError,
400
+ normalizeTaskDefinition,
401
+ normalizeTaskId,
402
+ normalizeTaskPatch,
403
+ validateDurableTask,
404
+ };
@@ -19,8 +19,8 @@ const CHILDREN_SHAPE = Object.freeze({
19
19
  properties: {
20
20
  id: { type: 'string', pattern: '^[a-zA-Z0-9_\\-]+$' },
21
21
  title: { type: 'string', minLength: 3, maxLength: 200 },
22
- type: { type: 'string', enum: ['architecture', 'feature', 'bugfix'] },
23
- priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
22
+ type: { type: 'string', minLength: 1, maxLength: 80 },
23
+ priority: { type: 'string', minLength: 1, maxLength: 80 },
24
24
  complexity: { type: 'integer', minimum: 1, maximum: 10 },
25
25
  deps: { type: 'array', items: { type: 'string' } },
26
26
  files_modified: { type: 'array', items: { type: 'string' } },