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.
- package/.ultra-template/changes/abandoned/.gitkeep +0 -0
- package/.ultra-template/contexts/TEMPLATE.md +94 -0
- package/.ultra-template/decisions/.gitkeep +1 -0
- package/.ultra-template/evidence/.gitkeep +1 -0
- package/.ultra-template/north-star.md +46 -0
- package/.ultra-template/research/.gitkeep +1 -0
- package/.ultra-template/specs/discovery.md +2 -2
- package/.ultra-template/tasks.json +3 -0
- package/.ultra-template/test-report.json +27 -0
- package/CHANGELOG.md +107 -1
- package/README.md +217 -579
- package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
- package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
- package/adapters/_shared/file-ops.cjs +76 -2
- package/adapters/_shared/host-profile.cjs +97 -0
- package/adapters/_shared/plugin-core.cjs +204 -0
- package/adapters/_shared/runtime-assets.cjs +31 -96
- package/adapters/claude.js +92 -291
- package/adapters/codex.js +309 -842
- package/adapters/grok.js +143 -1469
- package/adapters/kimi.js +124 -1053
- package/adapters/opencode.js +230 -742
- package/bin/delegate-worker.cjs +328 -0
- package/bin/delegate.cjs +463 -0
- package/bin/install.js +38 -11
- package/docs/ARCHITECTURE.md +125 -216
- package/docs/ARTIFACT-AUTHORITY.md +90 -268
- package/docs/DECISIONS.md +77 -87
- package/docs/PHILOSOPHY.md +208 -0
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +65 -39
- package/docs/RUNTIME-COMPAT-MATRIX.md +140 -155
- package/docs/SKILL-AUTHORING.md +94 -73
- package/docs/WORKFLOW-LIFECYCLE.md +97 -189
- package/hooks/README.md +19 -37
- package/hooks/_common.py +113 -0
- package/hooks/adapters/codex.py +9 -16
- package/hooks/adapters/grok.py +5 -5
- package/hooks/adapters/kimi.py +8 -14
- package/hooks/block_dangerous_commands.py +201 -0
- package/hooks/compact_context.py +65 -0
- package/hooks/mid_workflow_recall.py +25 -0
- package/hooks/post_edit_guard.py +105 -0
- package/hooks/session_context.py +30 -0
- package/package.json +17 -52
- package/skills/ultra-change/SKILL.md +115 -50
- package/skills/ultra-change/references/change-contract.md +68 -3
- package/skills/ultra-delegate/SKILL.md +89 -0
- package/skills/ultra-delegate/references/delegation-contract.md +59 -0
- package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
- package/skills/ultra-deliver/SKILL.md +98 -42
- package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
- package/skills/ultra-dev/SKILL.md +115 -62
- package/skills/ultra-dev/references/debugging.md +22 -0
- package/skills/ultra-domain-modeling/SKILL.md +79 -0
- package/skills/ultra-grilling/SKILL.md +118 -0
- package/skills/ultra-grilling/references/reframing.md +72 -0
- package/skills/ultra-init/SKILL.md +102 -37
- package/skills/ultra-init/references/brownfield-adoption.md +33 -83
- package/skills/ultra-init/scripts/init_project.cjs +84 -0
- package/skills/ultra-plan/SKILL.md +101 -68
- package/skills/ultra-research/SKILL.md +68 -48
- package/skills/ultra-research/references/00-problem-validation.md +5 -5
- package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
- package/skills/ultra-research/references/02-market-assessment.md +5 -4
- package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
- package/skills/ultra-research/references/04-product-strategy.md +3 -3
- package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
- package/skills/ultra-research/references/10-user-personas.md +3 -2
- package/skills/ultra-research/references/11-user-scenarios.md +3 -3
- package/skills/ultra-research/references/20-user-stories.md +4 -3
- package/skills/ultra-research/references/21-features-scope.md +3 -2
- package/skills/ultra-research/references/22-success-metrics.md +3 -3
- package/skills/ultra-research/references/30-architecture-context.md +3 -3
- package/skills/ultra-research/references/31-solution-strategy.md +3 -3
- package/skills/ultra-research/references/32-building-blocks.md +3 -3
- package/skills/ultra-research/references/40-deployment.md +3 -3
- package/skills/ultra-research/references/41-quality-risks.md +3 -3
- package/skills/ultra-research/references/99-synthesis.md +7 -7
- package/skills/ultra-review/SKILL.md +84 -32
- package/skills/ultra-review/references/code.md +17 -0
- package/skills/ultra-review/references/comments.md +12 -0
- package/skills/ultra-review/references/design.md +12 -0
- package/skills/ultra-review/references/errors.md +13 -0
- package/skills/ultra-review/references/spec.md +13 -0
- package/skills/ultra-review/references/tests.md +12 -0
- package/skills/ultra-review/references/worker-packet.md +40 -0
- package/skills/ultra-review/scripts/review_wait.py +26 -15
- package/skills/ultra-status/SKILL.md +61 -36
- package/skills/ultra-tdd/SKILL.md +81 -0
- package/skills/ultra-tdd/references/templates/README.md +20 -0
- package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
- package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
- package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
- package/skills/ultra-tdd/references/test-execution.md +18 -0
- package/skills/ultra-test/SKILL.md +75 -30
- package/skills/ultra-test/references/export-syntax.md +18 -0
- package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
- package/skills/ultra-think/SKILL.md +49 -27
- package/skills/ultra-think/references/autonomy-boundary.md +48 -0
- package/skills/ultra-think/references/interaction-boundary.md +18 -33
- package/.ultra-template/docs/research/README.md +0 -55
- package/.ultra-template/reports/templates/delivery-report.json +0 -17
- package/.ultra-template/reports/templates/test-report.json +0 -18
- package/.ultra-template/tasks/tasks.json +0 -15
- package/.ultra-template/templates/task-context.md +0 -19
- package/adapters/_shared/codex-assets.cjs +0 -725
- package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
- package/adapters/_shared/interaction-contract.cjs +0 -129
- package/adapters/_shared/path-rewrite.cjs +0 -79
- package/adapters/_shared/runtime-native.cjs +0 -132
- package/adapters/_shared/settings-merge.cjs +0 -76
- package/agents/code-reviewer.md +0 -42
- package/agents/debugger.md +0 -40
- package/agents/review-code.md +0 -49
- package/agents/review-comments.md +0 -38
- package/agents/review-coordinator.md +0 -56
- package/agents/review-design.md +0 -40
- package/agents/review-errors.md +0 -43
- package/agents/review-spec.md +0 -39
- package/agents/review-tests.md +0 -40
- package/agents/tdd-runner.md +0 -38
- package/bin/orchestrator.js +0 -383
- package/commands/ultra-change.md +0 -10
- package/commands/ultra-deliver.md +0 -10
- package/commands/ultra-dev.md +0 -10
- package/commands/ultra-doctor.md +0 -10
- package/commands/ultra-init.md +0 -10
- package/commands/ultra-plan.md +0 -10
- package/commands/ultra-research.md +0 -10
- package/commands/ultra-review.md +0 -10
- package/commands/ultra-status.md +0 -10
- package/commands/ultra-test.md +0 -10
- package/commands/ultra-think.md +0 -10
- package/docs/AGENT-CONTEXT.md +0 -191
- package/docs/COMMIT-HASH-BACKFILL.md +0 -51
- package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
- package/docs/ROADMAP.md +0 -101
- package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
- package/hooks/active_task_context.py +0 -152
- package/hooks/context_envelope.py +0 -100
- package/hooks/health_check.py +0 -196
- package/hooks/pre_stop_check.py +0 -66
- package/hooks/runtime_paths.py +0 -396
- package/hooks/subagent_tracker.py +0 -88
- package/hooks/workflow_checkpoint.py +0 -76
- package/hooks/workflow_context.py +0 -48
- package/hooks/workflow_resume.py +0 -84
- package/mcp-server/hook-context.cjs +0 -38
- package/mcp-server/hook-event.cjs +0 -34
- package/mcp-server/lib/archive-journal.cjs +0 -1119
- package/mcp-server/lib/archive-mutation-worker.py +0 -365
- package/mcp-server/lib/artifact-registry.cjs +0 -1232
- package/mcp-server/lib/baseline-workflow.cjs +0 -1193
- package/mcp-server/lib/canonical-json.cjs +0 -19
- package/mcp-server/lib/change-authority.cjs +0 -26
- package/mcp-server/lib/change-packet.cjs +0 -790
- package/mcp-server/lib/change-workflow.cjs +0 -65
- package/mcp-server/lib/context-envelope.cjs +0 -581
- package/mcp-server/lib/context-paths.cjs +0 -142
- package/mcp-server/lib/decision-records.cjs +0 -423
- package/mcp-server/lib/delivery-transaction.cjs +0 -471
- package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
- package/mcp-server/lib/doctor.cjs +0 -627
- package/mcp-server/lib/git-bootstrap.cjs +0 -651
- package/mcp-server/lib/init-project.cjs +0 -1021
- package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
- package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
- package/mcp-server/lib/managed-file-write.cjs +0 -172
- package/mcp-server/lib/plan-checkpoint.cjs +0 -177
- package/mcp-server/lib/plan-publication-worker.cjs +0 -830
- package/mcp-server/lib/plan-store.cjs +0 -794
- package/mcp-server/lib/prd-parser.cjs +0 -83
- package/mcp-server/lib/pricing.cjs +0 -44
- package/mcp-server/lib/project-context.cjs +0 -178
- package/mcp-server/lib/projector.cjs +0 -386
- package/mcp-server/lib/provider-refs.cjs +0 -42
- package/mcp-server/lib/runtime-paths.cjs +0 -1714
- package/mcp-server/lib/runtime-state.cjs +0 -222
- package/mcp-server/lib/safe-project-file.cjs +0 -385
- package/mcp-server/lib/stage-checkpoints.cjs +0 -463
- package/mcp-server/lib/state-authority.cjs +0 -95
- package/mcp-server/lib/state-db.cjs +0 -1580
- package/mcp-server/lib/state-ops.cjs +0 -1428
- package/mcp-server/lib/task-contract.cjs +0 -404
- package/mcp-server/lib/task-expander.cjs +0 -105
- package/mcp-server/lib/task-ledger.cjs +0 -1964
- package/mcp-server/lib/telemetry.cjs +0 -85
- package/mcp-server/lib/topo.cjs +0 -131
- package/mcp-server/lib/ultra-facade.cjs +0 -2684
- package/mcp-server/lib/worker-packet.cjs +0 -358
- package/mcp-server/server.cjs +0 -437
- package/orchestrator/auto-merge.cjs +0 -128
- package/orchestrator/daemon.cjs +0 -264
- package/orchestrator/dispatch-rules.cjs +0 -78
- package/orchestrator/parallel-orchestrator.cjs +0 -903
- package/orchestrator/planner/plan-builder.cjs +0 -125
- package/orchestrator/recovery.cjs +0 -188
- package/orchestrator/session-close-journal-worker.cjs +0 -5
- package/orchestrator/session-close-journal.cjs +0 -1014
- package/orchestrator/session-runner.cjs +0 -1153
- package/orchestrator/worktree-manager.cjs +0 -282
- package/settings.json +0 -7
- package/skills/cc-collab/SKILL.md +0 -42
- package/skills/code-review-expert/SKILL.md +0 -43
- package/skills/code-review-expert/references/correctness-reliability.md +0 -30
- package/skills/code-review-expert/references/design-boundaries.md +0 -26
- package/skills/code-review-expert/references/removal-plan.md +0 -24
- package/skills/codex-collab/SKILL.md +0 -49
- package/skills/codex-collab/evals/evals.json +0 -35
- package/skills/integration-rules/SKILL.md +0 -32
- package/skills/security-rules/SKILL.md +0 -38
- package/skills/testing-rules/SKILL.md +0 -32
- package/skills/ultra-doctor/SKILL.md +0 -41
- package/skills/ultra-plan/references/semantic-preflight.md +0 -20
- package/skills/ultra-verify/SKILL.md +0 -46
- package/skills/ultra-verify/evals/evals.json +0 -46
- package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
- package/skills/ultra-verify/references/evidence-status.md +0 -21
- package/skills/ultra-verify/scripts/verify_wait.py +0 -94
- package/spec/cli-protocol.md +0 -102
- package/spec/mcp-tools.yaml +0 -312
- package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
- package/spec/schemas/change-delta.v1.schema.json +0 -153
- package/spec/schemas/command-manifest.schema.json +0 -29
- package/spec/schemas/context-file.v4.5.schema.json +0 -54
- package/spec/schemas/delivery-report.v1.schema.json +0 -66
- package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
- package/spec/schemas/mcp-tools.schema.json +0 -136
- package/spec/schemas/skill-manifest.schema.json +0 -24
- package/spec/schemas/state-db.sql +0 -641
- package/spec/schemas/task-ledger.v2.schema.json +0 -296
- package/spec/schemas/tasks.v4.5.schema.json +0 -96
- package/spec/schemas/test-report.v1.schema.json +0 -225
- package/ultra-tools/cli.cjs +0 -129
- package/ultra-tools/commands/db.cjs +0 -226
- package/ultra-tools/commands/legacy-memory.cjs +0 -159
- package/ultra-tools/commands/migrate.cjs +0 -1386
- package/ultra-tools/commands/session.cjs +0 -213
- package/ultra-tools/commands/status.cjs +0 -559
- package/ultra-tools/commands/system.cjs +0 -498
- package/ultra-tools/commands/task.cjs +0 -105
|
@@ -1,404 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Persist host-derived child tasks atomically. Model judgment stays with the
|
|
4
|
-
// current host; this module validates ownership and the database contract.
|
|
5
|
-
|
|
6
|
-
const Ajv = require('ajv/dist/2020');
|
|
7
|
-
const ops = require('./state-ops.cjs');
|
|
8
|
-
const changes = require('./change-workflow.cjs');
|
|
9
|
-
const CHILDREN_SHAPE = Object.freeze({
|
|
10
|
-
type: 'object',
|
|
11
|
-
required: ['children'],
|
|
12
|
-
properties: {
|
|
13
|
-
children: {
|
|
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 validateChildren = ajv.compile(CHILDREN_SHAPE);
|
|
35
|
-
|
|
36
|
-
class TaskExpandError extends Error {
|
|
37
|
-
constructor(code, message, cause) {
|
|
38
|
-
super(message);
|
|
39
|
-
this.name = 'TaskExpandError';
|
|
40
|
-
this.code = code;
|
|
41
|
-
if (cause) this.cause = cause;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function expandTask(db, {
|
|
46
|
-
id, children, rootDir = process.cwd(),
|
|
47
|
-
} = {}) {
|
|
48
|
-
if (!id) throw new TaskExpandError('VALIDATION_ERROR', 'id required');
|
|
49
|
-
|
|
50
|
-
const parent = ops.readTask(db, id);
|
|
51
|
-
if (!parent) throw new TaskExpandError('TASK_NOT_FOUND', `no task ${id}`);
|
|
52
|
-
if (parent.status === 'expanded') {
|
|
53
|
-
throw new TaskExpandError('ALREADY_EXPANDED', `task ${id} is already expanded`);
|
|
54
|
-
}
|
|
55
|
-
changes.assertTaskCreationAllowed(db, { change_id: parent.change_id }, { rootDir });
|
|
56
|
-
if (!validateChildren({ children })) {
|
|
57
|
-
throw new TaskExpandError(
|
|
58
|
-
'INVALID_OUTPUT',
|
|
59
|
-
`child task graph failed schema: ${ajv.errorsText(validateChildren.errors)}`,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const seen = new Set();
|
|
64
|
-
const normalized = children.map((c) => {
|
|
65
|
-
if (seen.has(c.id)) {
|
|
66
|
-
throw new TaskExpandError('INVALID_OUTPUT', `duplicate child id "${c.id}"`);
|
|
67
|
-
}
|
|
68
|
-
seen.add(c.id);
|
|
69
|
-
return {
|
|
70
|
-
id: c.id,
|
|
71
|
-
title: c.title,
|
|
72
|
-
type: c.type,
|
|
73
|
-
priority: c.priority,
|
|
74
|
-
complexity: c.complexity ?? null,
|
|
75
|
-
deps: Array.isArray(c.deps) ? c.deps : [],
|
|
76
|
-
files_modified: Array.isArray(c.files_modified) ? c.files_modified : [],
|
|
77
|
-
parent_id: parent.id,
|
|
78
|
-
tag: parent.tag,
|
|
79
|
-
change_id: parent.change_id,
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return ops.tx(db, () => {
|
|
84
|
-
const admission = changes.assertTaskCreationAllowed(
|
|
85
|
-
db,
|
|
86
|
-
{ change_id: parent.change_id },
|
|
87
|
-
{ rootDir },
|
|
88
|
-
);
|
|
89
|
-
for (const child of normalized) {
|
|
90
|
-
ops.createTask(db, child);
|
|
91
|
-
}
|
|
92
|
-
ops.patchTask(db, parent.id, { status: 'expanded' });
|
|
93
|
-
return {
|
|
94
|
-
parent_id: parent.id,
|
|
95
|
-
children: normalized,
|
|
96
|
-
diagnostics: admission.diagnostics,
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
module.exports = {
|
|
102
|
-
expandTask,
|
|
103
|
-
TaskExpandError,
|
|
104
|
-
CHILDREN_SHAPE,
|
|
105
|
-
};
|