ultra-builder-pro-cli 0.13.0 → 0.15.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 +86 -1
- package/README.md +40 -26
- package/adapters/_shared/codex-assets.cjs +29 -165
- package/adapters/_shared/file-ops.cjs +6 -2
- package/adapters/_shared/runtime-assets.cjs +1 -0
- package/adapters/claude.js +3 -2
- package/adapters/codex.js +1 -0
- package/adapters/kimi.js +3 -2
- package/adapters/opencode.js +33 -122
- package/agents/review-coordinator.md +9 -6
- package/docs/AGENT-CONTEXT.md +21 -11
- package/docs/ARCHITECTURE.md +41 -13
- package/docs/PLAN.zh-CN.md +3 -2
- package/docs/ROADMAP.md +9 -5
- package/docs/RUNTIME-COMPAT-MATRIX.md +6 -6
- package/docs/STATE-DB-ACCESS-POLICY.md +27 -25
- package/docs/WORKFLOW-LIFECYCLE.md +266 -0
- package/hooks/README.md +6 -1
- package/hooks/active_task_context.py +22 -50
- package/hooks/context_spine.py +61 -332
- package/hooks/health_check.py +29 -18
- package/hooks/pre_stop_check.py +28 -33
- package/hooks/subagent_tracker.py +54 -85
- package/hooks/workflow_checkpoint.py +39 -47
- package/hooks/workflow_context.py +4 -9
- package/hooks/workflow_resume.py +48 -128
- package/mcp-server/breadcrumb.cjs +22 -0
- 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 +550 -85
- package/mcp-server/lib/context-spine.cjs +242 -24
- package/mcp-server/lib/doctor.cjs +33 -3
- package/mcp-server/lib/init-project.cjs +210 -16
- package/mcp-server/lib/plan-store.cjs +4 -1
- package/mcp-server/lib/prd-parser.cjs +14 -41
- package/mcp-server/lib/pricing.cjs +7 -10
- package/mcp-server/lib/project-breadcrumb.cjs +145 -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 +145 -8
- package/mcp-server/lib/state-ops.cjs +160 -9
- package/mcp-server/lib/task-expander.cjs +9 -38
- package/mcp-server/lib/workflow-state.cjs +2292 -0
- package/mcp-server/server.cjs +182 -50
- package/orchestrator/daemon.cjs +1 -2
- package/orchestrator/dispatch-rules.cjs +5 -31
- package/orchestrator/planner/plan-builder.cjs +21 -12
- package/package.json +14 -6
- package/skills/cc-collab/SKILL.md +2 -2
- package/skills/codex-collab/SKILL.md +2 -3
- package/skills/ultra-change/SKILL.md +53 -68
- package/skills/ultra-change/references/change-contract.md +36 -0
- package/skills/ultra-deliver/SKILL.md +69 -79
- package/skills/ultra-deliver/references/baseline-reconciliation.md +20 -0
- package/skills/ultra-dev/SKILL.md +54 -59
- package/skills/ultra-doctor/SKILL.md +40 -71
- package/skills/ultra-init/SKILL.md +96 -103
- package/skills/ultra-init/references/brownfield-adoption.md +2 -2
- package/skills/ultra-plan/SKILL.md +79 -75
- package/skills/ultra-plan/references/semantic-preflight.md +18 -0
- package/skills/ultra-research/SKILL.md +87 -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 +57 -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 -45
- package/skills/ultra-test/SKILL.md +48 -55
- package/skills/ultra-verify/SKILL.md +22 -31
- package/skills/ultra-verify/references/{confidence-system.md → evidence-status.md} +3 -3
- package/skills/ultra-verify/scripts/verify_wait.py +54 -54
- package/spec/cli-protocol.md +7 -2
- package/spec/mcp-tools.yaml +422 -73
- 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 +82 -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 +213 -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 -131
- 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 -137
- package/adapters/tests/codex-hook.test.cjs +0 -115
- package/adapters/tests/codex.test.cjs +0 -423
- package/adapters/tests/kimi-hook.test.cjs +0 -122
- package/adapters/tests/kimi.test.cjs +0 -259
- package/adapters/tests/opencode.test.cjs +0 -371
- 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/llm-client.cjs +0 -157
- package/mcp-server/lib/llm-client.test.cjs +0 -156
- package/mcp-server/lib/plan-store.test.cjs +0 -132
- package/mcp-server/lib/prd-parser.test.cjs +0 -171
- 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 -324
- 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 -1471
- 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/expand-prompt.cjs +0 -62
- package/orchestrator/planner/expand-prompt.test.cjs +0 -52
- package/orchestrator/planner/plan-builder.test.cjs +0 -165
- package/orchestrator/planner/prd-prompt.cjs +0 -44
- package/orchestrator/planner/prd-prompt.test.cjs +0 -30
- 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,20 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Ultra project status panel.
|
|
4
4
|
//
|
|
5
|
-
// Reads telemetry
|
|
6
|
-
//
|
|
7
|
-
// Does not modify state
|
|
5
|
+
// Reads project authority and telemetry from state.db and renders either a
|
|
6
|
+
// human-readable report or a JSON envelope consumable by skills/ultra-status.
|
|
7
|
+
// Does not modify state.
|
|
8
8
|
|
|
9
9
|
const fs = require('node:fs');
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
|
|
12
12
|
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
13
13
|
const baselines = require('../../mcp-server/lib/baseline-workflow.cjs');
|
|
14
|
+
const workflowState = require('../../mcp-server/lib/workflow-state.cjs');
|
|
15
|
+
const { readBreadcrumb } = require('../../mcp-server/lib/context-spine.cjs');
|
|
14
16
|
|
|
15
17
|
const USAGE = `ultra-tools status [flags]
|
|
16
18
|
|
|
17
|
-
Reads
|
|
19
|
+
Reads .ultra/state.db and prints baseline, workflow, task, session, route, and
|
|
20
|
+
cost-observability status.
|
|
18
21
|
|
|
19
22
|
FLAGS:
|
|
20
23
|
--cost include per-runtime + top-tasks cost panel (default on)
|
|
@@ -65,10 +68,25 @@ function parseFlags(args) {
|
|
|
65
68
|
return out;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd() } = {}) {
|
|
72
|
+
const tables = new Set(db.prepare(
|
|
73
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
|
|
74
|
+
).all().map((row) => row.name));
|
|
75
|
+
const by_runtime = tables.has('telemetry') && tables.has('sessions')
|
|
76
|
+
? ops.aggregateTelemetryByRuntime(db, { since }) : [];
|
|
77
|
+
const top_tasks = tables.has('telemetry') && tables.has('sessions')
|
|
78
|
+
? ops.aggregateTelemetryByTask(db, { since, limit }) : [];
|
|
79
|
+
const knownTotalCost = by_runtime.reduce((acc, r) => acc + (r.cost_usd || 0), 0);
|
|
80
|
+
const unpricedUsageEvents = by_runtime.reduce(
|
|
81
|
+
(acc, row) => acc + Number(row.unpriced_usage_events || 0), 0,
|
|
82
|
+
);
|
|
83
|
+
const pricedUsageEvents = by_runtime.reduce(
|
|
84
|
+
(acc, row) => acc + Number(row.priced_usage_events || 0), 0,
|
|
85
|
+
);
|
|
86
|
+
const costStatus = unpricedUsageEvents > 0
|
|
87
|
+
? (pricedUsageEvents > 0 ? 'partial' : 'unavailable')
|
|
88
|
+
: (pricedUsageEvents > 0 ? 'complete' : 'none');
|
|
89
|
+
const total_cost_usd = unpricedUsageEvents > 0 ? null : knownTotalCost;
|
|
72
90
|
const baselineHealth = baselines.inspectBaseline(db, { rootDir });
|
|
73
91
|
const current = baselineHealth.baseline;
|
|
74
92
|
const baselineStatus = current?.status
|
|
@@ -87,16 +105,165 @@ function buildCostPanel(db, { since = null, limit = 3, rootDir = process.cwd() }
|
|
|
87
105
|
baseline.repository_branch = current.repository_branch || null;
|
|
88
106
|
baseline.worktree_state = current.worktree_state || 'unavailable';
|
|
89
107
|
baseline.gaps = baselines.summarizeGaps(current.gaps);
|
|
108
|
+
if (current.research_run_id && tables.has('workflow_runs') && tables.has('workflow_steps')) {
|
|
109
|
+
const research = workflowState.readWorkflow(db, current.research_run_id, { rootDir });
|
|
110
|
+
baseline.research = research ? {
|
|
111
|
+
id: research.id,
|
|
112
|
+
status: research.status,
|
|
113
|
+
mode: research.mode,
|
|
114
|
+
output_health: research.artifact_health.status,
|
|
115
|
+
blockers: research.artifact_health.blockers,
|
|
116
|
+
} : {
|
|
117
|
+
id: current.research_run_id, status: 'missing', mode: null,
|
|
118
|
+
output_health: 'fail', blockers: ['BASELINE_RESEARCH_RECORD_INVALID'],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const countStatuses = (table, statuses, where = '') => {
|
|
123
|
+
if (!tables.has(table)) {
|
|
124
|
+
return {
|
|
125
|
+
status: 'unavailable',
|
|
126
|
+
...Object.fromEntries(statuses.map((status) => [status, 0])),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const rows = db.prepare(
|
|
130
|
+
`SELECT status, COUNT(*) AS count FROM ${table} ${where} GROUP BY status`,
|
|
131
|
+
).all();
|
|
132
|
+
const counts = Object.fromEntries(statuses.map((status) => [status, 0]));
|
|
133
|
+
for (const row of rows) if (row.status in counts) counts[row.status] = Number(row.count);
|
|
134
|
+
return counts;
|
|
135
|
+
};
|
|
136
|
+
const taskCounts = countStatuses(
|
|
137
|
+
'tasks', ['pending', 'in_progress', 'completed', 'blocked', 'expanded'],
|
|
138
|
+
);
|
|
139
|
+
taskCounts.stale = tables.has('tasks')
|
|
140
|
+
? Number(db.prepare('SELECT COUNT(*) AS count FROM tasks WHERE stale = 1').get().count) : 0;
|
|
141
|
+
const sessions = countStatuses('sessions', ['running', 'completed', 'crashed', 'orphan']);
|
|
142
|
+
const changes = countStatuses('changes', ['active', 'blocked', 'ready', 'archived', 'cancelled']);
|
|
143
|
+
let workflows;
|
|
144
|
+
try {
|
|
145
|
+
if (!tables.has('workflow_runs') || !tables.has('workflow_steps')) {
|
|
146
|
+
workflows = {
|
|
147
|
+
status: 'unavailable', active: 0, blocked: 0, ready: 0,
|
|
148
|
+
stale_outputs: [], historical_stale_outputs: [],
|
|
149
|
+
terminal_authority_runs: [], untracked_active_changes: [],
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
workflows = workflowState.inspectWorkflowHealth(db, { rootDir });
|
|
153
|
+
workflows.current = workflowState.listWorkflows(db, { limit: 500 }, { rootDir })
|
|
154
|
+
.filter((run) => ['active', 'blocked', 'ready'].includes(run.status))
|
|
155
|
+
.map((run) => ({
|
|
156
|
+
id: run.id, kind: run.kind, status: run.status, current_step: run.current_step,
|
|
157
|
+
baseline_id: run.baseline_id, change_id: run.change_id, task_id: run.task_id,
|
|
158
|
+
blockers: run.blockers, output_health: run.artifact_health.status,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
workflows = {
|
|
163
|
+
status: 'fail', active: 0, blocked: 0, ready: 0,
|
|
164
|
+
stale_outputs: [], historical_stale_outputs: [],
|
|
165
|
+
terminal_authority_runs: [], untracked_active_changes: [],
|
|
166
|
+
error: error.code || error.message,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(workflows.current)) workflows.current = [];
|
|
170
|
+
let next;
|
|
171
|
+
let breadcrumb = null;
|
|
172
|
+
try {
|
|
173
|
+
if (!tables.has('baselines')) {
|
|
174
|
+
next = {
|
|
175
|
+
action: 'Resume project initialization to migrate and establish baseline authority.',
|
|
176
|
+
recommended_workflow: 'ultra-init', readiness: 'blocked',
|
|
177
|
+
blockers: ['BASELINE_SCHEMA_MIGRATION_REQUIRED'], warnings: [],
|
|
178
|
+
};
|
|
179
|
+
} else {
|
|
180
|
+
breadcrumb = readBreadcrumb(db, {}, { rootDir });
|
|
181
|
+
next = {
|
|
182
|
+
action: breadcrumb.next_action,
|
|
183
|
+
recommended_workflow: breadcrumb.recommended_workflow,
|
|
184
|
+
readiness: breadcrumb.readiness,
|
|
185
|
+
blockers: breadcrumb.blockers,
|
|
186
|
+
warnings: breadcrumb.warnings,
|
|
187
|
+
change_id: breadcrumb.change_id || null,
|
|
188
|
+
task_id: breadcrumb.task_id || null,
|
|
189
|
+
workflow: breadcrumb.workflow || null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
next = {
|
|
194
|
+
action: 'Run ultra-doctor to inspect authoritative state.',
|
|
195
|
+
recommended_workflow: 'ultra-doctor', readiness: 'blocked',
|
|
196
|
+
blockers: [`STATUS_AUTHORITY_UNAVAILABLE:${error.code || 'unknown'}`], warnings: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const currentChange = tables.has('changes')
|
|
201
|
+
? db.prepare(
|
|
202
|
+
`SELECT id, title, kind, status, intent, base_commit, artifact_root, updated_at
|
|
203
|
+
FROM changes
|
|
204
|
+
WHERE status IN ('active', 'blocked', 'ready')
|
|
205
|
+
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
206
|
+
).get() || null
|
|
207
|
+
: null;
|
|
208
|
+
const evidenceChangeId = breadcrumb?.change_id || currentChange?.id
|
|
209
|
+
|| (tables.has('changes') ? db.prepare(
|
|
210
|
+
`SELECT id FROM changes WHERE status = 'archived'
|
|
211
|
+
ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
212
|
+
).get()?.id : null);
|
|
213
|
+
let currentTask = null;
|
|
214
|
+
if (tables.has('tasks')) {
|
|
215
|
+
const taskId = breadcrumb?.task_id || (currentChange?.id ? db.prepare(
|
|
216
|
+
`SELECT id FROM tasks WHERE change_id = ?
|
|
217
|
+
ORDER BY CASE status WHEN 'in_progress' THEN 0 WHEN 'blocked' THEN 1
|
|
218
|
+
WHEN 'pending' THEN 2 ELSE 3 END, priority ASC, created_at ASC LIMIT 1`,
|
|
219
|
+
).get(currentChange.id)?.id : null);
|
|
220
|
+
if (taskId) currentTask = ops.readTask(db, taskId);
|
|
221
|
+
}
|
|
222
|
+
const evidence = {};
|
|
223
|
+
for (const kind of ['test', 'review', 'deliver']) {
|
|
224
|
+
const run = evidenceChangeId && tables.has('workflow_runs') && tables.has('workflow_steps')
|
|
225
|
+
? workflowState.listWorkflows(
|
|
226
|
+
db, { kind, status: 'completed', change_id: evidenceChangeId, limit: 1 }, { rootDir },
|
|
227
|
+
)[0]
|
|
228
|
+
: null;
|
|
229
|
+
let status = 'missing';
|
|
230
|
+
if (run && kind === 'test') status = run.summary.passed === true ? 'pass' : 'fail';
|
|
231
|
+
if (run && kind === 'review') status = run.summary.verdict || 'unknown';
|
|
232
|
+
if (run && kind === 'deliver') {
|
|
233
|
+
status = run.summary.archive_status === 'archived' ? 'delivered' : 'incomplete';
|
|
234
|
+
}
|
|
235
|
+
const evidenceKey = kind === 'deliver' ? 'delivery' : kind;
|
|
236
|
+
evidence[evidenceKey] = run ? {
|
|
237
|
+
workflow_id: run.id, status, output_health: run.artifact_health.status,
|
|
238
|
+
report_path: run.summary.report_path || null,
|
|
239
|
+
} : { workflow_id: null, status: 'missing', output_health: 'missing', report_path: null };
|
|
90
240
|
}
|
|
91
241
|
return {
|
|
92
242
|
period: { since: since || 'all-time' },
|
|
93
243
|
baseline,
|
|
244
|
+
workflows,
|
|
245
|
+
current_change: currentChange,
|
|
246
|
+
current_task: currentTask,
|
|
247
|
+
evidence,
|
|
248
|
+
changes,
|
|
249
|
+
tasks: taskCounts,
|
|
250
|
+
sessions,
|
|
251
|
+
next,
|
|
94
252
|
by_runtime,
|
|
95
253
|
top_tasks,
|
|
96
254
|
total_cost_usd,
|
|
255
|
+
cost: {
|
|
256
|
+
status: costStatus,
|
|
257
|
+
known_total_usd: knownTotalCost,
|
|
258
|
+
priced_usage_events: pricedUsageEvents,
|
|
259
|
+
unpriced_usage_events: unpricedUsageEvents,
|
|
260
|
+
},
|
|
97
261
|
};
|
|
98
262
|
}
|
|
99
263
|
|
|
264
|
+
// Backward-compatible internal export for consumers that used the old name.
|
|
265
|
+
const buildCostPanel = buildStatusPanel;
|
|
266
|
+
|
|
100
267
|
function formatCost(n) {
|
|
101
268
|
if (!n) return '$0.0000';
|
|
102
269
|
if (n < 0.01) return `$${n.toFixed(6)}`;
|
|
@@ -107,11 +274,44 @@ function renderHuman(panel) {
|
|
|
107
274
|
const lines = [];
|
|
108
275
|
const baseline = panel.baseline;
|
|
109
276
|
lines.push(
|
|
110
|
-
`Baseline: ${baseline.mode || 'none'}/${baseline.status} · ${baseline.health === 'pass' ? 'ready' : 'blocked'}
|
|
277
|
+
`Baseline: ${baseline.mode || 'none'}/${baseline.status} · ${baseline.health === 'pass' ? 'ready' : 'blocked'}`
|
|
278
|
+
+ (baseline.research ? ` · research=${baseline.research.id}/${baseline.research.status}/${baseline.research.output_health}` : ''),
|
|
111
279
|
);
|
|
112
280
|
if (baseline.blockers.length > 0) lines.push(`Baseline blockers: ${baseline.blockers.join(', ')}`);
|
|
281
|
+
lines.push(
|
|
282
|
+
`Workflows: active=${panel.workflows.active} blocked=${panel.workflows.blocked} ready=${panel.workflows.ready} health=${panel.workflows.status}`,
|
|
283
|
+
);
|
|
284
|
+
if (panel.workflows.current.length > 0) {
|
|
285
|
+
const current = panel.workflows.current[0];
|
|
286
|
+
lines.push(
|
|
287
|
+
`Workflow: ${current.id} (${current.kind}/${current.status}) · step=${current.current_step || 'finalize'} · outputs=${current.output_health}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
lines.push(
|
|
291
|
+
`Changes: active=${panel.changes.active} blocked=${panel.changes.blocked} ready=${panel.changes.ready} archived=${panel.changes.archived}`,
|
|
292
|
+
);
|
|
293
|
+
lines.push(
|
|
294
|
+
`Tasks: pending=${panel.tasks.pending} in_progress=${panel.tasks.in_progress} completed=${panel.tasks.completed} blocked=${panel.tasks.blocked} stale=${panel.tasks.stale}`,
|
|
295
|
+
);
|
|
296
|
+
if (panel.current_change) {
|
|
297
|
+
lines.push(`Current change: ${panel.current_change.id} (${panel.current_change.status})`);
|
|
298
|
+
}
|
|
299
|
+
if (panel.current_task) lines.push(`Current task: ${panel.current_task.id} (${panel.current_task.status})`);
|
|
300
|
+
lines.push(
|
|
301
|
+
`Sessions: running=${panel.sessions.running} crashed=${panel.sessions.crashed} orphan=${panel.sessions.orphan}`,
|
|
302
|
+
);
|
|
303
|
+
lines.push(`Next: ${panel.next.action} (${panel.next.recommended_workflow})`);
|
|
304
|
+
lines.push(
|
|
305
|
+
`Evidence: test=${panel.evidence.test.status} review=${panel.evidence.review.status} delivery=${panel.evidence.delivery.status}`,
|
|
306
|
+
);
|
|
113
307
|
lines.push(`Period: ${panel.period.since}`);
|
|
114
|
-
|
|
308
|
+
if (panel.total_cost_usd === null) {
|
|
309
|
+
lines.push(
|
|
310
|
+
`Total cost: unavailable (${panel.cost.unpriced_usage_events} unpriced usage events; known cost ${formatCost(panel.cost.known_total_usd)})`,
|
|
311
|
+
);
|
|
312
|
+
} else {
|
|
313
|
+
lines.push(`Total cost: ${formatCost(panel.total_cost_usd)}`);
|
|
314
|
+
}
|
|
115
315
|
lines.push('');
|
|
116
316
|
lines.push('Cost by runtime:');
|
|
117
317
|
if (panel.by_runtime.length === 0) {
|
|
@@ -163,7 +363,7 @@ function dispatch(args) {
|
|
|
163
363
|
const db = new Database(dbPath, { readonly: true });
|
|
164
364
|
try {
|
|
165
365
|
const rootDir = path.dirname(path.dirname(dbPath));
|
|
166
|
-
const panel =
|
|
366
|
+
const panel = buildStatusPanel(db, { since, limit: flags.limit, rootDir });
|
|
167
367
|
if (flags.json) {
|
|
168
368
|
emit({ ok: true, data: panel });
|
|
169
369
|
} else {
|
|
@@ -178,6 +378,7 @@ function dispatch(args) {
|
|
|
178
378
|
module.exports = {
|
|
179
379
|
USAGE,
|
|
180
380
|
dispatch,
|
|
381
|
+
buildStatusPanel,
|
|
181
382
|
buildCostPanel,
|
|
182
383
|
renderHuman,
|
|
183
384
|
parseSince,
|
|
@@ -1,54 +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 { copyTree, writeAtomic, removeTree } = require('../file-ops.cjs');
|
|
10
|
-
|
|
11
|
-
function mk(prefix = 'ubp-shared-') {
|
|
12
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
test('copyTree copies recursively and skips .DS_Store', () => {
|
|
16
|
-
const src = mk();
|
|
17
|
-
const dst = mk();
|
|
18
|
-
try {
|
|
19
|
-
fs.mkdirSync(path.join(src, 'nested'));
|
|
20
|
-
fs.writeFileSync(path.join(src, 'a.txt'), 'A');
|
|
21
|
-
fs.writeFileSync(path.join(src, '.DS_Store'), 'junk');
|
|
22
|
-
fs.writeFileSync(path.join(src, 'nested', 'b.txt'), 'B');
|
|
23
|
-
|
|
24
|
-
const files = copyTree(src, dst);
|
|
25
|
-
assert.deepEqual(files.sort(), ['a.txt', path.join('nested', 'b.txt')]);
|
|
26
|
-
assert.equal(fs.readFileSync(path.join(dst, 'a.txt'), 'utf8'), 'A');
|
|
27
|
-
assert.equal(fs.readFileSync(path.join(dst, 'nested', 'b.txt'), 'utf8'), 'B');
|
|
28
|
-
assert.ok(!fs.existsSync(path.join(dst, '.DS_Store')));
|
|
29
|
-
} finally {
|
|
30
|
-
removeTree(src);
|
|
31
|
-
removeTree(dst);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('writeAtomic never leaves a torn write — tmp file is renamed', () => {
|
|
36
|
-
const dir = mk();
|
|
37
|
-
try {
|
|
38
|
-
const target = path.join(dir, 'out', 'file.txt');
|
|
39
|
-
writeAtomic(target, 'hello');
|
|
40
|
-
assert.equal(fs.readFileSync(target, 'utf8'), 'hello');
|
|
41
|
-
|
|
42
|
-
const siblings = fs.readdirSync(path.dirname(target));
|
|
43
|
-
assert.deepEqual(siblings, ['file.txt']); // no .tmp-* left behind
|
|
44
|
-
} finally {
|
|
45
|
-
removeTree(dir);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('removeTree refuses to wipe filesystem root without allowRoot', () => {
|
|
50
|
-
assert.throws(
|
|
51
|
-
() => removeTree('/', { allowRoot: false }),
|
|
52
|
-
/refusing to remove filesystem root/,
|
|
53
|
-
);
|
|
54
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { test } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
|
|
6
|
-
const { parse, serialize, lowercaseKeys, patch } = require('../frontmatter.cjs');
|
|
7
|
-
|
|
8
|
-
test('parse extracts frontmatter and body; degraded inputs survive', () => {
|
|
9
|
-
const withFm = '---\ndescription: hi\nmodel: opus\n---\n\n# Hello\n';
|
|
10
|
-
const out = parse(withFm);
|
|
11
|
-
assert.equal(out.fm.description, 'hi');
|
|
12
|
-
assert.equal(out.fm.model, 'opus');
|
|
13
|
-
assert.ok(out.body.startsWith('# Hello'));
|
|
14
|
-
|
|
15
|
-
// no delimiter at all
|
|
16
|
-
const plain = parse('# No frontmatter\n');
|
|
17
|
-
assert.equal(plain.fm, null);
|
|
18
|
-
assert.equal(plain.body, '# No frontmatter\n');
|
|
19
|
-
|
|
20
|
-
// delimiter open but never closed — treat as plain text
|
|
21
|
-
const unclosed = parse('---\nfoo: 1\nno closing delimiter\n');
|
|
22
|
-
assert.equal(unclosed.fm, null);
|
|
23
|
-
assert.ok(unclosed.body.includes('no closing delimiter'));
|
|
24
|
-
|
|
25
|
-
// type guard
|
|
26
|
-
assert.throws(() => parse(42), /expects string/);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('serialize → parse round-trip preserves data', () => {
|
|
30
|
-
const fm = { description: 'round-trip', tags: ['a', 'b'] };
|
|
31
|
-
const body = '# Body\n\nparagraph\n';
|
|
32
|
-
const text = serialize(fm, body);
|
|
33
|
-
const { fm: got, body: gotBody } = parse(text);
|
|
34
|
-
assert.deepEqual(got, fm);
|
|
35
|
-
assert.equal(gotBody, body);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('lowercaseKeys normalizes nested frontmatter (OpenCode contract)', () => {
|
|
39
|
-
const input = { Name: 'X', TAGS: ['T1'], Nested: { Key: 1 } };
|
|
40
|
-
const out = lowercaseKeys(input);
|
|
41
|
-
assert.deepEqual(out, { name: 'X', tags: ['T1'], nested: { key: 1 } });
|
|
42
|
-
|
|
43
|
-
// patch() should let callers transform fm in-place
|
|
44
|
-
const patched = patch('---\nFoo: 1\n---\nbody', (fm) => ({ foo: fm.Foo }));
|
|
45
|
-
const { fm } = parse(patched);
|
|
46
|
-
assert.deepEqual(fm, { foo: 1 });
|
|
47
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const assert = require('node:assert/strict');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
const test = require('node:test');
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
BEGIN_MARKER,
|
|
11
|
-
END_MARKER,
|
|
12
|
-
applyHandbook,
|
|
13
|
-
mergeHandbook,
|
|
14
|
-
renderHandbook,
|
|
15
|
-
resolveHandbookFile,
|
|
16
|
-
} = require('../handbook.cjs');
|
|
17
|
-
|
|
18
|
-
test('renders one common contract with host-native invocation syntax', () => {
|
|
19
|
-
const claude = renderHandbook('claude');
|
|
20
|
-
const codex = renderHandbook('codex');
|
|
21
|
-
const opencode = renderHandbook('opencode');
|
|
22
|
-
const kimi = renderHandbook('kimi');
|
|
23
|
-
|
|
24
|
-
assert.match(claude, /\/ultra-plan/);
|
|
25
|
-
assert.match(codex, /\$ultra-builder-pro:ultra-plan/);
|
|
26
|
-
assert.match(opencode, /\/ultra-plan/);
|
|
27
|
-
assert.match(kimi, /\/ultra-builder-pro:ultra-plan/);
|
|
28
|
-
assert.match(kimi, /Kimi Code/);
|
|
29
|
-
assert.match(kimi, /AgentSwarm/);
|
|
30
|
-
for (const rendered of [claude, codex, opencode, kimi]) {
|
|
31
|
-
assert.match(rendered, /\.ultra\/state\.db/);
|
|
32
|
-
assert.match(rendered, /Separately installed providers/);
|
|
33
|
-
assert.match(rendered, /ultra-change/);
|
|
34
|
-
assert.match(rendered, /ultra-doctor/);
|
|
35
|
-
assert.match(rendered, /metadata references/);
|
|
36
|
-
assert.match(rendered, /Context Spine boundary/);
|
|
37
|
-
assert.match(rendered, /budgets are advisory attention signals/);
|
|
38
|
-
assert.match(rendered, /blocks change convergence/);
|
|
39
|
-
assert.match(rendered, /health-checked atomically at archive/);
|
|
40
|
-
assert.match(rendered, /never traps session stop/);
|
|
41
|
-
assert.match(rendered, /change\.breadcrumb/);
|
|
42
|
-
assert.match(rendered, /Specification learning boundary/);
|
|
43
|
-
assert.doesNotMatch(rendered, /\.ultra\/memory|memory\.retain|memory\.recall/);
|
|
44
|
-
assert.match(rendered, new RegExp(BEGIN_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
45
|
-
assert.match(rendered, new RegExp(END_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('merge preserves user content and is idempotent', () => {
|
|
50
|
-
const existing = '# User rules\n\nKeep this.\n';
|
|
51
|
-
const once = mergeHandbook(existing, 'claude');
|
|
52
|
-
const twice = mergeHandbook(once, 'claude');
|
|
53
|
-
|
|
54
|
-
assert.match(once, /^# User rules/m);
|
|
55
|
-
assert.match(once, /Keep this\./);
|
|
56
|
-
assert.equal(once, twice);
|
|
57
|
-
assert.equal(once.split(BEGIN_MARKER).length - 1, 1);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('Codex legacy Ultra section is replaced without touching the next section', () => {
|
|
61
|
-
const existing = [
|
|
62
|
-
'# Handbook',
|
|
63
|
-
'',
|
|
64
|
-
'## Ultra Builder Pro Runtime Contract',
|
|
65
|
-
'',
|
|
66
|
-
'Old memory contract under `.ultra/memory/`.',
|
|
67
|
-
'',
|
|
68
|
-
'## Standard Operating Workflow',
|
|
69
|
-
'',
|
|
70
|
-
'Keep this workflow.',
|
|
71
|
-
'',
|
|
72
|
-
].join('\n');
|
|
73
|
-
|
|
74
|
-
const merged = mergeHandbook(existing, 'codex');
|
|
75
|
-
assert.doesNotMatch(merged, /Old memory contract/);
|
|
76
|
-
assert.match(merged, /## Standard Operating Workflow\n\nKeep this workflow\./);
|
|
77
|
-
assert.equal(merged.split(BEGIN_MARKER).length - 1, 1);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('apply creates a backup and reports a no-op on the second run', () => {
|
|
81
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ubp-handbook-'));
|
|
82
|
-
const file = path.join(root, 'AGENTS.md');
|
|
83
|
-
fs.writeFileSync(file, '# Existing\n');
|
|
84
|
-
|
|
85
|
-
const first = applyHandbook({ runtime: 'codex', file, now: '20260715T120000Z' });
|
|
86
|
-
assert.equal(first.changed, true);
|
|
87
|
-
assert.equal(first.backup, `${file}.ubp-backup-20260715T120000Z`);
|
|
88
|
-
assert.equal(fs.readFileSync(first.backup, 'utf8'), '# Existing\n');
|
|
89
|
-
|
|
90
|
-
const second = applyHandbook({ runtime: 'codex', file, now: '20260715T120001Z' });
|
|
91
|
-
assert.equal(second.changed, false);
|
|
92
|
-
assert.equal(second.backup, null);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('default handbook paths are host-specific', () => {
|
|
96
|
-
const homeDir = '/tmp/example-home';
|
|
97
|
-
assert.equal(resolveHandbookFile('claude', { homeDir }), path.join(homeDir, '.claude', 'CLAUDE.md'));
|
|
98
|
-
assert.equal(resolveHandbookFile('codex', { homeDir }), path.join(homeDir, '.codex', 'AGENTS.md'));
|
|
99
|
-
assert.equal(resolveHandbookFile('opencode', { homeDir }), path.join(homeDir, '.config', 'opencode', 'AGENTS.md'));
|
|
100
|
-
assert.equal(resolveHandbookFile('kimi', { homeDir }), path.join(homeDir, '.kimi-code', 'AGENTS.md'));
|
|
101
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { test } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const {
|
|
8
|
-
parseSkillRef,
|
|
9
|
-
resolveSkillRef,
|
|
10
|
-
rewriteWorkflowRefInText,
|
|
11
|
-
resolveAssetTarget,
|
|
12
|
-
} = require('../path-rewrite.cjs');
|
|
13
|
-
|
|
14
|
-
test('parseSkillRef accepts canonical @skills/<slug>/SKILL.md only', () => {
|
|
15
|
-
assert.deepEqual(parseSkillRef('@skills/ultra-init/SKILL.md'), {
|
|
16
|
-
name: 'ultra-init',
|
|
17
|
-
filename: 'SKILL.md',
|
|
18
|
-
});
|
|
19
|
-
assert.equal(parseSkillRef('skills/ultra-init/SKILL.md'), null); // missing @
|
|
20
|
-
assert.equal(parseSkillRef('@skills/Bad-Case/SKILL.md'), null); // uppercase slug
|
|
21
|
-
assert.equal(parseSkillRef(null), null);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('resolveSkillRef routes to runtime-specific root under homeDir', () => {
|
|
25
|
-
const home = '/Users/test';
|
|
26
|
-
const claude = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'claude', homeDir: home });
|
|
27
|
-
const opencode = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'opencode', homeDir: home });
|
|
28
|
-
const codex = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'codex', homeDir: home });
|
|
29
|
-
const kimi = resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'kimi', homeDir: home });
|
|
30
|
-
|
|
31
|
-
assert.equal(claude, path.join(home, '.claude/skills/ultra-init/SKILL.md'));
|
|
32
|
-
assert.equal(opencode, path.join(home, '.config/opencode/skills/ultra-init/SKILL.md'));
|
|
33
|
-
assert.equal(codex, path.join(home, '.agents/skills/ultra-init/SKILL.md'));
|
|
34
|
-
assert.equal(kimi, path.join(home, '.kimi-code/skills/ultra-init/SKILL.md'));
|
|
35
|
-
|
|
36
|
-
assert.throws(
|
|
37
|
-
() => resolveSkillRef('@skills/ultra-init/SKILL.md', { runtime: 'unknown', homeDir: home }),
|
|
38
|
-
/unknown runtime/,
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('rewriteWorkflowRefInText + resolveAssetTarget give predictable paths', () => {
|
|
43
|
-
const home = '/h';
|
|
44
|
-
const cwd = '/w';
|
|
45
|
-
const input = 'see @skills/ultra-init/SKILL.md for details';
|
|
46
|
-
const out = rewriteWorkflowRefInText(input, { runtime: 'claude', homeDir: home });
|
|
47
|
-
assert.equal(out, `see ${path.join(home, '.claude/skills/ultra-init/SKILL.md')} for details`);
|
|
48
|
-
|
|
49
|
-
// Cover every runtime × every scope so runtimeRoot/localRoot branches hit
|
|
50
|
-
for (const rt of ['claude', 'opencode', 'codex', 'kimi']) {
|
|
51
|
-
const g = resolveAssetTarget('x', { runtime: rt, scope: 'global', homeDir: home, cwd });
|
|
52
|
-
const l = resolveAssetTarget('x', { runtime: rt, scope: 'local', homeDir: home, cwd });
|
|
53
|
-
assert.ok(g.startsWith(home));
|
|
54
|
-
assert.ok(l.startsWith(cwd));
|
|
55
|
-
}
|
|
56
|
-
assert.throws(
|
|
57
|
-
() => resolveAssetTarget('x', { runtime: 'unknown', scope: 'global', homeDir: home, cwd }),
|
|
58
|
-
/unknown runtime/,
|
|
59
|
-
);
|
|
60
|
-
});
|