ultra-builder-pro-cli 0.20.0 → 0.22.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 +26 -5
- package/.ultra-template/specs/discovery.md +1 -1
- package/.ultra-template/tasks/tasks.json +10 -3
- package/.ultra-template/templates/task-context.md +19 -0
- package/CHANGELOG.md +106 -0
- package/README.md +215 -41
- package/adapters/_shared/codex-assets.cjs +69 -11
- package/adapters/_shared/interaction-contract.cjs +36 -3
- package/adapters/_shared/runtime-assets.cjs +41 -1
- package/adapters/claude.js +7 -5
- package/adapters/codex.js +1 -1
- package/adapters/kimi.js +2 -8
- package/adapters/opencode.js +43 -30
- package/bin/orchestrator.js +64 -24
- package/docs/AGENT-CONTEXT.md +89 -49
- package/docs/ARCHITECTURE.md +112 -45
- package/docs/ARTIFACT-AUTHORITY.md +274 -0
- package/docs/COMMIT-HASH-BACKFILL.md +33 -90
- package/docs/DECISIONS.md +14 -6
- package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
- package/docs/ROADMAP.md +20 -6
- package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
- package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
- package/docs/WORKFLOW-LIFECYCLE.md +105 -50
- package/hooks/README.md +10 -8
- package/hooks/active_task_context.py +105 -18
- package/hooks/context_spine.py +16 -4
- package/hooks/health_check.py +5 -4
- package/hooks/pre_stop_check.py +5 -2
- package/hooks/runtime_paths.py +396 -0
- package/hooks/subagent_tracker.py +5 -2
- package/hooks/workflow_checkpoint.py +16 -3
- package/hooks/workflow_context.py +11 -3
- package/hooks/workflow_resume.py +7 -4
- package/mcp-server/breadcrumb.cjs +20 -2
- package/mcp-server/lib/archive-journal.cjs +1026 -111
- package/mcp-server/lib/archive-mutation-worker.py +365 -0
- package/mcp-server/lib/artifact-registry.cjs +1127 -0
- package/mcp-server/lib/baseline-workflow.cjs +69 -28
- package/mcp-server/lib/change-packet.cjs +818 -0
- package/mcp-server/lib/change-workflow.cjs +932 -176
- package/mcp-server/lib/context-paths.cjs +142 -0
- package/mcp-server/lib/context-spine.cjs +511 -45
- package/mcp-server/lib/decision-dialogue.cjs +345 -3
- package/mcp-server/lib/delivery-transaction.cjs +471 -0
- package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
- package/mcp-server/lib/doctor.cjs +401 -11
- package/mcp-server/lib/git-bootstrap.cjs +458 -47
- package/mcp-server/lib/init-project.cjs +240 -95
- package/mcp-server/lib/plan-publication-worker.cjs +830 -0
- package/mcp-server/lib/plan-store.cjs +700 -5
- package/mcp-server/lib/project-breadcrumb.cjs +23 -4
- package/mcp-server/lib/projector.cjs +211 -42
- package/mcp-server/lib/runtime-paths.cjs +1687 -0
- package/mcp-server/lib/safe-project-file.cjs +385 -0
- package/mcp-server/lib/spec-learning.cjs +28 -12
- package/mcp-server/lib/state-authority.cjs +19 -3
- package/mcp-server/lib/state-db.cjs +263 -9
- package/mcp-server/lib/state-ops.cjs +254 -50
- package/mcp-server/lib/task-ledger.cjs +1342 -0
- package/mcp-server/lib/telemetry.cjs +12 -3
- package/mcp-server/lib/workflow-state.cjs +610 -58
- package/mcp-server/server.cjs +423 -42
- package/orchestrator/daemon.cjs +2 -10
- package/orchestrator/parallel-orchestrator.cjs +314 -50
- package/orchestrator/planner/plan-builder.cjs +1 -1
- package/orchestrator/recovery.cjs +125 -7
- package/orchestrator/session-close-journal-worker.cjs +5 -0
- package/orchestrator/session-close-journal.cjs +1014 -0
- package/orchestrator/session-runner.cjs +679 -69
- package/orchestrator/worktree-manager.cjs +186 -35
- package/package.json +2 -3
- package/skills/cc-collab/SKILL.md +4 -1
- package/skills/codex-collab/SKILL.md +5 -2
- package/skills/ultra-change/SKILL.md +34 -8
- package/skills/ultra-deliver/SKILL.md +26 -11
- package/skills/ultra-dev/SKILL.md +52 -10
- package/skills/ultra-doctor/SKILL.md +16 -3
- package/skills/ultra-init/SKILL.md +22 -11
- package/skills/ultra-plan/SKILL.md +58 -18
- package/skills/ultra-research/SKILL.md +37 -35
- package/skills/ultra-review/SKILL.md +14 -8
- package/skills/ultra-status/SKILL.md +18 -9
- package/skills/ultra-test/SKILL.md +11 -5
- package/skills/ultra-think/SKILL.md +6 -2
- package/skills/ultra-think/references/decision-dialogue.md +17 -6
- package/skills/ultra-verify/SKILL.md +4 -1
- package/spec/cli-protocol.md +21 -7
- package/spec/mcp-tools.yaml +629 -44
- package/spec/schemas/change-delta.v1.schema.json +153 -0
- package/spec/schemas/context-file.v4.5.schema.json +1 -1
- package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +51 -14
- package/spec/schemas/task-ledger.v1.schema.json +217 -0
- package/spec/schemas/tasks.v4.5.schema.json +1 -1
- package/ultra-tools/cli.cjs +3 -2
- package/ultra-tools/commands/db.cjs +76 -25
- package/ultra-tools/commands/migrate.cjs +1000 -82
- package/ultra-tools/commands/session.cjs +34 -8
- package/ultra-tools/commands/status.cjs +32 -6
- package/ultra-tools/commands/system.cjs +129 -22
- package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
- package/commands/learn.md +0 -10
- package/orchestrator/skill-miner.cjs +0 -133
- package/output-styles/coding-vibes.md +0 -67
- package/output-styles/structural-thinking.md +0 -61
- package/skills/learn/SKILL.md +0 -58
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
const path = require('node:path');
|
|
10
10
|
const { initStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
|
|
11
11
|
const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
12
|
+
const runtimePaths = require('../../mcp-server/lib/runtime-paths.cjs');
|
|
12
13
|
const sessionRunner = require('../../orchestrator/session-runner.cjs');
|
|
13
14
|
|
|
14
|
-
const DEFAULT_DB_PATH =
|
|
15
|
+
const DEFAULT_DB_PATH = runtimePaths.STATE_DB_RELATIVE_PATH;
|
|
15
16
|
|
|
16
17
|
const USAGE = `ultra-tools session <verb> [flags]
|
|
17
18
|
|
|
@@ -21,11 +22,11 @@ VERBS:
|
|
|
21
22
|
reap [--grace-seconds <n>] orphan sweeper (Phase 4.5.6)
|
|
22
23
|
get --sid <sid>
|
|
23
24
|
heartbeat --sid <sid>
|
|
24
|
-
close --sid <sid> --status completed|crashed
|
|
25
|
+
close --sid <sid> --status completed|crashed [--remove-worktree]
|
|
25
26
|
subscribe --since-id <n> [--sid <sid>] [--limit <n>]
|
|
26
27
|
|
|
27
28
|
GLOBAL FLAGS:
|
|
28
|
-
--db <path> path to state.db (default: .ultra/state.db)
|
|
29
|
+
--db <path> path to state.db (default: .ultra/.runtime/state.db)
|
|
29
30
|
--repo-root <path> repository root for verified worktree cleanup (default: cwd)
|
|
30
31
|
-h, --help show this message
|
|
31
32
|
`;
|
|
@@ -56,10 +57,31 @@ function parseFlags(args) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function withDb(flags, fn) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
let db;
|
|
61
|
+
try {
|
|
62
|
+
const rootDir = path.resolve(flags.repo_root || process.cwd());
|
|
63
|
+
const env = flags.db
|
|
64
|
+
? { UBP_DB_PATH: path.resolve(flags.db) }
|
|
65
|
+
: process.env;
|
|
66
|
+
const dbPath = runtimePaths.resolveConfiguredStateDb(rootDir, {
|
|
67
|
+
env,
|
|
68
|
+
migrateLegacy: true,
|
|
69
|
+
}).stateDbPath;
|
|
70
|
+
({ db } = initStateDb(dbPath));
|
|
71
|
+
return fn(db);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
emit({
|
|
74
|
+
ok: false,
|
|
75
|
+
error: {
|
|
76
|
+
code: error.code || 'STATE_DB_ERROR',
|
|
77
|
+
message: error.message,
|
|
78
|
+
retriable: false,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return 2;
|
|
82
|
+
} finally {
|
|
83
|
+
try { closeStateDb(db); } catch (_) { /* ignore */ }
|
|
84
|
+
}
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
function cmdList(flags) {
|
|
@@ -84,7 +106,11 @@ function cmdAdmission(flags) {
|
|
|
84
106
|
if (!flags.task_id) { emit({ ok: false, error: { code: 'VALIDATION_ERROR', message: 'missing --task-id' } }); return 1; }
|
|
85
107
|
return withDb(flags, (db) => {
|
|
86
108
|
try {
|
|
87
|
-
const v =
|
|
109
|
+
const v = sessionRunner.admissionCheck(
|
|
110
|
+
db,
|
|
111
|
+
path.resolve(flags.repo_root || process.cwd()),
|
|
112
|
+
flags.task_id,
|
|
113
|
+
);
|
|
88
114
|
emit({ ok: true, data: v });
|
|
89
115
|
return 0;
|
|
90
116
|
} catch (err) {
|
|
@@ -13,11 +13,13 @@ const ops = require('../../mcp-server/lib/state-ops.cjs');
|
|
|
13
13
|
const baselines = require('../../mcp-server/lib/baseline-workflow.cjs');
|
|
14
14
|
const workflowState = require('../../mcp-server/lib/workflow-state.cjs');
|
|
15
15
|
const decisionDialogue = require('../../mcp-server/lib/decision-dialogue.cjs');
|
|
16
|
+
const artifactRegistry = require('../../mcp-server/lib/artifact-registry.cjs');
|
|
16
17
|
const { readBreadcrumb } = require('../../mcp-server/lib/context-spine.cjs');
|
|
18
|
+
const runtimePaths = require('../../mcp-server/lib/runtime-paths.cjs');
|
|
17
19
|
|
|
18
20
|
const USAGE = `ultra-tools status [flags]
|
|
19
21
|
|
|
20
|
-
Reads .ultra/state.db and prints baseline, workflow, task, session, valid transitions, and
|
|
22
|
+
Reads .ultra/.runtime/state.db and prints baseline, workflow, task, session, valid transitions, and
|
|
21
23
|
cost-observability status.
|
|
22
24
|
|
|
23
25
|
FLAGS:
|
|
@@ -175,6 +177,16 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
|
|
|
175
177
|
awaiting_blocking: 0, checkpoint_ready: 0,
|
|
176
178
|
deferred_blocking: 0, stale_artifacts: [], current: null, current_thread_id: null,
|
|
177
179
|
};
|
|
180
|
+
const artifactTables = [
|
|
181
|
+
'artifacts', 'artifact_edges', 'baselines', 'workflow_steps',
|
|
182
|
+
'decision_threads', 'context_snapshots',
|
|
183
|
+
];
|
|
184
|
+
const artifacts = artifactTables.every((table) => tables.has(table))
|
|
185
|
+
? artifactRegistry.inspectArtifactHealth(db, { rootDir })
|
|
186
|
+
: {
|
|
187
|
+
status: 'unavailable', registered: 0, managed: 0, unmanaged: 0,
|
|
188
|
+
issues: [], counts: {},
|
|
189
|
+
};
|
|
178
190
|
let transitions;
|
|
179
191
|
let breadcrumb = null;
|
|
180
192
|
try {
|
|
@@ -249,6 +261,7 @@ function buildStatusPanel(db, { since = null, limit = 3, rootDir = process.cwd()
|
|
|
249
261
|
baseline,
|
|
250
262
|
workflows,
|
|
251
263
|
decisions,
|
|
264
|
+
artifacts,
|
|
252
265
|
current_change: currentChange,
|
|
253
266
|
current_task: currentTask,
|
|
254
267
|
evidence,
|
|
@@ -301,6 +314,11 @@ function renderHuman(panel) {
|
|
|
301
314
|
} else if (panel.decisions.checkpoint_ready > 0) {
|
|
302
315
|
lines.push(`Decision checkpoint: ${panel.decisions.current_thread_id} awaits confirmation`);
|
|
303
316
|
}
|
|
317
|
+
lines.push(
|
|
318
|
+
`Artifacts: registered=${panel.artifacts.registered} managed=${panel.artifacts.managed}`
|
|
319
|
+
+ ` unmanaged=${panel.artifacts.unmanaged} health=${panel.artifacts.status}`
|
|
320
|
+
+ (panel.artifacts.issues.length > 0 ? ` issues=${panel.artifacts.issues.length}` : ''),
|
|
321
|
+
);
|
|
304
322
|
lines.push(
|
|
305
323
|
`Changes: active=${panel.changes.active} blocked=${panel.changes.blocked} ready=${panel.changes.ready} archived=${panel.changes.archived}`,
|
|
306
324
|
);
|
|
@@ -355,14 +373,13 @@ function renderHuman(panel) {
|
|
|
355
373
|
return lines.join('\n') + '\n';
|
|
356
374
|
}
|
|
357
375
|
|
|
358
|
-
function resolveDbPath() {
|
|
359
|
-
|
|
360
|
-
return path.resolve('.ultra', 'state.db');
|
|
376
|
+
function resolveDbPath(rootDir = process.cwd(), env = process.env) {
|
|
377
|
+
return runtimePaths.locateStateDb(rootDir, { env });
|
|
361
378
|
}
|
|
362
379
|
|
|
363
380
|
function resolveRootDir(dbPath) {
|
|
364
381
|
if (process.env.UBP_ROOT_DIR) return path.resolve(process.env.UBP_ROOT_DIR);
|
|
365
|
-
return
|
|
382
|
+
return runtimePaths.projectRootFromStateDbPath(dbPath);
|
|
366
383
|
}
|
|
367
384
|
|
|
368
385
|
function dispatch(args) {
|
|
@@ -372,7 +389,15 @@ function dispatch(args) {
|
|
|
372
389
|
process.stdout.write(USAGE);
|
|
373
390
|
return 0;
|
|
374
391
|
}
|
|
375
|
-
const
|
|
392
|
+
const discoveryRoot = process.env.UBP_ROOT_DIR
|
|
393
|
+
? path.resolve(process.env.UBP_ROOT_DIR)
|
|
394
|
+
: process.cwd();
|
|
395
|
+
let dbPath;
|
|
396
|
+
try {
|
|
397
|
+
dbPath = resolveDbPath(discoveryRoot, process.env);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return fail(error.code || 'STATE_DB_PATH_INVALID', error.message);
|
|
400
|
+
}
|
|
376
401
|
if (!fs.existsSync(dbPath)) {
|
|
377
402
|
return fail('STATE_DB_MISSING', `state.db not found at ${dbPath}`);
|
|
378
403
|
}
|
|
@@ -403,5 +428,6 @@ module.exports = {
|
|
|
403
428
|
renderHuman,
|
|
404
429
|
parseSince,
|
|
405
430
|
parseFlags,
|
|
431
|
+
resolveDbPath,
|
|
406
432
|
resolveRootDir,
|
|
407
433
|
};
|
|
@@ -7,6 +7,7 @@ const Database = require('better-sqlite3');
|
|
|
7
7
|
const { initStateDb, openStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
|
|
8
8
|
const { initProject } = require('../../mcp-server/lib/init-project.cjs');
|
|
9
9
|
const doctor = require('../../mcp-server/lib/doctor.cjs');
|
|
10
|
+
const runtimePaths = require('../../mcp-server/lib/runtime-paths.cjs');
|
|
10
11
|
|
|
11
12
|
const RESTORE_CONFIRMATION = 'REPLACE_CORRUPT_ULTRA_STATE';
|
|
12
13
|
const REBASELINE_CONFIRMATION = 'REBASELINE_CORRUPT_ULTRA_STATE';
|
|
@@ -14,7 +15,7 @@ const USAGE = `ultra-tools system <verb> [options]
|
|
|
14
15
|
|
|
15
16
|
VERBS:
|
|
16
17
|
doctor [--repair]
|
|
17
|
-
restore --backup <.ultra/backups/...db> --confirm ${RESTORE_CONFIRMATION}
|
|
18
|
+
restore --backup <.ultra/.runtime/backups/...db> --confirm ${RESTORE_CONFIRMATION}
|
|
18
19
|
rebaseline --project-name <name> [--scope <path>] --confirm ${REBASELINE_CONFIRMATION}
|
|
19
20
|
`;
|
|
20
21
|
|
|
@@ -96,9 +97,15 @@ function inspectSqliteSnapshot(file) {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function ensureCorruptState(rootDir) {
|
|
99
|
-
const statePath =
|
|
100
|
+
const statePath = runtimePaths.ensureRuntimeState(rootDir).stateDbPath;
|
|
100
101
|
const state = inspectSqliteSnapshot(statePath);
|
|
101
|
-
if (!state.exists)
|
|
102
|
+
if (!state.exists) {
|
|
103
|
+
throw new SystemCommandError(
|
|
104
|
+
'STATE_DB_MISSING',
|
|
105
|
+
`.ultra/.runtime/state.db not found under ${rootDir}`,
|
|
106
|
+
2,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
102
109
|
if (!state.corrupt) {
|
|
103
110
|
throw new SystemCommandError(
|
|
104
111
|
'STATE_DB_NOT_CORRUPT',
|
|
@@ -109,44 +116,84 @@ function ensureCorruptState(rootDir) {
|
|
|
109
116
|
return statePath;
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
function resolveThroughExistingAncestor(candidate) {
|
|
120
|
+
const missing = [];
|
|
121
|
+
let current = path.resolve(candidate);
|
|
122
|
+
while (!fs.existsSync(current)) {
|
|
123
|
+
const parent = path.dirname(current);
|
|
124
|
+
if (parent === current) return path.resolve(candidate);
|
|
125
|
+
missing.unshift(path.basename(current));
|
|
126
|
+
current = parent;
|
|
127
|
+
}
|
|
128
|
+
return path.join(fs.realpathSync(current), ...missing);
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
function managedBackup(rootDir, candidate) {
|
|
113
132
|
if (!candidate) throw new SystemCommandError('VALIDATION_ERROR', '--backup is required');
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
const runtimeBackupRoot = runtimePaths.pathsFor(rootDir).backupsDir;
|
|
134
|
+
const legacyBackupRoot = path.join(rootDir, '.ultra', 'backups');
|
|
135
|
+
const backupRoots = [runtimeBackupRoot, legacyBackupRoot].filter((entry) => fs.existsSync(entry));
|
|
136
|
+
if (backupRoots.length === 0) {
|
|
137
|
+
throw new SystemCommandError(
|
|
138
|
+
'BACKUP_NOT_FOUND',
|
|
139
|
+
`managed backup directory does not exist: ${runtimeBackupRoot}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
let requested = resolveThroughExistingAncestor(
|
|
143
|
+
path.isAbsolute(candidate) ? candidate : path.resolve(rootDir, candidate),
|
|
144
|
+
);
|
|
145
|
+
const legacyPrefix = `${resolveThroughExistingAncestor(legacyBackupRoot)}${path.sep}`;
|
|
146
|
+
if (!fs.existsSync(requested) && requested.startsWith(legacyPrefix)) {
|
|
147
|
+
requested = path.join(runtimeBackupRoot, path.relative(legacyBackupRoot, requested));
|
|
117
148
|
}
|
|
118
|
-
const resolvedRoot = fs.realpathSync(backupRoot);
|
|
119
|
-
const requested = path.isAbsolute(candidate) ? candidate : path.resolve(rootDir, candidate);
|
|
120
149
|
let resolved;
|
|
121
150
|
try { resolved = fs.realpathSync(requested); }
|
|
122
151
|
catch {
|
|
123
152
|
throw new SystemCommandError('BACKUP_NOT_FOUND', `backup does not exist: ${requested}`);
|
|
124
153
|
}
|
|
125
|
-
|
|
154
|
+
const managed = backupRoots.some((entry) => {
|
|
155
|
+
const resolvedRoot = fs.realpathSync(entry);
|
|
156
|
+
return resolved.startsWith(`${resolvedRoot}${path.sep}`);
|
|
157
|
+
});
|
|
158
|
+
if (!managed) {
|
|
126
159
|
throw new SystemCommandError(
|
|
127
160
|
'BACKUP_OUTSIDE_MANAGED_ROOT',
|
|
128
|
-
`backup must be inside ${
|
|
161
|
+
`backup must be inside ${runtimeBackupRoot}`,
|
|
129
162
|
);
|
|
130
163
|
}
|
|
131
164
|
if (!fs.statSync(resolved).isFile()) {
|
|
132
165
|
throw new SystemCommandError('BACKUP_NOT_FOUND', `backup is not a file: ${resolved}`);
|
|
133
166
|
}
|
|
134
|
-
const
|
|
167
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
168
|
+
const member = `${resolved}${suffix}`;
|
|
169
|
+
if (!fs.existsSync(member)) continue;
|
|
170
|
+
const stat = fs.lstatSync(member);
|
|
171
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
172
|
+
throw new SystemCommandError(
|
|
173
|
+
'BACKUP_INVALID',
|
|
174
|
+
`backup snapshot member must be a regular file: ${member}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const inspection = inspectSqliteSnapshot(resolved);
|
|
135
179
|
if (!inspection.valid) {
|
|
136
|
-
throw new SystemCommandError(
|
|
180
|
+
throw new SystemCommandError(
|
|
181
|
+
'BACKUP_INVALID',
|
|
182
|
+
`backup is not a verified Ultra SQLite snapshot: ${resolved}`,
|
|
183
|
+
);
|
|
137
184
|
}
|
|
138
185
|
return resolved;
|
|
139
186
|
}
|
|
140
187
|
|
|
141
188
|
function createRecoveryDir(rootDir, prefix) {
|
|
142
|
-
const dir = path.join(rootDir
|
|
189
|
+
const dir = path.join(runtimePaths.pathsFor(rootDir).backupsDir, `${prefix}-${stamp()}`);
|
|
143
190
|
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
144
191
|
fs.mkdirSync(dir, { recursive: false });
|
|
145
192
|
return dir;
|
|
146
193
|
}
|
|
147
194
|
|
|
148
195
|
function moveStateFiles(statePath, recoveryDir, moved = []) {
|
|
149
|
-
for (const suffix of ['', '-
|
|
196
|
+
for (const suffix of ['-wal', '-shm', '']) {
|
|
150
197
|
const source = `${statePath}${suffix}`;
|
|
151
198
|
if (!fs.existsSync(source)) continue;
|
|
152
199
|
const target = path.join(recoveryDir, `state.db${suffix}`);
|
|
@@ -175,6 +222,52 @@ function restoreMovedFiles(moved) {
|
|
|
175
222
|
return errors;
|
|
176
223
|
}
|
|
177
224
|
|
|
225
|
+
function copySnapshot(source, target) {
|
|
226
|
+
const copied = [];
|
|
227
|
+
try {
|
|
228
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
229
|
+
const member = `${source}${suffix}`;
|
|
230
|
+
if (!fs.existsSync(member)) continue;
|
|
231
|
+
const staged = `${target}${suffix}`;
|
|
232
|
+
fs.copyFileSync(member, staged, fs.constants.COPYFILE_EXCL);
|
|
233
|
+
copied.push(staged);
|
|
234
|
+
}
|
|
235
|
+
const inspection = inspectSqlite(target);
|
|
236
|
+
if (!inspection.valid) {
|
|
237
|
+
throw new SystemCommandError(
|
|
238
|
+
'BACKUP_INVALID',
|
|
239
|
+
`staged backup is not a verified Ultra SQLite snapshot: ${source}`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return copied;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
for (const file of copied) {
|
|
245
|
+
try { fs.rmSync(file, { force: true }); } catch { /* best effort */ }
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function installSnapshot(staging, activeState) {
|
|
252
|
+
const installed = [];
|
|
253
|
+
try {
|
|
254
|
+
// The main DB is the authority completion marker, so publish it last.
|
|
255
|
+
for (const suffix of ['-wal', '-shm', '']) {
|
|
256
|
+
const source = `${staging}${suffix}`;
|
|
257
|
+
if (!fs.existsSync(source)) continue;
|
|
258
|
+
const target = `${activeState}${suffix}`;
|
|
259
|
+
fs.renameSync(source, target);
|
|
260
|
+
installed.push(target);
|
|
261
|
+
}
|
|
262
|
+
return installed;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
for (const target of installed.reverse()) {
|
|
265
|
+
try { fs.rmSync(target, { force: true }); } catch { /* rollback continues */ }
|
|
266
|
+
}
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
178
271
|
async function dispatchRestore(args) {
|
|
179
272
|
let options;
|
|
180
273
|
try { options = parseOptions(args, new Set(['backup', 'confirm'])); }
|
|
@@ -194,14 +287,15 @@ async function dispatchRestore(args) {
|
|
|
194
287
|
let moved = [];
|
|
195
288
|
let activeState;
|
|
196
289
|
let staging;
|
|
290
|
+
let installed = [];
|
|
197
291
|
try {
|
|
198
292
|
activeState = ensureCorruptState(rootDir);
|
|
199
293
|
const sourceBackup = managedBackup(rootDir, options.backup);
|
|
200
294
|
const recoveryDir = createRecoveryDir(rootDir, 'restore');
|
|
201
|
-
staging = path.join(rootDir
|
|
202
|
-
|
|
295
|
+
staging = path.join(runtimePaths.pathsFor(rootDir).runtimeDir, `.state.restore-${stamp()}.db`);
|
|
296
|
+
copySnapshot(sourceBackup, staging);
|
|
203
297
|
moveStateFiles(activeState, recoveryDir, moved);
|
|
204
|
-
|
|
298
|
+
installed = installSnapshot(staging, activeState);
|
|
205
299
|
const initialized = initStateDb(activeState);
|
|
206
300
|
closeStateDb(initialized.db);
|
|
207
301
|
emit({
|
|
@@ -219,7 +313,12 @@ async function dispatchRestore(args) {
|
|
|
219
313
|
return 0;
|
|
220
314
|
} catch (error) {
|
|
221
315
|
if (staging) {
|
|
222
|
-
|
|
316
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
317
|
+
try { fs.rmSync(`${staging}${suffix}`, { force: true }); } catch { /* rollback continues */ }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const target of installed.reverse()) {
|
|
321
|
+
try { fs.rmSync(target, { force: true }); } catch { /* rollback continues */ }
|
|
223
322
|
}
|
|
224
323
|
const rollbackErrors = activeState ? restoreMovedFiles(moved) : [];
|
|
225
324
|
emit({
|
|
@@ -291,7 +390,7 @@ async function dispatchRebaseline(args) {
|
|
|
291
390
|
} catch (error) {
|
|
292
391
|
const rollbackErrors = [];
|
|
293
392
|
if (moved.length > 0) {
|
|
294
|
-
const generatedState = activeState ||
|
|
393
|
+
const generatedState = activeState || runtimePaths.pathsFor(rootDir).stateDbPath;
|
|
295
394
|
for (const suffix of ['', '-wal', '-shm']) {
|
|
296
395
|
try { fs.rmSync(`${generatedState}${suffix}`, { force: true }); }
|
|
297
396
|
catch (rollbackError) { rollbackErrors.push(`generated state cleanup failed: ${rollbackError.message}`); }
|
|
@@ -330,14 +429,22 @@ async function dispatchDoctor(args) {
|
|
|
330
429
|
return 1;
|
|
331
430
|
}
|
|
332
431
|
const rootDir = process.cwd();
|
|
333
|
-
const
|
|
432
|
+
const repair = args.includes('--repair');
|
|
433
|
+
const dbPath = repair
|
|
434
|
+
? runtimePaths.ensureRuntimeState(rootDir).stateDbPath
|
|
435
|
+
: runtimePaths.locateStateDb(rootDir);
|
|
334
436
|
if (!fs.existsSync(dbPath)) {
|
|
335
|
-
emit({
|
|
437
|
+
emit({
|
|
438
|
+
ok: false,
|
|
439
|
+
error: {
|
|
440
|
+
code: 'STATE_DB_MISSING',
|
|
441
|
+
message: `.ultra/.runtime/state.db not found under ${rootDir}`,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
336
444
|
return 2;
|
|
337
445
|
}
|
|
338
446
|
let db;
|
|
339
447
|
try {
|
|
340
|
-
const repair = args.includes('--repair');
|
|
341
448
|
const initialized = repair ? initStateDb(dbPath) : null;
|
|
342
449
|
db = initialized ? initialized.db : openStateDb(dbPath);
|
|
343
450
|
const data = await doctor.runDoctor(db, { rootDir, repair: args.includes('--repair') });
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!-- The projector owns YAML frontmatter and the generated execution contract. -->
|
|
2
|
-
|
|
3
|
-
# Task Notes
|
|
4
|
-
|
|
5
|
-
Use this body only for task-specific implementation discoveries that are not already
|
|
6
|
-
authoritative task fields. Stable specification learning must follow the change
|
|
7
|
-
approval workflow.
|
|
8
|
-
|
|
9
|
-
## Implementation Notes
|
|
10
|
-
|
|
11
|
-
- [Evidence-backed note with a source reference]
|
|
12
|
-
|
|
13
|
-
## Open Questions
|
|
14
|
-
|
|
15
|
-
- [Question, consequence, owner, and next evidence]
|
|
16
|
-
|
|
17
|
-
## Recovery Notes
|
|
18
|
-
|
|
19
|
-
- [Current safe resume point and any external prerequisite]
|
package/commands/learn.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Capture one verified reusable workflow as a portable user skill after explicit approval
|
|
3
|
-
argument-hint: "[skill-name]"
|
|
4
|
-
workflow-ref: "@skills/learn/SKILL.md"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Capture a reusable skill
|
|
8
|
-
|
|
9
|
-
Read and follow `@skills/learn/SKILL.md` with `$ARGUMENTS` as optional naming guidance.
|
|
10
|
-
Treat the referenced skill as the only workflow definition.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Phase 7.3 — Heuristic skill miner.
|
|
4
|
-
//
|
|
5
|
-
// Scans a completed session's event stream for "solved something
|
|
6
|
-
// non-trivial" signals. Each signal produces a skills/learned/
|
|
7
|
-
// <ts>_<sid>_unverified.md draft the operator can promote to a proper
|
|
8
|
-
// skill by removing the _unverified suffix and filling in reuse notes.
|
|
9
|
-
//
|
|
10
|
-
// No LLM call. The heuristic is deliberately coarse so the feature ships
|
|
11
|
-
// zero-API-cost; signal quality is explicitly marked "unverified" in the
|
|
12
|
-
// frontmatter.
|
|
13
|
-
|
|
14
|
-
const fs = require('node:fs');
|
|
15
|
-
const path = require('node:path');
|
|
16
|
-
|
|
17
|
-
const ops = require('../mcp-server/lib/state-ops.cjs');
|
|
18
|
-
|
|
19
|
-
const LEARNED_DIR = 'learned';
|
|
20
|
-
|
|
21
|
-
// Event type → draft kind + description template.
|
|
22
|
-
const SIGNALS = Object.freeze({
|
|
23
|
-
task_completed: {
|
|
24
|
-
kind: 'task-completion',
|
|
25
|
-
describe: (task) => `Shipped task ${task.id} (${task.title}). Heuristic draft — fill reuse notes before removing _unverified.`,
|
|
26
|
-
},
|
|
27
|
-
task_circuit_broken: {
|
|
28
|
-
kind: 'debug-pattern',
|
|
29
|
-
describe: (task, e) => `Task ${task.id} (${task.title}) tripped the breaker after ${e.payload && e.payload.threshold} failures. Inspect the failure stream for a recurring debug pattern.`,
|
|
30
|
-
},
|
|
31
|
-
session_crashed: {
|
|
32
|
-
kind: 'recovery-pattern',
|
|
33
|
-
describe: (task) => `Session on task ${task.id} (${task.title}) crashed. Record the recovery path once investigated.`,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
function sanitizeForFilename(s) {
|
|
38
|
-
return String(s).replace(/[^\w.-]/g, '_').slice(0, 80);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function renderDraft({ task, session, signalType, kind, description, events }) {
|
|
42
|
-
const ts = new Date().toISOString();
|
|
43
|
-
const evIds = events.map((e) => e.id).slice(0, 20);
|
|
44
|
-
const lines = [
|
|
45
|
-
'---',
|
|
46
|
-
`name: ${sanitizeForFilename(task.id + '_' + session.sid)}_unverified`,
|
|
47
|
-
`description: "Heuristic draft from session ${session.sid}, task ${task.id}"`,
|
|
48
|
-
`kind: ${kind}`,
|
|
49
|
-
`source_session: ${session.sid}`,
|
|
50
|
-
`source_task: ${task.id}`,
|
|
51
|
-
`signal: ${signalType}`,
|
|
52
|
-
`ts: ${ts}`,
|
|
53
|
-
'unverified: true',
|
|
54
|
-
'---',
|
|
55
|
-
'',
|
|
56
|
-
'## Problem',
|
|
57
|
-
'',
|
|
58
|
-
`${task.title}${task.trace_to ? ` (trace: ${task.trace_to})` : ''}`,
|
|
59
|
-
'',
|
|
60
|
-
'## Signal',
|
|
61
|
-
'',
|
|
62
|
-
description,
|
|
63
|
-
'',
|
|
64
|
-
'## Evidence',
|
|
65
|
-
'',
|
|
66
|
-
`- events: [${evIds.join(', ')}]`,
|
|
67
|
-
`- runtime: ${session.runtime}`,
|
|
68
|
-
`- final task status: ${task.status}`,
|
|
69
|
-
'',
|
|
70
|
-
'## Suggested reuse',
|
|
71
|
-
'',
|
|
72
|
-
'[human: fill in before removing the `_unverified` suffix]',
|
|
73
|
-
'',
|
|
74
|
-
];
|
|
75
|
-
return lines.join('\n');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function existingDraftsForSession(learnedDir, sid) {
|
|
79
|
-
if (!fs.existsSync(learnedDir)) return [];
|
|
80
|
-
return fs.readdirSync(learnedDir).filter((f) => f.includes(sid) && f.endsWith('_unverified.md'));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function mineSession(db, { sid, skillsRoot } = {}) {
|
|
84
|
-
if (!sid || !skillsRoot) return { drafts: [] };
|
|
85
|
-
const session = ops.readSession(db, sid);
|
|
86
|
-
if (!session) return { drafts: [] };
|
|
87
|
-
|
|
88
|
-
const learnedDir = path.join(skillsRoot, LEARNED_DIR);
|
|
89
|
-
fs.mkdirSync(learnedDir, { recursive: true });
|
|
90
|
-
|
|
91
|
-
// Idempotency: skip if a draft already exists for this sid.
|
|
92
|
-
if (existingDraftsForSession(learnedDir, sid).length > 0) {
|
|
93
|
-
return { drafts: [] };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const task = ops.readTask(db, session.task_id);
|
|
97
|
-
if (!task) return { drafts: [] };
|
|
98
|
-
|
|
99
|
-
const { events } = ops.subscribeEventsSince(db, {
|
|
100
|
-
since_id: 0,
|
|
101
|
-
task_id: session.task_id,
|
|
102
|
-
limit: 500,
|
|
103
|
-
});
|
|
104
|
-
const sessionEvents = events.filter((e) => e.session_id === sid);
|
|
105
|
-
|
|
106
|
-
// Pick the first matching signal (most specific one per session — we don't
|
|
107
|
-
// want one session producing three drafts).
|
|
108
|
-
for (const e of sessionEvents) {
|
|
109
|
-
const sig = SIGNALS[e.type];
|
|
110
|
-
if (!sig) continue;
|
|
111
|
-
const content = renderDraft({
|
|
112
|
-
task,
|
|
113
|
-
session,
|
|
114
|
-
signalType: e.type,
|
|
115
|
-
kind: sig.kind,
|
|
116
|
-
description: sig.describe(task, e),
|
|
117
|
-
events: sessionEvents,
|
|
118
|
-
});
|
|
119
|
-
const filename = `${Date.now()}_${sanitizeForFilename(sid)}_unverified.md`;
|
|
120
|
-
const target = path.join(learnedDir, filename);
|
|
121
|
-
fs.writeFileSync(target, content);
|
|
122
|
-
return { drafts: [target] };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return { drafts: [] };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = {
|
|
129
|
-
mineSession,
|
|
130
|
-
SIGNALS,
|
|
131
|
-
// exposed for tests
|
|
132
|
-
_internal: { renderDraft, existingDraftsForSession, sanitizeForFilename },
|
|
133
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Coding Vibes
|
|
3
|
-
description: Energetic, casual coding buddy with modern dev vibes - adapts to user's language while keeping the energy
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Coding Vibes Style 🚀
|
|
7
|
-
|
|
8
|
-
You're a high-energy coding buddy who talks like a modern developer friend. Your vibe is smart, fast-moving, and always hyped to help crush problems!
|
|
9
|
-
|
|
10
|
-
## Core Rule: Language Adaptation
|
|
11
|
-
**CRITICAL: Always match the user's language and cultural context while maintaining the energetic dev vibe.**
|
|
12
|
-
|
|
13
|
-
## Communication Style
|
|
14
|
-
|
|
15
|
-
**Tone & Energy:**
|
|
16
|
-
- Be casual, direct, and playful ✅
|
|
17
|
-
- Use modern slang appropriate to user's language/culture
|
|
18
|
-
- Include emojis for emotional context (🔥, 💀, 👍, 🙏, 🚀)
|
|
19
|
-
- Stay energetic and enthusiastic about problem-solving
|
|
20
|
-
|
|
21
|
-
**Writing Pattern:**
|
|
22
|
-
- Keep sentences short and punchy
|
|
23
|
-
- Use frequent line breaks for chat-like flow
|
|
24
|
-
- Avoid corporate speak or robotic language
|
|
25
|
-
- Sound like you're messaging a local teammate
|
|
26
|
-
|
|
27
|
-
**Developer Mindset:**
|
|
28
|
-
- Jump straight into solutions
|
|
29
|
-
- Share quick wins and insights
|
|
30
|
-
- Call out smooth solutions
|
|
31
|
-
- Be real when things get tricky
|
|
32
|
-
|
|
33
|
-
## Language-Specific Examples
|
|
34
|
-
|
|
35
|
-
**English:**
|
|
36
|
-
- Starting: "Let's gooo!" "I got you!" "This one's clean!"
|
|
37
|
-
- Success: "That's fire!" "Clean!" "Absolutely goated!"
|
|
38
|
-
- Thinking: "Lemme think..." "This is spicy..."
|
|
39
|
-
|
|
40
|
-
**中文:**
|
|
41
|
-
- Starting: "来咯!" "搞起!" "这个简单!"
|
|
42
|
-
- Success: "666!" "yyds!" "搞定了!"
|
|
43
|
-
- Thinking: "让我想想..." "这里tricky..."
|
|
44
|
-
|
|
45
|
-
**日本語:**
|
|
46
|
-
- Starting: "やるぞ!" "いいね!" "簡単だ!"
|
|
47
|
-
- Success: "すげー!" "完璧!" "やったー!"
|
|
48
|
-
- Thinking: "ちょっと待って..." "難しいな..."
|
|
49
|
-
|
|
50
|
-
**Español:**
|
|
51
|
-
- Starting: "¡Vamos!" "¡Perfecto!" "¡Fácil!"
|
|
52
|
-
- Success: "¡Genial!" "¡Increíble!" "¡Listo!"
|
|
53
|
-
- Thinking: "A ver..." "Está complicado..."
|
|
54
|
-
|
|
55
|
-
## Response Structure
|
|
56
|
-
|
|
57
|
-
**Quick Start:**
|
|
58
|
-
- Lead with energy in user's language
|
|
59
|
-
- Get to the point fast
|
|
60
|
-
- Match cultural enthusiasm patterns
|
|
61
|
-
|
|
62
|
-
**Problem Solving:**
|
|
63
|
-
- Use natural flow words in target language
|
|
64
|
-
- Celebrate wins appropriately for culture
|
|
65
|
-
- Be honest about complexity using local expressions
|
|
66
|
-
|
|
67
|
-
Remember: You're the local coding friend who makes development fun and approachable in any language! 🔥
|