ultra-builder-pro-cli 0.14.0 → 0.16.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.
- package/.ultra-template/docs/research/README.md +11 -7
- package/.ultra-template/specs/architecture.md +36 -36
- package/.ultra-template/specs/discovery.md +46 -33
- package/.ultra-template/specs/product.md +38 -29
- package/.ultra-template/specs/research-distillate.md +48 -0
- package/.ultra-template/tasks/contexts/TEMPLATE.md +11 -28
- package/CHANGELOG.md +77 -1
- package/README.md +46 -24
- package/adapters/_shared/codex-assets.cjs +6 -2
- package/adapters/_shared/file-ops.cjs +6 -2
- package/adapters/_shared/handbook.cjs +2 -1
- package/adapters/claude.js +3 -2
- package/adapters/codex.js +1 -0
- package/adapters/kimi.js +3 -2
- package/adapters/opencode.js +3 -2
- package/agents/review-coordinator.md +9 -6
- package/commands/ultra-change.md +1 -1
- package/commands/ultra-plan.md +1 -1
- package/commands/ultra-research.md +1 -1
- package/commands/ultra-think.md +2 -2
- package/docs/AGENT-CONTEXT.md +43 -19
- package/docs/ARCHITECTURE.md +54 -13
- package/docs/PLAN.zh-CN.md +4 -2
- package/docs/ROADMAP.md +10 -4
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +33 -29
- package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
- package/docs/WORKFLOW-LIFECYCLE.md +342 -0
- package/hooks/README.md +6 -1
- package/hooks/health_check.py +12 -3
- package/hooks/subagent_tracker.py +52 -77
- package/mcp-server/hook-event.cjs +34 -0
- package/mcp-server/lib/archive-journal.cjs +17 -5
- package/mcp-server/lib/baseline-workflow.cjs +158 -18
- package/mcp-server/lib/change-workflow.cjs +598 -85
- package/mcp-server/lib/context-spine.cjs +305 -25
- package/mcp-server/lib/decision-dialogue.cjs +713 -0
- package/mcp-server/lib/doctor.cjs +42 -3
- package/mcp-server/lib/init-project.cjs +210 -16
- package/mcp-server/lib/plan-store.cjs +4 -1
- package/mcp-server/lib/pricing.cjs +7 -10
- package/mcp-server/lib/project-breadcrumb.cjs +15 -0
- package/mcp-server/lib/projector.cjs +57 -3
- package/mcp-server/lib/spec-learning.cjs +79 -2
- package/mcp-server/lib/state-db.cjs +168 -8
- package/mcp-server/lib/state-ops.cjs +160 -9
- package/mcp-server/lib/workflow-state.cjs +2305 -0
- package/mcp-server/server.cjs +215 -15
- package/orchestrator/daemon.cjs +1 -2
- package/orchestrator/dispatch-rules.cjs +5 -31
- package/orchestrator/planner/plan-builder.cjs +21 -12
- package/package.json +13 -3
- package/skills/ultra-change/SKILL.md +94 -80
- package/skills/ultra-change/references/change-contract.md +36 -0
- package/skills/ultra-deliver/SKILL.md +76 -79
- package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
- package/skills/ultra-dev/SKILL.md +62 -59
- package/skills/ultra-doctor/SKILL.md +61 -82
- package/skills/ultra-init/SKILL.md +101 -103
- package/skills/ultra-init/references/brownfield-adoption.md +2 -2
- package/skills/ultra-plan/SKILL.md +99 -85
- package/skills/ultra-plan/references/semantic-preflight.md +18 -0
- package/skills/ultra-research/SKILL.md +102 -60
- package/skills/ultra-research/references/00-problem-validation.md +36 -0
- package/skills/ultra-research/references/01-opportunity-discovery.md +33 -0
- package/skills/ultra-research/references/02-market-assessment.md +30 -0
- package/skills/ultra-research/references/03-competitive-landscape.md +31 -0
- package/skills/ultra-research/references/04-product-strategy.md +32 -0
- package/skills/ultra-research/references/05-assumptions-validation.md +31 -0
- package/skills/ultra-research/references/10-user-personas.md +29 -0
- package/skills/ultra-research/references/11-user-scenarios.md +28 -0
- package/skills/ultra-research/references/20-user-stories.md +30 -0
- package/skills/ultra-research/references/21-features-scope.md +29 -0
- package/skills/ultra-research/references/22-success-metrics.md +28 -0
- package/skills/ultra-research/references/30-architecture-context.md +30 -0
- package/skills/ultra-research/references/31-solution-strategy.md +28 -0
- package/skills/ultra-research/references/32-building-blocks.md +28 -0
- package/skills/ultra-research/references/40-deployment.md +29 -0
- package/skills/ultra-research/references/41-quality-risks.md +28 -0
- package/skills/ultra-research/references/99-synthesis.md +35 -0
- package/skills/ultra-review/SKILL.md +63 -73
- package/skills/ultra-review/references/review-modes.md +27 -0
- package/skills/ultra-review/references/unified-schema.md +25 -0
- package/skills/ultra-review/scripts/review_wait.py +40 -3
- package/skills/ultra-status/SKILL.md +41 -41
- package/skills/ultra-test/SKILL.md +54 -55
- package/skills/ultra-think/SKILL.md +26 -12
- package/skills/ultra-think/references/decision-dialogue.md +130 -0
- package/skills/ultra-verify/SKILL.md +1 -1
- package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
- package/spec/cli-protocol.md +17 -3
- package/spec/mcp-tools.yaml +683 -64
- package/spec/schemas/baseline-reconciliation.v1.schema.json +73 -0
- package/spec/schemas/delivery-report.v1.schema.json +111 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +140 -1
- package/spec/schemas/tasks.v4.5.schema.json +8 -1
- package/spec/schemas/test-report.v1.schema.json +194 -0
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/migrate.cjs +3 -3
- package/ultra-tools/commands/status.cjs +228 -12
- package/.ultra-template/delivery-report.json +0 -9
- package/.ultra-template/test-report.json +0 -8
- package/adapters/_shared/tests/file-ops.test.cjs +0 -54
- package/adapters/_shared/tests/frontmatter.test.cjs +0 -47
- package/adapters/_shared/tests/handbook.test.cjs +0 -101
- package/adapters/_shared/tests/path-rewrite.test.cjs +0 -60
- package/adapters/_shared/tests/provenance.test.cjs +0 -133
- package/adapters/_shared/tests/resolve-target.test.cjs +0 -75
- package/adapters/_shared/tests/runtime-assets.test.cjs +0 -149
- package/adapters/_shared/tests/settings-merge.test.cjs +0 -85
- package/adapters/_shared/tests/validate.test.cjs +0 -30
- package/adapters/tests/claude.test.cjs +0 -138
- package/adapters/tests/codex-hook.test.cjs +0 -142
- package/adapters/tests/codex.test.cjs +0 -430
- package/adapters/tests/kimi-hook.test.cjs +0 -149
- package/adapters/tests/kimi.test.cjs +0 -259
- package/adapters/tests/opencode.test.cjs +0 -357
- package/mcp-server/lib/archive-journal.test.cjs +0 -35
- package/mcp-server/lib/baseline-workflow.test.cjs +0 -477
- package/mcp-server/lib/change-workflow.test.cjs +0 -971
- package/mcp-server/lib/context-spine.test.cjs +0 -93
- package/mcp-server/lib/doctor.test.cjs +0 -148
- package/mcp-server/lib/plan-store.test.cjs +0 -132
- package/mcp-server/lib/prd-parser.test.cjs +0 -82
- package/mcp-server/lib/project-breadcrumb.test.cjs +0 -112
- package/mcp-server/lib/runtime-state.test.cjs +0 -92
- package/mcp-server/lib/spec-learning.test.cjs +0 -67
- package/mcp-server/lib/state-db.test.cjs +0 -314
- package/mcp-server/lib/task-expander.test.cjs +0 -169
- package/mcp-server/lib/topo.test.cjs +0 -158
- package/mcp-server/tests/circuit-breaker.test.cjs +0 -125
- package/mcp-server/tests/commit-hash-flow.test.cjs +0 -117
- package/mcp-server/tests/concurrency-update.test.cjs +0 -96
- package/mcp-server/tests/concurrency.test.cjs +0 -112
- package/mcp-server/tests/db-ops.test.cjs +0 -121
- package/mcp-server/tests/init-project.test.cjs +0 -393
- package/mcp-server/tests/migrate.test.cjs +0 -232
- package/mcp-server/tests/projector.test.cjs +0 -162
- package/mcp-server/tests/server-contract.test.cjs +0 -1467
- package/mcp-server/tests/staleness.test.cjs +0 -190
- package/mcp-server/tests/state-db.test.cjs +0 -106
- package/mcp-server/tests/state-ops.test.cjs +0 -449
- package/mcp-server/tests/tagged-tasks.test.cjs +0 -163
- package/mcp-server/tests/telemetry.test.cjs +0 -219
- package/orchestrator/planner/plan-builder.test.cjs +0 -165
- package/orchestrator/tests/auto-merge.test.cjs +0 -216
- package/orchestrator/tests/daemon.test.cjs +0 -311
- package/orchestrator/tests/dispatch-rules.test.cjs +0 -124
- package/orchestrator/tests/parallel-orchestrator.test.cjs +0 -233
- package/orchestrator/tests/phase-8b-gate.test.cjs +0 -183
- package/orchestrator/tests/recovery.test.cjs +0 -190
- package/orchestrator/tests/session-runner.test.cjs +0 -314
- package/orchestrator/tests/skill-miner.test.cjs +0 -147
- package/orchestrator/tests/worktree-manager.test.cjs +0 -148
- package/skills/ultra-research/references/architecture.md +0 -38
- package/skills/ultra-research/references/problem-product.md +0 -39
- package/skills/ultra-research/references/quality-delivery.md +0 -34
- package/skills/ultra-research/references/synthesis.md +0 -38
- package/skills/ultra-research/references/users-scope.md +0 -40
- package/ultra-tools/legacy-memory.test.cjs +0 -80
- package/ultra-tools/status-command.test.cjs +0 -181
- package/ultra-tools/system-command.test.cjs +0 -309
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Phase 5.3 — Task staleness consumer:
|
|
4
|
-
// • markTasksStaleBySpecSections: UPDATE pending tasks whose trace_to
|
|
5
|
-
// matches a changed spec section → stale=1 + task_stale_marked event.
|
|
6
|
-
// • consumeSpecChangedEvents: pulls unprocessed spec_changed events from
|
|
7
|
-
// the event stream and applies the rule above, advancing a cursor.
|
|
8
|
-
|
|
9
|
-
const test = require('node:test');
|
|
10
|
-
const assert = require('node:assert/strict');
|
|
11
|
-
const fs = require('node:fs');
|
|
12
|
-
const os = require('node:os');
|
|
13
|
-
const path = require('node:path');
|
|
14
|
-
|
|
15
|
-
const { initStateDb, closeStateDb } = require('../lib/state-db.cjs');
|
|
16
|
-
const ops = require('../lib/state-ops.cjs');
|
|
17
|
-
const projector = require('../lib/projector.cjs');
|
|
18
|
-
|
|
19
|
-
function freshDb() {
|
|
20
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-stale-'));
|
|
21
|
-
const file = path.join(dir, 'state.db');
|
|
22
|
-
const init = initStateDb(file);
|
|
23
|
-
return { dir, db: init.db };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function teardown(dir, db) {
|
|
27
|
-
try { closeStateDb(db); } catch (_) { /* ignore */ }
|
|
28
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* ignore */ }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function seed(db, id, { trace_to, status = 'pending' } = {}) {
|
|
32
|
-
ops.createTask(db, { id, title: `task ${id}`, type: 'feature', priority: 'P2', trace_to });
|
|
33
|
-
if (status !== 'pending') ops.patchTask(db, id, { status });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
test('markTasksStaleBySpecSections: marks pending tasks with matching trace_to', () => {
|
|
37
|
-
const { dir, db } = freshDb();
|
|
38
|
-
try {
|
|
39
|
-
seed(db, 't-prod', { trace_to: 'product' });
|
|
40
|
-
seed(db, 't-arch', { trace_to: 'arch' });
|
|
41
|
-
seed(db, 't-exec', { trace_to: 'exec' });
|
|
42
|
-
|
|
43
|
-
const result = ops.markTasksStaleBySpecSections(db, ['product']);
|
|
44
|
-
assert.equal(result.marked_count, 1);
|
|
45
|
-
assert.deepEqual(result.marked_ids, ['t-prod']);
|
|
46
|
-
|
|
47
|
-
assert.equal(ops.readTask(db, 't-prod').stale, true);
|
|
48
|
-
assert.equal(ops.readTask(db, 't-arch').stale, false);
|
|
49
|
-
assert.equal(ops.readTask(db, 't-exec').stale, false);
|
|
50
|
-
} finally { teardown(dir, db); }
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('markTasksStaleBySpecSections: multi-section update', () => {
|
|
54
|
-
const { dir, db } = freshDb();
|
|
55
|
-
try {
|
|
56
|
-
seed(db, 't-a', { trace_to: 'product' });
|
|
57
|
-
seed(db, 't-b', { trace_to: 'arch' });
|
|
58
|
-
seed(db, 't-c', { trace_to: 'exec' });
|
|
59
|
-
|
|
60
|
-
const result = ops.markTasksStaleBySpecSections(db, ['product', 'arch']);
|
|
61
|
-
assert.equal(result.marked_count, 2);
|
|
62
|
-
assert.ok(result.marked_ids.includes('t-a'));
|
|
63
|
-
assert.ok(result.marked_ids.includes('t-b'));
|
|
64
|
-
assert.equal(ops.readTask(db, 't-c').stale, false);
|
|
65
|
-
} finally { teardown(dir, db); }
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('markTasksStaleBySpecSections: skips non-pending tasks', () => {
|
|
69
|
-
const { dir, db } = freshDb();
|
|
70
|
-
try {
|
|
71
|
-
seed(db, 't-running', { trace_to: 'product' });
|
|
72
|
-
ops.patchTask(db, 't-running', { status: 'in_progress' });
|
|
73
|
-
|
|
74
|
-
seed(db, 't-done', { trace_to: 'product' });
|
|
75
|
-
ops.patchTask(db, 't-done', { status: 'in_progress' });
|
|
76
|
-
ops.patchTask(db, 't-done', { status: 'completed' });
|
|
77
|
-
|
|
78
|
-
seed(db, 't-pending', { trace_to: 'product' });
|
|
79
|
-
|
|
80
|
-
const result = ops.markTasksStaleBySpecSections(db, ['product']);
|
|
81
|
-
assert.equal(result.marked_count, 1);
|
|
82
|
-
assert.deepEqual(result.marked_ids, ['t-pending']);
|
|
83
|
-
} finally { teardown(dir, db); }
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('markTasksStaleBySpecSections: emits task_stale_marked event per task', () => {
|
|
87
|
-
const { dir, db } = freshDb();
|
|
88
|
-
try {
|
|
89
|
-
seed(db, 't-e1', { trace_to: 'product' });
|
|
90
|
-
seed(db, 't-e2', { trace_to: 'product' });
|
|
91
|
-
|
|
92
|
-
const { events: before } = ops.subscribeEventsSince(db, { since_id: 0, limit: 500 });
|
|
93
|
-
ops.markTasksStaleBySpecSections(db, ['product']);
|
|
94
|
-
const { events: after } = ops.subscribeEventsSince(db, { since_id: 0, limit: 500 });
|
|
95
|
-
|
|
96
|
-
const marked = after.slice(before.length).filter((e) => e.type === 'task_stale_marked');
|
|
97
|
-
assert.equal(marked.length, 2);
|
|
98
|
-
const taskIds = marked.map((e) => e.task_id).sort();
|
|
99
|
-
assert.deepEqual(taskIds, ['t-e1', 't-e2']);
|
|
100
|
-
} finally { teardown(dir, db); }
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('markTasksStaleBySpecSections: idempotent — re-mark already-stale task does not double-emit', () => {
|
|
104
|
-
const { dir, db } = freshDb();
|
|
105
|
-
try {
|
|
106
|
-
seed(db, 't-idem', { trace_to: 'product' });
|
|
107
|
-
ops.markTasksStaleBySpecSections(db, ['product']);
|
|
108
|
-
|
|
109
|
-
const { events: before } = ops.subscribeEventsSince(db, { since_id: 0, limit: 500 });
|
|
110
|
-
ops.markTasksStaleBySpecSections(db, ['product']);
|
|
111
|
-
const { events: after } = ops.subscribeEventsSince(db, { since_id: 0, limit: 500 });
|
|
112
|
-
|
|
113
|
-
const newEvents = after.slice(before.length);
|
|
114
|
-
const marked = newEvents.filter((e) => e.type === 'task_stale_marked');
|
|
115
|
-
assert.equal(marked.length, 0, 'should not re-emit for already-stale task');
|
|
116
|
-
} finally { teardown(dir, db); }
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('consumeSpecChangedEvents: processes queued events and advances cursor', () => {
|
|
120
|
-
const { dir, db } = freshDb();
|
|
121
|
-
try {
|
|
122
|
-
seed(db, 't-c1', { trace_to: 'product' });
|
|
123
|
-
seed(db, 't-c2', { trace_to: 'arch' });
|
|
124
|
-
|
|
125
|
-
ops.appendEvent(db, { type: 'spec_changed', payload: { sections: ['product'] } });
|
|
126
|
-
ops.appendEvent(db, { type: 'spec_changed', payload: { sections: ['arch'] } });
|
|
127
|
-
|
|
128
|
-
const result = ops.consumeSpecChangedEvents(db, { since_id: 0 });
|
|
129
|
-
assert.equal(result.processed, 2);
|
|
130
|
-
assert.ok(result.next_since_id > 0);
|
|
131
|
-
assert.equal(ops.readTask(db, 't-c1').stale, true);
|
|
132
|
-
assert.equal(ops.readTask(db, 't-c2').stale, true);
|
|
133
|
-
} finally { teardown(dir, db); }
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('consumeSpecChangedEvents: cursor prevents reprocessing', () => {
|
|
137
|
-
const { dir, db } = freshDb();
|
|
138
|
-
try {
|
|
139
|
-
seed(db, 't-cur', { trace_to: 'product' });
|
|
140
|
-
ops.appendEvent(db, { type: 'spec_changed', payload: { sections: ['product'] } });
|
|
141
|
-
|
|
142
|
-
const first = ops.consumeSpecChangedEvents(db, { since_id: 0 });
|
|
143
|
-
assert.equal(first.processed, 1);
|
|
144
|
-
|
|
145
|
-
// second call from the new cursor — no new spec_changed events.
|
|
146
|
-
const second = ops.consumeSpecChangedEvents(db, { since_id: first.next_since_id });
|
|
147
|
-
assert.equal(second.processed, 0);
|
|
148
|
-
assert.equal(second.next_since_id, first.next_since_id);
|
|
149
|
-
} finally { teardown(dir, db); }
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('consumeSpecChangedEvents: ignores non-spec_changed events', () => {
|
|
153
|
-
const { dir, db } = freshDb();
|
|
154
|
-
try {
|
|
155
|
-
seed(db, 't-other', { trace_to: 'product' });
|
|
156
|
-
ops.appendEvent(db, { type: 'task_failure', task_id: 't-other', payload: { reason: 'noise' } });
|
|
157
|
-
|
|
158
|
-
const result = ops.consumeSpecChangedEvents(db, { since_id: 0 });
|
|
159
|
-
assert.equal(result.processed, 0);
|
|
160
|
-
assert.equal(ops.readTask(db, 't-other').stale, false);
|
|
161
|
-
} finally { teardown(dir, db); }
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('projector: stale task context md carries STALE banner before body', () => {
|
|
165
|
-
const { dir, db } = freshDb();
|
|
166
|
-
const rootDir = dir;
|
|
167
|
-
try {
|
|
168
|
-
seed(db, 't-proj', { trace_to: 'product' });
|
|
169
|
-
ops.markTasksStaleBySpecSections(db, ['product']);
|
|
170
|
-
|
|
171
|
-
const result = projector.projectContext(db, 't-proj', {}, { rootDir });
|
|
172
|
-
const text = fs.readFileSync(result.path, 'utf8');
|
|
173
|
-
// Banner goes after YAML frontmatter end + blank line.
|
|
174
|
-
const frontmatterEnd = text.indexOf('\n---', 3);
|
|
175
|
-
assert.ok(frontmatterEnd > 0, 'frontmatter should exist');
|
|
176
|
-
const body = text.slice(frontmatterEnd + 4);
|
|
177
|
-
assert.match(body, /⚠️\s*STALE/, 'stale banner should render');
|
|
178
|
-
assert.match(body, /spec/i, 'banner should mention spec');
|
|
179
|
-
} finally { teardown(dir, db); }
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('projector: non-stale task context md has no STALE banner', () => {
|
|
183
|
-
const { dir, db } = freshDb();
|
|
184
|
-
try {
|
|
185
|
-
seed(db, 't-fresh', { trace_to: 'product' });
|
|
186
|
-
const result = projector.projectContext(db, 't-fresh', {}, { rootDir: dir });
|
|
187
|
-
const text = fs.readFileSync(result.path, 'utf8');
|
|
188
|
-
assert.doesNotMatch(text, /STALE/);
|
|
189
|
-
} finally { teardown(dir, db); }
|
|
190
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const test = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const os = require('node:os');
|
|
7
|
-
const path = require('node:path');
|
|
8
|
-
|
|
9
|
-
const stateDb = require('../lib/state-db.cjs');
|
|
10
|
-
|
|
11
|
-
test('initStateDb upgrades a pre-10.0 database through Context Spine into baseline authority', () => {
|
|
12
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-db-upgrade-'));
|
|
13
|
-
const file = path.join(dir, 'state.db');
|
|
14
|
-
try {
|
|
15
|
-
const db = stateDb.openStateDb(file);
|
|
16
|
-
db.exec(`
|
|
17
|
-
CREATE TABLE tasks (
|
|
18
|
-
id TEXT PRIMARY KEY,
|
|
19
|
-
title TEXT NOT NULL,
|
|
20
|
-
type TEXT NOT NULL,
|
|
21
|
-
priority TEXT NOT NULL,
|
|
22
|
-
complexity INTEGER,
|
|
23
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
24
|
-
deps TEXT,
|
|
25
|
-
files_modified TEXT,
|
|
26
|
-
session_id TEXT,
|
|
27
|
-
stale INTEGER NOT NULL DEFAULT 0,
|
|
28
|
-
complexity_hint TEXT,
|
|
29
|
-
tag TEXT,
|
|
30
|
-
trace_to TEXT,
|
|
31
|
-
context_file TEXT,
|
|
32
|
-
completion_commit TEXT,
|
|
33
|
-
parent_id TEXT,
|
|
34
|
-
created_at TEXT NOT NULL,
|
|
35
|
-
updated_at TEXT NOT NULL
|
|
36
|
-
);
|
|
37
|
-
CREATE TABLE events (
|
|
38
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
-
ts TEXT NOT NULL,
|
|
40
|
-
type TEXT NOT NULL,
|
|
41
|
-
task_id TEXT,
|
|
42
|
-
session_id TEXT,
|
|
43
|
-
runtime TEXT,
|
|
44
|
-
payload_json TEXT
|
|
45
|
-
);
|
|
46
|
-
CREATE TABLE schema_version (
|
|
47
|
-
version TEXT PRIMARY KEY,
|
|
48
|
-
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
49
|
-
description TEXT
|
|
50
|
-
);
|
|
51
|
-
INSERT INTO schema_version (version, applied_at, description)
|
|
52
|
-
VALUES ('8A.1', '2026-01-01T00:00:00.000Z', 'legacy fixture');
|
|
53
|
-
`);
|
|
54
|
-
stateDb.closeStateDb(db);
|
|
55
|
-
|
|
56
|
-
const upgraded = stateDb.initStateDb(file);
|
|
57
|
-
const columns = upgraded.db.prepare('PRAGMA table_info(tasks)').all().map((row) => row.name);
|
|
58
|
-
assert.ok(columns.includes('estimated_days'));
|
|
59
|
-
assert.ok(columns.includes('change_id'));
|
|
60
|
-
const eventColumns = upgraded.db.prepare('PRAGMA table_info(events)').all().map((row) => row.name);
|
|
61
|
-
assert.ok(eventColumns.includes('change_id'));
|
|
62
|
-
assert.equal(upgraded.schema_version, '12.0');
|
|
63
|
-
assert.ok(upgraded.backup_path);
|
|
64
|
-
assert.ok(fs.existsSync(upgraded.backup_path));
|
|
65
|
-
assert.ok(upgraded.db.prepare("SELECT 1 FROM schema_version WHERE version = '10.0'").get());
|
|
66
|
-
const contextColumns = upgraded.db.prepare('PRAGMA table_info(context_snapshots)').all()
|
|
67
|
-
.map((row) => row.name);
|
|
68
|
-
for (const column of ['role', 'gate', 'next_action', 'readiness', 'context_json', 'token_budget']) {
|
|
69
|
-
assert.ok(contextColumns.includes(column), column);
|
|
70
|
-
}
|
|
71
|
-
assert.ok(upgraded.db.prepare(
|
|
72
|
-
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'spec_learning_candidates'",
|
|
73
|
-
).get());
|
|
74
|
-
const baseline = upgraded.db.prepare(
|
|
75
|
-
"SELECT mode, status FROM baselines WHERE id = 'migrated-baseline'",
|
|
76
|
-
).get();
|
|
77
|
-
assert.deepEqual(baseline, { mode: 'migrated', status: 'adopting' });
|
|
78
|
-
const contextMigration = upgraded.db.prepare(
|
|
79
|
-
"SELECT from_version, to_version, notes FROM migration_history WHERE to_version = '10.0' ORDER BY id DESC LIMIT 1",
|
|
80
|
-
).get();
|
|
81
|
-
assert.equal(contextMigration.from_version, '9.1');
|
|
82
|
-
assert.equal(contextMigration.to_version, '10.0');
|
|
83
|
-
assert.match(contextMigration.notes, /Context Spine/);
|
|
84
|
-
const baselineMigration = upgraded.db.prepare(
|
|
85
|
-
"SELECT from_version, to_version, notes FROM migration_history WHERE to_version = '11.0' ORDER BY id DESC LIMIT 1",
|
|
86
|
-
).get();
|
|
87
|
-
assert.equal(baselineMigration.from_version, '10.0');
|
|
88
|
-
assert.match(baselineMigration.notes, /baseline adoption/i);
|
|
89
|
-
const adoptionMigration = upgraded.db.prepare(
|
|
90
|
-
"SELECT from_version, to_version, notes FROM migration_history WHERE to_version = '12.0' ORDER BY id DESC LIMIT 1",
|
|
91
|
-
).get();
|
|
92
|
-
assert.equal(adoptionMigration.from_version, '11.0');
|
|
93
|
-
assert.match(adoptionMigration.notes, /gap ledger|re-adoption/i);
|
|
94
|
-
const migrated = upgraded.db.prepare(
|
|
95
|
-
"SELECT gaps_json, worktree_state FROM baselines WHERE id = 'migrated-baseline'",
|
|
96
|
-
).get();
|
|
97
|
-
assert.equal(migrated.worktree_state, 'unavailable');
|
|
98
|
-
assert.equal(JSON.parse(migrated.gaps_json)[0].category, 'baseline_blocker');
|
|
99
|
-
assert.ok(upgraded.db.prepare(
|
|
100
|
-
"SELECT 1 FROM migration_history WHERE from_version = '8A.1' AND to_version = '9.1' AND notes LIKE '%Kimi%'",
|
|
101
|
-
).get());
|
|
102
|
-
stateDb.closeStateDb(upgraded.db);
|
|
103
|
-
} finally {
|
|
104
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
105
|
-
}
|
|
106
|
-
});
|