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,147 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Phase 7.3 — Skill miner (heuristic, no LLM):
|
|
4
|
-
// • mineSession scans a session's events + task state for "solved
|
|
5
|
-
// something non-trivial" signals and writes skills/learned/
|
|
6
|
-
// <ts>_<sid>_unverified.md drafts.
|
|
7
|
-
// • Idempotent: same sid twice → only one draft per signal.
|
|
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('../../mcp-server/lib/state-db.cjs');
|
|
16
|
-
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
17
|
-
const skillMiner = require('../skill-miner.cjs');
|
|
18
|
-
|
|
19
|
-
function mkFixture() {
|
|
20
|
-
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-skill-'));
|
|
21
|
-
const dbPath = path.join(repoRoot, '.ultra', 'state.db');
|
|
22
|
-
const { db } = initStateDb(dbPath);
|
|
23
|
-
const skillsRoot = path.join(repoRoot, 'skills');
|
|
24
|
-
fs.mkdirSync(skillsRoot, { recursive: true });
|
|
25
|
-
return { repoRoot, db, skillsRoot };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function teardown(repoRoot, db) {
|
|
29
|
-
try { closeStateDb(db); } catch (_) { /* ignore */ }
|
|
30
|
-
try { fs.rmSync(repoRoot, { recursive: true, force: true }); } catch (_) { /* ignore */ }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function seed(db, { sid, task_id, title = 'demo', tag = null }) {
|
|
34
|
-
ops.createTask(db, { id: task_id, title, type: 'feature', priority: 'P1', tag });
|
|
35
|
-
ops.createSession(db, {
|
|
36
|
-
sid, task_id, runtime: 'claude',
|
|
37
|
-
worktree_path: `/tmp/${sid}/wt`, artifact_dir: `/tmp/${sid}/art`,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test('mineSession: task_completed signal → generates draft', () => {
|
|
42
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
43
|
-
try {
|
|
44
|
-
seed(db, { sid: 's-done', task_id: 't-done', title: 'ship auth flow' });
|
|
45
|
-
ops.patchTask(db, 't-done', { status: 'in_progress' });
|
|
46
|
-
ops.appendEvent(db, { type: 'task_completed', task_id: 't-done', session_id: 's-done', payload: { to: 'completed' } });
|
|
47
|
-
ops.patchTask(db, 't-done', { status: 'completed' });
|
|
48
|
-
|
|
49
|
-
const out = skillMiner.mineSession(db, { sid: 's-done', skillsRoot });
|
|
50
|
-
assert.equal(out.drafts.length, 1);
|
|
51
|
-
assert.match(out.drafts[0], /_unverified\.md$/);
|
|
52
|
-
const draft = fs.readFileSync(out.drafts[0], 'utf8');
|
|
53
|
-
assert.match(draft, /kind: task-completion/);
|
|
54
|
-
assert.match(draft, /unverified: true/);
|
|
55
|
-
assert.match(draft, /ship auth flow/);
|
|
56
|
-
} finally { teardown(repoRoot, db); }
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('mineSession: task_circuit_broken signal → debug-pattern draft', () => {
|
|
60
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
61
|
-
try {
|
|
62
|
-
seed(db, { sid: 's-cb', task_id: 't-cb', title: 'flaky integration' });
|
|
63
|
-
ops.appendEvent(db, { type: 'task_circuit_broken', task_id: 't-cb', session_id: 's-cb', payload: { threshold: 3 } });
|
|
64
|
-
const out = skillMiner.mineSession(db, { sid: 's-cb', skillsRoot });
|
|
65
|
-
assert.equal(out.drafts.length, 1);
|
|
66
|
-
const draft = fs.readFileSync(out.drafts[0], 'utf8');
|
|
67
|
-
assert.match(draft, /kind: debug-pattern/);
|
|
68
|
-
} finally { teardown(repoRoot, db); }
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('mineSession: session_crashed signal → recovery-pattern draft', () => {
|
|
72
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
73
|
-
try {
|
|
74
|
-
seed(db, { sid: 's-crash', task_id: 't-crash', title: 'crashing task' });
|
|
75
|
-
ops.appendEvent(db, { type: 'session_crashed', task_id: 't-crash', session_id: 's-crash', payload: { to: 'crashed' } });
|
|
76
|
-
const out = skillMiner.mineSession(db, { sid: 's-crash', skillsRoot });
|
|
77
|
-
assert.equal(out.drafts.length, 1);
|
|
78
|
-
const draft = fs.readFileSync(out.drafts[0], 'utf8');
|
|
79
|
-
assert.match(draft, /kind: recovery-pattern/);
|
|
80
|
-
} finally { teardown(repoRoot, db); }
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('mineSession: no trigger signals → no draft', () => {
|
|
84
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
85
|
-
try {
|
|
86
|
-
seed(db, { sid: 's-quiet', task_id: 't-quiet', title: 'nothing happened' });
|
|
87
|
-
const out = skillMiner.mineSession(db, { sid: 's-quiet', skillsRoot });
|
|
88
|
-
assert.equal(out.drafts.length, 0);
|
|
89
|
-
} finally { teardown(repoRoot, db); }
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('mineSession: idempotent — running twice on same sid yields no new drafts', () => {
|
|
93
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
94
|
-
try {
|
|
95
|
-
seed(db, { sid: 's-idem', task_id: 't-idem', title: 'shipped' });
|
|
96
|
-
ops.patchTask(db, 't-idem', { status: 'in_progress' });
|
|
97
|
-
ops.appendEvent(db, { type: 'task_completed', task_id: 't-idem', session_id: 's-idem', payload: { to: 'completed' } });
|
|
98
|
-
ops.patchTask(db, 't-idem', { status: 'completed' });
|
|
99
|
-
|
|
100
|
-
const first = skillMiner.mineSession(db, { sid: 's-idem', skillsRoot });
|
|
101
|
-
const second = skillMiner.mineSession(db, { sid: 's-idem', skillsRoot });
|
|
102
|
-
assert.equal(first.drafts.length, 1);
|
|
103
|
-
assert.equal(second.drafts.length, 0, 'second mining should see existing draft and skip');
|
|
104
|
-
const learned = fs.readdirSync(path.join(skillsRoot, 'learned'));
|
|
105
|
-
assert.equal(learned.length, 1);
|
|
106
|
-
} finally { teardown(repoRoot, db); }
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('mineSession: missing session → empty drafts array', () => {
|
|
110
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
111
|
-
try {
|
|
112
|
-
const out = skillMiner.mineSession(db, { sid: 's-nope', skillsRoot });
|
|
113
|
-
assert.deepEqual(out.drafts, []);
|
|
114
|
-
} finally { teardown(repoRoot, db); }
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test('AC flow: 5 mock sessions (3 with signal, 2 empty) → ≥3 drafts', () => {
|
|
118
|
-
const { repoRoot, db, skillsRoot } = mkFixture();
|
|
119
|
-
try {
|
|
120
|
-
// 3 with a trigger signal
|
|
121
|
-
for (const [i, kind] of [['ac1', 'completed'], ['ac2', 'crashed'], ['ac3', 'broken']]) {
|
|
122
|
-
seed(db, { sid: `s-${i}`, task_id: `t-${i}`, title: `task ${i}` });
|
|
123
|
-
if (kind === 'completed') {
|
|
124
|
-
ops.patchTask(db, `t-${i}`, { status: 'in_progress' });
|
|
125
|
-
ops.appendEvent(db, { type: 'task_completed', task_id: `t-${i}`, session_id: `s-${i}`, payload: {} });
|
|
126
|
-
ops.patchTask(db, `t-${i}`, { status: 'completed' });
|
|
127
|
-
} else if (kind === 'crashed') {
|
|
128
|
-
ops.appendEvent(db, { type: 'session_crashed', task_id: `t-${i}`, session_id: `s-${i}`, payload: {} });
|
|
129
|
-
} else if (kind === 'broken') {
|
|
130
|
-
ops.appendEvent(db, { type: 'task_circuit_broken', task_id: `t-${i}`, session_id: `s-${i}`, payload: { threshold: 3 } });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// 2 empty
|
|
134
|
-
for (const i of ['ac4', 'ac5']) {
|
|
135
|
-
seed(db, { sid: `s-${i}`, task_id: `t-${i}`, title: `task ${i}` });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let totalDrafts = 0;
|
|
139
|
-
for (const i of ['ac1', 'ac2', 'ac3', 'ac4', 'ac5']) {
|
|
140
|
-
const out = skillMiner.mineSession(db, { sid: `s-${i}`, skillsRoot });
|
|
141
|
-
totalDrafts += out.drafts.length;
|
|
142
|
-
}
|
|
143
|
-
assert.ok(totalDrafts >= 3, `expected ≥3 drafts across 5 sessions, got ${totalDrafts}`);
|
|
144
|
-
const learned = fs.readdirSync(path.join(skillsRoot, 'learned'));
|
|
145
|
-
assert.ok(learned.length >= 3);
|
|
146
|
-
} finally { teardown(repoRoot, db); }
|
|
147
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Phase 8B.3 — N-concurrent git worktree management.
|
|
4
|
-
//
|
|
5
|
-
// Session-runner (Phase 4.5) already encapsulates `git worktree add/remove`
|
|
6
|
-
// for a single session. worktree-manager is the registry / batch layer:
|
|
7
|
-
// allocate N, track them via `git worktree list --porcelain`, release one
|
|
8
|
-
// or all (for crash recovery), detect filesystem orphans.
|
|
9
|
-
//
|
|
10
|
-
// AC (from PLAN §6 Phase 8B.3):
|
|
11
|
-
// - 3 concurrent slices → 3 independent worktrees, git worktree list
|
|
12
|
-
// shows them; main branch checkout untouched
|
|
13
|
-
// - 5 rapid allocations in one tick → no .git/config.lock contention
|
|
14
|
-
// (Node single-thread execFileSync is naturally serialized)
|
|
15
|
-
// - releaseAll cleans filesystem leftovers even when git forgot them
|
|
16
|
-
|
|
17
|
-
const { test } = require('node:test');
|
|
18
|
-
const assert = require('node:assert/strict');
|
|
19
|
-
const fs = require('node:fs');
|
|
20
|
-
const os = require('node:os');
|
|
21
|
-
const path = require('node:path');
|
|
22
|
-
const { execFileSync } = require('node:child_process');
|
|
23
|
-
|
|
24
|
-
const wm = require('../worktree-manager.cjs');
|
|
25
|
-
|
|
26
|
-
function mkRepo() {
|
|
27
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-wtmgr-'));
|
|
28
|
-
execFileSync('git', ['init', '-q'], { cwd: dir });
|
|
29
|
-
execFileSync('git', ['config', 'user.email', 'test@ubp.dev'], { cwd: dir });
|
|
30
|
-
execFileSync('git', ['config', 'user.name', 'ubp-test'], { cwd: dir });
|
|
31
|
-
fs.writeFileSync(path.join(dir, 'README.md'), '# seed\n');
|
|
32
|
-
execFileSync('git', ['add', '-A'], { cwd: dir });
|
|
33
|
-
execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: dir });
|
|
34
|
-
return dir;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function cleanup(dir) {
|
|
38
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test('allocate: creates worktree under .ultra/worktrees/<sid> and returns path', () => {
|
|
42
|
-
const repo = mkRepo();
|
|
43
|
-
try {
|
|
44
|
-
const { worktree_path } = wm.allocate({ repoRoot: repo, sid: 'sess-abc' });
|
|
45
|
-
assert.ok(worktree_path.endsWith('.ultra/worktrees/sess-abc'));
|
|
46
|
-
assert.ok(fs.existsSync(worktree_path), 'worktree dir should exist');
|
|
47
|
-
assert.ok(fs.existsSync(path.join(worktree_path, 'README.md')), 'seed file visible');
|
|
48
|
-
} finally { cleanup(repo); }
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('release: removes worktree dir and unregisters from git', () => {
|
|
52
|
-
const repo = mkRepo();
|
|
53
|
-
try {
|
|
54
|
-
const { worktree_path } = wm.allocate({ repoRoot: repo, sid: 'sess-rel' });
|
|
55
|
-
wm.release({ repoRoot: repo, worktree_path });
|
|
56
|
-
assert.equal(fs.existsSync(worktree_path), false, 'dir should be removed');
|
|
57
|
-
const active = wm.listActive(repo);
|
|
58
|
-
assert.equal(active.length, 0, 'no slices should remain');
|
|
59
|
-
} finally { cleanup(repo); }
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('3 concurrent allocates → 3 independent worktrees visible via listActive', () => {
|
|
63
|
-
const repo = mkRepo();
|
|
64
|
-
try {
|
|
65
|
-
const sids = ['sess-a', 'sess-b', 'sess-c'];
|
|
66
|
-
const paths = sids.map((sid) => wm.allocate({ repoRoot: repo, sid }).worktree_path);
|
|
67
|
-
for (const p of paths) assert.ok(fs.existsSync(p), `${p} missing`);
|
|
68
|
-
const active = wm.listActive(repo);
|
|
69
|
-
const activeSids = active.map((a) => a.sid).sort();
|
|
70
|
-
assert.deepEqual(activeSids, sids.slice().sort());
|
|
71
|
-
// Every entry has a distinct worktree_path
|
|
72
|
-
const uniquePaths = new Set(active.map((a) => a.worktree_path));
|
|
73
|
-
assert.equal(uniquePaths.size, 3);
|
|
74
|
-
} finally { cleanup(repo); }
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('5 rapid allocates in one tick → no .git/config.lock contention', () => {
|
|
78
|
-
const repo = mkRepo();
|
|
79
|
-
try {
|
|
80
|
-
const sids = ['s1', 's2', 's3', 's4', 's5'];
|
|
81
|
-
// Execute synchronously back-to-back; Node single-thread guarantees
|
|
82
|
-
// serialized git calls, so no file-lock race.
|
|
83
|
-
for (const sid of sids) {
|
|
84
|
-
wm.allocate({ repoRoot: repo, sid });
|
|
85
|
-
}
|
|
86
|
-
assert.equal(wm.listActive(repo).length, 5);
|
|
87
|
-
} finally { cleanup(repo); }
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('listActive: only returns worktrees under .ultra/worktrees/ (main excluded)', () => {
|
|
91
|
-
const repo = mkRepo();
|
|
92
|
-
try {
|
|
93
|
-
// Manually add a worktree OUTSIDE .ultra/worktrees — should be ignored.
|
|
94
|
-
const outsideDir = path.join(repo, 'other-wt');
|
|
95
|
-
execFileSync('git', ['worktree', 'add', '--detach', outsideDir, 'HEAD'], {
|
|
96
|
-
cwd: repo, stdio: 'pipe',
|
|
97
|
-
});
|
|
98
|
-
wm.allocate({ repoRoot: repo, sid: 'sess-domain' });
|
|
99
|
-
const active = wm.listActive(repo);
|
|
100
|
-
assert.equal(active.length, 1);
|
|
101
|
-
assert.equal(active[0].sid, 'sess-domain');
|
|
102
|
-
} finally { cleanup(repo); }
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('listActive: empty repo → []', () => {
|
|
106
|
-
const repo = mkRepo();
|
|
107
|
-
try {
|
|
108
|
-
assert.deepEqual(wm.listActive(repo), []);
|
|
109
|
-
} finally { cleanup(repo); }
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('releaseAll: clears all allocated worktrees + filesystem orphans', () => {
|
|
113
|
-
const repo = mkRepo();
|
|
114
|
-
try {
|
|
115
|
-
wm.allocate({ repoRoot: repo, sid: 'x1' });
|
|
116
|
-
wm.allocate({ repoRoot: repo, sid: 'x2' });
|
|
117
|
-
// Create an orphan dir: git doesn't know about it, but fs exists.
|
|
118
|
-
const orphanDir = path.join(repo, '.ultra', 'worktrees', 'orphan-dir');
|
|
119
|
-
fs.mkdirSync(orphanDir, { recursive: true });
|
|
120
|
-
fs.writeFileSync(path.join(orphanDir, 'junk.txt'), 'x');
|
|
121
|
-
|
|
122
|
-
const result = wm.releaseAll(repo);
|
|
123
|
-
assert.equal(result.cleaned, 2, 'should report 2 git-tracked cleanups');
|
|
124
|
-
assert.equal(wm.listActive(repo).length, 0);
|
|
125
|
-
assert.equal(fs.existsSync(orphanDir), false, 'orphan must be swept');
|
|
126
|
-
} finally { cleanup(repo); }
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('allocate: baseRef honored — worktree checked out at given ref', () => {
|
|
130
|
-
const repo = mkRepo();
|
|
131
|
-
try {
|
|
132
|
-
// Make a second commit so we have two distinct SHAs.
|
|
133
|
-
fs.writeFileSync(path.join(repo, 'second.md'), '# second\n');
|
|
134
|
-
execFileSync('git', ['add', '-A'], { cwd: repo });
|
|
135
|
-
execFileSync('git', ['commit', '-q', '-m', 'second'], { cwd: repo });
|
|
136
|
-
const firstSha = execFileSync('git', ['rev-parse', 'HEAD~1'], {
|
|
137
|
-
cwd: repo, stdio: ['ignore', 'pipe', 'pipe'],
|
|
138
|
-
}).toString().trim();
|
|
139
|
-
|
|
140
|
-
const { worktree_path } = wm.allocate({
|
|
141
|
-
repoRoot: repo, sid: 'sess-ref', baseRef: firstSha,
|
|
142
|
-
});
|
|
143
|
-
const wtSha = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
144
|
-
cwd: worktree_path, stdio: ['ignore', 'pipe', 'pipe'],
|
|
145
|
-
}).toString().trim();
|
|
146
|
-
assert.equal(wtSha, firstSha);
|
|
147
|
-
} finally { cleanup(repo); }
|
|
148
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Architecture research
|
|
2
|
-
|
|
3
|
-
Read this reference when the system boundary, quality attributes, integration seams,
|
|
4
|
-
technology choice, or implementation constraints are unresolved.
|
|
5
|
-
|
|
6
|
-
## Current system evidence
|
|
7
|
-
|
|
8
|
-
Start from the current checkout:
|
|
9
|
-
|
|
10
|
-
- entry points and external interfaces;
|
|
11
|
-
- domain boundaries and authoritative state;
|
|
12
|
-
- data flows and side effects;
|
|
13
|
-
- existing dependencies and deployment topology;
|
|
14
|
-
- failure, recovery, authorization, and observability paths.
|
|
15
|
-
|
|
16
|
-
Do not design a replacement architecture before identifying the live path and the
|
|
17
|
-
constraint that requires change.
|
|
18
|
-
|
|
19
|
-
## Decision method
|
|
20
|
-
|
|
21
|
-
For each material architecture decision:
|
|
22
|
-
|
|
23
|
-
1. state the required capability and quality constraints;
|
|
24
|
-
2. identify credible options already compatible with the system when possible;
|
|
25
|
-
3. use official primary documentation for library or platform behavior;
|
|
26
|
-
4. evaluate integration cost, operability, failure modes, migration, and rollback;
|
|
27
|
-
5. record the selected option, rejected alternatives that may recur, and the evidence
|
|
28
|
-
that would reopen the decision.
|
|
29
|
-
|
|
30
|
-
Do not compare technologies for ceremony. A single established repository pattern is
|
|
31
|
-
the default when it satisfies the contract.
|
|
32
|
-
|
|
33
|
-
## Record the result
|
|
34
|
-
|
|
35
|
-
Update the architecture specification with system context, boundaries, building
|
|
36
|
-
blocks, runtime flow, authoritative state, external contracts, quality scenarios,
|
|
37
|
-
deployment constraints, and accepted decisions. Connect each required behavior to a
|
|
38
|
-
reachable public seam and a verification approach.
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Problem and product research
|
|
2
|
-
|
|
3
|
-
Read this reference when the problem, value, market context, or product direction is
|
|
4
|
-
uncertain.
|
|
5
|
-
|
|
6
|
-
## Establish the problem
|
|
7
|
-
|
|
8
|
-
- Identify the affected actor, current behavior, observable cost, and desired outcome.
|
|
9
|
-
- Distinguish user evidence from stakeholder belief and solution preference.
|
|
10
|
-
- Find current alternatives, including manual work or doing nothing.
|
|
11
|
-
- State what evidence would disprove the problem framing.
|
|
12
|
-
|
|
13
|
-
Use repository artifacts, support data, interviews, analytics, or primary external
|
|
14
|
-
sources that actually exist. Do not invent quotes, demand, or market size.
|
|
15
|
-
|
|
16
|
-
## Decide how much market work is needed
|
|
17
|
-
|
|
18
|
-
Market sizing and competitive research are optional. Use them only when they affect a
|
|
19
|
-
funding, positioning, pricing, or build-versus-buy decision.
|
|
20
|
-
|
|
21
|
-
When needed:
|
|
22
|
-
|
|
23
|
-
1. define the market boundary and time period;
|
|
24
|
-
2. show the calculation inputs and sources;
|
|
25
|
-
3. separate direct, adjacent, and substitute alternatives;
|
|
26
|
-
4. explain how the evidence changes the product decision.
|
|
27
|
-
|
|
28
|
-
Do not present unsourced top-down totals or call a category competitor-free.
|
|
29
|
-
|
|
30
|
-
## Record the result
|
|
31
|
-
|
|
32
|
-
Update the discovery specification with:
|
|
33
|
-
|
|
34
|
-
- problem and affected actors;
|
|
35
|
-
- observed evidence and source quality;
|
|
36
|
-
- desired outcome and non-goals;
|
|
37
|
-
- material alternatives and tradeoffs;
|
|
38
|
-
- assumptions, validation method, and blocking questions;
|
|
39
|
-
- the accepted product decision and why it follows from the evidence.
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Quality and delivery research
|
|
2
|
-
|
|
3
|
-
Read this reference when acceptance depends on operational risk, security, migration,
|
|
4
|
-
deployment, recovery, or release constraints.
|
|
5
|
-
|
|
6
|
-
## Risk-driven quality
|
|
7
|
-
|
|
8
|
-
Identify failures that would materially affect users, data, authorization, money,
|
|
9
|
-
availability, or recoverability. For each relevant risk, record:
|
|
10
|
-
|
|
11
|
-
- trigger and affected boundary;
|
|
12
|
-
- expected behavior and observable signal;
|
|
13
|
-
- prevention, detection, and recovery;
|
|
14
|
-
- verification method and owner.
|
|
15
|
-
|
|
16
|
-
Apply security, performance, accessibility, and resilience checks according to the
|
|
17
|
-
actual product surface. Do not impose generic categories or coverage percentages when
|
|
18
|
-
they do not define acceptance.
|
|
19
|
-
|
|
20
|
-
## Delivery path
|
|
21
|
-
|
|
22
|
-
Capture the environments, configuration authority, migration order, compatibility
|
|
23
|
-
window, health and readiness checks, observability, rollout, rollback, and operator
|
|
24
|
-
actions needed for this product.
|
|
25
|
-
|
|
26
|
-
Prefer existing repository and platform workflows. Add a new service, dependency, or
|
|
27
|
-
deployment mechanism only when the accepted architecture requires it.
|
|
28
|
-
|
|
29
|
-
## Record the result
|
|
30
|
-
|
|
31
|
-
Update the architecture and product specifications with testable quality scenarios,
|
|
32
|
-
delivery constraints, failure behavior, recovery evidence, and release acceptance.
|
|
33
|
-
Keep exact volatile commands in the repository's runbook when they are operational
|
|
34
|
-
procedure rather than durable architecture.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Baseline synthesis and validation
|
|
2
|
-
|
|
3
|
-
Read this reference after the selected research lanes are complete.
|
|
4
|
-
|
|
5
|
-
## Traceability check
|
|
6
|
-
|
|
7
|
-
Verify that each accepted outcome has:
|
|
8
|
-
|
|
9
|
-
1. supporting problem, user, or constraint evidence;
|
|
10
|
-
2. an explicit decision and scope boundary;
|
|
11
|
-
3. testable acceptance behavior;
|
|
12
|
-
4. a compatible architecture or delivery path;
|
|
13
|
-
5. a named unresolved blocker when any link is missing.
|
|
14
|
-
|
|
15
|
-
Check the reverse direction as well: every proposed component, dependency, and quality
|
|
16
|
-
requirement must support an accepted outcome or constraint.
|
|
17
|
-
|
|
18
|
-
## Consistency check
|
|
19
|
-
|
|
20
|
-
- Use one term for each actor, boundary, and authoritative data source.
|
|
21
|
-
- Remove duplicate narratives and stale alternatives.
|
|
22
|
-
- Keep source links next to the claims they support.
|
|
23
|
-
- Separate durable decisions from volatile external facts.
|
|
24
|
-
- Preserve explicit non-goals and rejected alternatives only when they prevent a
|
|
25
|
-
likely future mistake.
|
|
26
|
-
|
|
27
|
-
## Handoff
|
|
28
|
-
|
|
29
|
-
Report:
|
|
30
|
-
|
|
31
|
-
- validated scope and decisive evidence;
|
|
32
|
-
- specification files changed;
|
|
33
|
-
- accepted decisions and material uncertainty;
|
|
34
|
-
- blockers, or an explicit statement that none remain;
|
|
35
|
-
- one next action.
|
|
36
|
-
|
|
37
|
-
Do not create a parallel summary document solely for model consumption. The planning
|
|
38
|
-
workflow should read the smallest authoritative baseline sections it needs.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Users, behavior, and scope
|
|
2
|
-
|
|
3
|
-
Read this reference when actors, workflows, acceptance, boundaries, or success signals
|
|
4
|
-
are unclear.
|
|
5
|
-
|
|
6
|
-
## Actors and scenarios
|
|
7
|
-
|
|
8
|
-
Model only actors supported by evidence. Prefer roles and jobs over fictional persona
|
|
9
|
-
biographies. For each material scenario, capture:
|
|
10
|
-
|
|
11
|
-
- trigger and preconditions;
|
|
12
|
-
- actor goal;
|
|
13
|
-
- main flow and system response;
|
|
14
|
-
- important failure or recovery path;
|
|
15
|
-
- observable completion signal.
|
|
16
|
-
|
|
17
|
-
## Acceptance and scope
|
|
18
|
-
|
|
19
|
-
Turn scenarios into testable behavior. Each requirement should name the observable
|
|
20
|
-
outcome, relevant constraints, and how success can be verified.
|
|
21
|
-
|
|
22
|
-
Define:
|
|
23
|
-
|
|
24
|
-
- included behavior required for the accepted outcome;
|
|
25
|
-
- explicit non-goals;
|
|
26
|
-
- dependencies and assumptions;
|
|
27
|
-
- deferred work only when a real sequencing decision exists.
|
|
28
|
-
|
|
29
|
-
Avoid arbitrary release buckets or a fixed number of stories. Scope follows the
|
|
30
|
-
validated outcome and delivery constraint.
|
|
31
|
-
|
|
32
|
-
## Success signals
|
|
33
|
-
|
|
34
|
-
Choose metrics only when they will guide a decision. Define the event, population,
|
|
35
|
-
window, source, owner, and interpretation. Do not invent target values; record a
|
|
36
|
-
baseline, a user-approved target, or the experiment needed to establish one.
|
|
37
|
-
|
|
38
|
-
Update the product specification with actors, scenarios, acceptance, scope, non-goals,
|
|
39
|
-
and decision-relevant success signals. Keep business facts in their source system and
|
|
40
|
-
link them rather than copying volatile datasets into the specification.
|
|
@@ -1,80 +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
|
-
const Database = require('better-sqlite3');
|
|
9
|
-
|
|
10
|
-
const legacy = require('./commands/legacy-memory.cjs');
|
|
11
|
-
|
|
12
|
-
function fixture() {
|
|
13
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-legacy-memory-'));
|
|
14
|
-
const ultra = path.join(root, '.ultra');
|
|
15
|
-
fs.mkdirSync(path.join(ultra, 'memory'), { recursive: true });
|
|
16
|
-
fs.writeFileSync(path.join(ultra, 'memory', 'memory.db'), 'legacy-hook-store');
|
|
17
|
-
const db = new Database(path.join(ultra, 'state.db'));
|
|
18
|
-
db.exec(`
|
|
19
|
-
CREATE TABLE memory_entries (
|
|
20
|
-
id INTEGER PRIMARY KEY, kind TEXT, content TEXT, task_id TEXT,
|
|
21
|
-
session_id TEXT, tag TEXT, source TEXT, ts TEXT
|
|
22
|
-
);
|
|
23
|
-
INSERT INTO memory_entries
|
|
24
|
-
(kind, content, task_id, session_id, tag, source, ts)
|
|
25
|
-
VALUES ('decision', 'legacy decision', 't1', 's1', 'arch', 'test', '2026-01-01T00:00:00Z');
|
|
26
|
-
`);
|
|
27
|
-
db.close();
|
|
28
|
-
return root;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
test('inspect reports both legacy stores without mutating them', () => {
|
|
32
|
-
const root = fixture();
|
|
33
|
-
try {
|
|
34
|
-
const result = legacy.inspectLegacy(root);
|
|
35
|
-
assert.equal(result.hook_memory_dir, true);
|
|
36
|
-
assert.equal(result.state_memory_table, true);
|
|
37
|
-
assert.equal(result.state_memory_entries, 1);
|
|
38
|
-
assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
|
|
39
|
-
} finally {
|
|
40
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
test('archive exports legacy state rows and copies hook memory without deletion', () => {
|
|
44
|
-
const root = fixture();
|
|
45
|
-
try {
|
|
46
|
-
const result = legacy.archiveLegacy(root, { timestamp: '2026-07-15T00-00-00-000Z' });
|
|
47
|
-
assert.equal(result.archived, true);
|
|
48
|
-
assert.ok(fs.existsSync(path.join(result.archive_dir, 'hook-memory', 'memory.db')));
|
|
49
|
-
const exported = JSON.parse(fs.readFileSync(path.join(result.archive_dir, 'state-memory-entries.json'), 'utf8'));
|
|
50
|
-
assert.equal(exported.entries.length, 1);
|
|
51
|
-
assert.equal(exported.entries[0].content, 'legacy decision');
|
|
52
|
-
assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
|
|
53
|
-
const db = new Database(path.join(root, '.ultra', 'state.db'), { readonly: true });
|
|
54
|
-
assert.ok(db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'memory_entries'").get());
|
|
55
|
-
db.close();
|
|
56
|
-
} finally {
|
|
57
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('prune requires an exact confirmation and archives before deleting', () => {
|
|
62
|
-
const root = fixture();
|
|
63
|
-
try {
|
|
64
|
-
assert.throws(() => legacy.pruneLegacy(root, { confirm: 'yes' }), /DELETE_ULTRA_LEGACY_MEMORY/);
|
|
65
|
-
assert.ok(fs.existsSync(path.join(root, '.ultra', 'memory', 'memory.db')));
|
|
66
|
-
|
|
67
|
-
const result = legacy.pruneLegacy(root, {
|
|
68
|
-
confirm: 'DELETE_ULTRA_LEGACY_MEMORY',
|
|
69
|
-
timestamp: '2026-07-15T00-00-01-000Z',
|
|
70
|
-
});
|
|
71
|
-
assert.equal(result.pruned, true);
|
|
72
|
-
assert.ok(fs.existsSync(result.archive_dir));
|
|
73
|
-
assert.ok(!fs.existsSync(path.join(root, '.ultra', 'memory')));
|
|
74
|
-
const db = new Database(path.join(root, '.ultra', 'state.db'), { readonly: true });
|
|
75
|
-
assert.equal(db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'memory_entries'").get(), undefined);
|
|
76
|
-
db.close();
|
|
77
|
-
} finally {
|
|
78
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
79
|
-
}
|
|
80
|
-
});
|