ultra-builder-pro-cli 0.16.0 → 0.17.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/specs/research-distillate.md +4 -3
- package/CHANGELOG.md +86 -0
- package/README.md +54 -24
- package/adapters/_shared/codex-assets.cjs +10 -5
- package/adapters/_shared/handbook.cjs +1 -1
- package/adapters/_shared/interaction-contract.cjs +61 -0
- package/adapters/claude.js +1 -1
- package/adapters/opencode.js +1 -1
- package/bin/orchestrator.js +175 -19
- package/commands/ultra-change.md +1 -1
- package/commands/ultra-deliver.md +3 -3
- package/commands/ultra-init.md +1 -1
- package/commands/ultra-plan.md +1 -1
- package/commands/ultra-research.md +2 -2
- package/commands/ultra-review.md +10 -0
- package/commands/ultra-status.md +1 -1
- package/commands/ultra-test.md +1 -1
- package/docs/AGENT-CONTEXT.md +11 -8
- package/docs/ARCHITECTURE.md +67 -9
- package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
- package/docs/PLAN.zh-CN.md +19 -13
- package/docs/RUNTIME-COMPAT-MATRIX.md +2 -2
- package/docs/WORKFLOW-LIFECYCLE.md +123 -59
- package/hooks/pre_stop_check.py +3 -2
- package/mcp-server/lib/baseline-workflow.cjs +84 -16
- package/mcp-server/lib/change-workflow.cjs +55 -35
- package/mcp-server/lib/context-spine.cjs +171 -105
- package/mcp-server/lib/git-bootstrap.cjs +240 -0
- package/mcp-server/lib/init-project.cjs +178 -41
- package/mcp-server/lib/project-breadcrumb.cjs +14 -14
- package/mcp-server/lib/state-db.cjs +257 -10
- package/mcp-server/lib/state-ops.cjs +64 -1
- package/mcp-server/lib/workflow-state.cjs +300 -205
- package/mcp-server/server.cjs +58 -34
- package/orchestrator/auto-merge.cjs +19 -0
- package/orchestrator/daemon.cjs +104 -0
- package/orchestrator/parallel-orchestrator.cjs +371 -49
- package/orchestrator/session-runner.cjs +354 -25
- package/package.json +2 -1
- package/skills/ultra-change/SKILL.md +69 -94
- package/skills/ultra-deliver/SKILL.md +37 -65
- package/skills/ultra-dev/SKILL.md +50 -52
- package/skills/ultra-doctor/SKILL.md +34 -49
- package/skills/ultra-init/SKILL.md +78 -105
- package/skills/ultra-plan/SKILL.md +78 -99
- package/skills/ultra-research/SKILL.md +99 -113
- package/skills/ultra-review/SKILL.md +38 -53
- package/skills/ultra-review/references/review-modes.md +5 -3
- package/skills/ultra-review/references/unified-schema.md +26 -6
- package/skills/ultra-review/scripts/review_wait.py +12 -0
- package/skills/ultra-status/SKILL.md +34 -44
- package/skills/ultra-test/SKILL.md +61 -54
- package/skills/ultra-think/SKILL.md +37 -51
- package/skills/ultra-think/references/decision-dialogue.md +87 -111
- package/spec/cli-protocol.md +35 -0
- package/spec/mcp-tools.yaml +145 -28
- package/spec/schemas/delivery-report.v1.schema.json +1 -46
- package/spec/schemas/state-db.sql +8 -2
- package/spec/schemas/test-report.v1.schema.json +45 -14
- package/ultra-tools/cli.cjs +1 -1
- package/ultra-tools/commands/session.cjs +10 -3
- package/ultra-tools/commands/status.cjs +19 -14
- package/ultra-tools/commands/system.cjs +2 -2
- package/ultra-tools/commands/task.cjs +3 -0
|
@@ -9,7 +9,7 @@ const REPO_ROOT = process.env.UBP_RUNTIME_ROOT
|
|
|
9
9
|
? path.resolve(process.env.UBP_RUNTIME_ROOT)
|
|
10
10
|
: path.resolve(__dirname, '..', '..');
|
|
11
11
|
const SCHEMA_FILE = path.join(REPO_ROOT, 'spec', 'schemas', 'state-db.sql');
|
|
12
|
-
const EXPECTED_VERSION = '
|
|
12
|
+
const EXPECTED_VERSION = '18.0';
|
|
13
13
|
const KIMI_SCHEMA_VERSION = '9.1';
|
|
14
14
|
const CONTEXT_SCHEMA_VERSION = '10.0';
|
|
15
15
|
const BASELINE_SCHEMA_VERSION = '11.0';
|
|
@@ -17,6 +17,8 @@ const ADOPTION_SCHEMA_VERSION = '12.0';
|
|
|
17
17
|
const WORKFLOW_SCHEMA_VERSION = '13.0';
|
|
18
18
|
const AUTHORITY_SCHEMA_VERSION = '14.0';
|
|
19
19
|
const SEMANTIC_SCHEMA_VERSION = '15.0';
|
|
20
|
+
const DIALOGUE_SCHEMA_VERSION = '16.0';
|
|
21
|
+
const GIT_AUTHORITY_SCHEMA_VERSION = '17.0';
|
|
20
22
|
|
|
21
23
|
const MIGRATED_GAPS = Object.freeze([{
|
|
22
24
|
id: 'legacy-rebaseline-required',
|
|
@@ -67,7 +69,7 @@ function applyBaselineUpgrade(db, { legacyState = false } = {}) {
|
|
|
67
69
|
repository_revision TEXT,
|
|
68
70
|
repository_branch TEXT,
|
|
69
71
|
worktree_state TEXT NOT NULL DEFAULT 'unavailable'
|
|
70
|
-
CHECK (worktree_state IN ('clean', 'dirty', 'unavailable')),
|
|
72
|
+
CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
|
|
71
73
|
worktree_digest TEXT,
|
|
72
74
|
worktree_files_json TEXT NOT NULL DEFAULT '[]',
|
|
73
75
|
worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
|
|
@@ -114,7 +116,7 @@ function applyBaselineColumns(db) {
|
|
|
114
116
|
changed = addColumnIfMissing(db, 'baselines', columns, 'repository_branch', 'TEXT') || changed;
|
|
115
117
|
changed = addColumnIfMissing(
|
|
116
118
|
db, 'baselines', columns, 'worktree_state',
|
|
117
|
-
"TEXT NOT NULL DEFAULT 'unavailable' CHECK (worktree_state IN ('clean', 'dirty', 'unavailable'))",
|
|
119
|
+
"TEXT NOT NULL DEFAULT 'unavailable' CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable'))",
|
|
118
120
|
) || changed;
|
|
119
121
|
changed = addColumnIfMissing(db, 'baselines', columns, 'worktree_digest', 'TEXT') || changed;
|
|
120
122
|
changed = addColumnIfMissing(
|
|
@@ -284,7 +286,9 @@ function applyContextSpineUpgrade(db) {
|
|
|
284
286
|
const additions = [
|
|
285
287
|
['role', "TEXT NOT NULL DEFAULT 'plan' CHECK (role IN ('plan', 'implement', 'check', 'review'))"],
|
|
286
288
|
['gate', "TEXT NOT NULL DEFAULT 'alignment' CHECK (gate IN ('alignment', 'planning', 'implementation', 'verification', 'review', 'convergence', 'recovery'))"],
|
|
287
|
-
['next_action', "TEXT NOT NULL DEFAULT '
|
|
289
|
+
['next_action', "TEXT NOT NULL DEFAULT ''"],
|
|
290
|
+
['allowed_transitions_json', "TEXT NOT NULL DEFAULT '[]'"],
|
|
291
|
+
['required_transition', 'TEXT'],
|
|
288
292
|
['readiness', "TEXT NOT NULL DEFAULT 'ready' CHECK (readiness IN ('ready', 'blocked'))"],
|
|
289
293
|
['blockers_json', "TEXT NOT NULL DEFAULT '[]'"],
|
|
290
294
|
['context_json', "TEXT NOT NULL DEFAULT '{}'"],
|
|
@@ -318,6 +322,125 @@ function applyContextSpineUpgrade(db) {
|
|
|
318
322
|
return changed;
|
|
319
323
|
}
|
|
320
324
|
|
|
325
|
+
function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db)) {
|
|
326
|
+
const tables = new Set(tableNames(db));
|
|
327
|
+
if (!tables.has('workflow_runs') || !tables.has('workflow_steps')) return false;
|
|
328
|
+
const existing = db.prepare(
|
|
329
|
+
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
330
|
+
).get();
|
|
331
|
+
if (fromVersion === EXPECTED_VERSION || existing) return false;
|
|
332
|
+
|
|
333
|
+
const obsoleteSteps = Object.freeze({
|
|
334
|
+
init: ['establish-baseline'],
|
|
335
|
+
change: ['plan-change', 'compile-context', 'verify-readiness'],
|
|
336
|
+
plan: ['select-posture', 'approve-plan'],
|
|
337
|
+
deliver: ['release-if-authorized'],
|
|
338
|
+
});
|
|
339
|
+
const activeRuns = db.prepare(
|
|
340
|
+
`SELECT id, kind, status FROM workflow_runs
|
|
341
|
+
WHERE status IN ('active', 'blocked', 'ready')`,
|
|
342
|
+
).all();
|
|
343
|
+
const isCompleted = db.prepare(
|
|
344
|
+
"SELECT status FROM workflow_steps WHERE run_id = ? AND step_id = ?",
|
|
345
|
+
);
|
|
346
|
+
const currentRequired = db.prepare(
|
|
347
|
+
`SELECT step_id FROM workflow_steps
|
|
348
|
+
WHERE run_id = ? AND required = 1 AND status NOT IN ('completed', 'skipped')
|
|
349
|
+
ORDER BY position ASC LIMIT 1`,
|
|
350
|
+
);
|
|
351
|
+
const skipObsolete = db.prepare(
|
|
352
|
+
`UPDATE workflow_steps
|
|
353
|
+
SET required = 0,
|
|
354
|
+
status = CASE WHEN status IN ('pending', 'in_progress', 'blocked') THEN 'skipped' ELSE status END,
|
|
355
|
+
blockers_json = CASE WHEN status IN ('pending', 'in_progress', 'blocked') THEN '[]' ELSE blockers_json END,
|
|
356
|
+
skip_reason = CASE
|
|
357
|
+
WHEN status IN ('pending', 'in_progress', 'blocked')
|
|
358
|
+
THEN 'Removed by schema 18 adaptive workflow migration; durable evidence is preserved.'
|
|
359
|
+
ELSE skip_reason
|
|
360
|
+
END,
|
|
361
|
+
completed_at = CASE
|
|
362
|
+
WHEN status IN ('pending', 'in_progress', 'blocked')
|
|
363
|
+
THEN strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
364
|
+
ELSE completed_at
|
|
365
|
+
END,
|
|
366
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
367
|
+
WHERE run_id = ? AND step_id = ?`,
|
|
368
|
+
);
|
|
369
|
+
const completeRun = db.prepare(
|
|
370
|
+
`UPDATE workflow_runs
|
|
371
|
+
SET status = 'completed', current_step = NULL, blockers_json = '[]',
|
|
372
|
+
definition_version = '2.0', summary_json = ?,
|
|
373
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
374
|
+
completed_at = COALESCE(completed_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
375
|
+
WHERE id = ?`,
|
|
376
|
+
);
|
|
377
|
+
for (const run of activeRuns) {
|
|
378
|
+
for (const stepId of obsoleteSteps[run.kind] || []) skipObsolete.run(run.id, stepId);
|
|
379
|
+
|
|
380
|
+
if (run.kind === 'init') {
|
|
381
|
+
const scaffoldReady = ['inspect-authority', 'classify-repository', 'scaffold-authority']
|
|
382
|
+
.every((stepId) => isCompleted.get(run.id, stepId)?.status === 'completed');
|
|
383
|
+
if (scaffoldReady) {
|
|
384
|
+
db.prepare(
|
|
385
|
+
`UPDATE workflow_steps
|
|
386
|
+
SET status = 'completed',
|
|
387
|
+
evidence_json = CASE WHEN evidence_json = '[]' THEN ? ELSE evidence_json END,
|
|
388
|
+
blockers_json = '[]',
|
|
389
|
+
started_at = COALESCE(started_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
390
|
+
completed_at = COALESCE(completed_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
391
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
392
|
+
WHERE run_id = ? AND step_id = 'verify-initialization'
|
|
393
|
+
AND status IN ('pending', 'in_progress', 'blocked')`,
|
|
394
|
+
).run(JSON.stringify([{
|
|
395
|
+
kind: 'migration',
|
|
396
|
+
ref: 'schema:18.0',
|
|
397
|
+
summary: 'Existing scaffold and durable authority were preserved during adaptive migration.',
|
|
398
|
+
}]), run.id);
|
|
399
|
+
if (!currentRequired.get(run.id)) {
|
|
400
|
+
completeRun.run(JSON.stringify({
|
|
401
|
+
migrated: true,
|
|
402
|
+
authority_basis: 'initialized_scaffold',
|
|
403
|
+
research_started: false,
|
|
404
|
+
}), run.id);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (run.kind === 'change') {
|
|
411
|
+
const contractCaptured = ['bind-baseline', 'classify-change', 'record-intent']
|
|
412
|
+
.every((stepId) => isCompleted.get(run.id, stepId)?.status === 'completed');
|
|
413
|
+
if (contractCaptured && !currentRequired.get(run.id)) {
|
|
414
|
+
completeRun.run(JSON.stringify({
|
|
415
|
+
migrated: true,
|
|
416
|
+
authority_basis: 'accepted_change_contract',
|
|
417
|
+
}), run.id);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const next = currentRequired.get(run.id)?.step_id || null;
|
|
423
|
+
db.prepare(
|
|
424
|
+
`UPDATE workflow_runs
|
|
425
|
+
SET definition_version = '2.0', current_step = ?, blockers_json = '[]',
|
|
426
|
+
status = CASE
|
|
427
|
+
WHEN ? IS NULL AND status IN ('active', 'blocked') THEN 'ready'
|
|
428
|
+
WHEN ? IS NOT NULL AND status = 'blocked' THEN 'active'
|
|
429
|
+
ELSE status
|
|
430
|
+
END,
|
|
431
|
+
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
432
|
+
WHERE id = ?`,
|
|
433
|
+
).run(next, next, next, run.id);
|
|
434
|
+
}
|
|
435
|
+
db.prepare(
|
|
436
|
+
`UPDATE context_snapshots
|
|
437
|
+
SET allowed_transitions_json = COALESCE(allowed_transitions_json, '[]'),
|
|
438
|
+
required_transition = NULL
|
|
439
|
+
WHERE allowed_transitions_json IS NULL OR required_transition IS NOT NULL`,
|
|
440
|
+
).run();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
321
444
|
function tableSupportsKimi(db, table) {
|
|
322
445
|
const row = db.prepare(
|
|
323
446
|
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
@@ -325,6 +448,87 @@ function tableSupportsKimi(db, table) {
|
|
|
325
448
|
return typeof row?.sql === 'string' && row.sql.includes("'kimi'");
|
|
326
449
|
}
|
|
327
450
|
|
|
451
|
+
function baselineSupportsUnbornGit(db) {
|
|
452
|
+
const row = db.prepare(
|
|
453
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'baselines'",
|
|
454
|
+
).get();
|
|
455
|
+
return typeof row?.sql === 'string' && row.sql.includes("'unborn'");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion(db)) {
|
|
459
|
+
if (!tableNames(db).includes('baselines') || baselineSupportsUnbornGit(db)) return false;
|
|
460
|
+
const previousVersion = fromVersion || 'unknown';
|
|
461
|
+
const foreignKeys = db.pragma('foreign_keys', { simple: true });
|
|
462
|
+
db.pragma('foreign_keys = OFF');
|
|
463
|
+
try {
|
|
464
|
+
db.transaction(() => {
|
|
465
|
+
db.exec(`
|
|
466
|
+
DROP TABLE IF EXISTS baselines_git_upgrade;
|
|
467
|
+
CREATE TABLE baselines_git_upgrade (
|
|
468
|
+
id TEXT PRIMARY KEY,
|
|
469
|
+
project_name TEXT NOT NULL,
|
|
470
|
+
project_type TEXT,
|
|
471
|
+
stack TEXT,
|
|
472
|
+
mode TEXT NOT NULL CHECK (mode IN ('greenfield', 'brownfield', 'migrated')),
|
|
473
|
+
status TEXT NOT NULL DEFAULT 'draft'
|
|
474
|
+
CHECK (status IN ('draft', 'adopting', 'blocked', 'ready', 'superseded')),
|
|
475
|
+
repository_root TEXT NOT NULL DEFAULT '.',
|
|
476
|
+
scope_json TEXT NOT NULL DEFAULT '["."]',
|
|
477
|
+
repository_revision TEXT,
|
|
478
|
+
repository_branch TEXT,
|
|
479
|
+
worktree_state TEXT NOT NULL DEFAULT 'unavailable'
|
|
480
|
+
CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
|
|
481
|
+
worktree_digest TEXT,
|
|
482
|
+
worktree_files_json TEXT NOT NULL DEFAULT '[]',
|
|
483
|
+
worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
|
|
484
|
+
known_red_accepted INTEGER NOT NULL DEFAULT 0 CHECK (known_red_accepted IN (0, 1)),
|
|
485
|
+
spec_refs_json TEXT NOT NULL DEFAULT '[]',
|
|
486
|
+
evidence_json TEXT NOT NULL DEFAULT '[]',
|
|
487
|
+
verification_json TEXT NOT NULL DEFAULT '[]',
|
|
488
|
+
unknowns_json TEXT NOT NULL DEFAULT '[]',
|
|
489
|
+
gaps_json TEXT NOT NULL DEFAULT '[]',
|
|
490
|
+
classification_json TEXT NOT NULL DEFAULT '{}',
|
|
491
|
+
provider_refs_json TEXT NOT NULL DEFAULT '{}',
|
|
492
|
+
research_run_id TEXT,
|
|
493
|
+
approved_by TEXT,
|
|
494
|
+
approval_note TEXT,
|
|
495
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
496
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
497
|
+
converged_at TEXT
|
|
498
|
+
);
|
|
499
|
+
INSERT INTO baselines_git_upgrade (
|
|
500
|
+
id, project_name, project_type, stack, mode, status, repository_root, scope_json,
|
|
501
|
+
repository_revision, repository_branch, worktree_state, worktree_digest,
|
|
502
|
+
worktree_files_json, worktree_accepted, known_red_accepted, spec_refs_json,
|
|
503
|
+
evidence_json, verification_json, unknowns_json, gaps_json, classification_json,
|
|
504
|
+
provider_refs_json, research_run_id, approved_by, approval_note, started_at,
|
|
505
|
+
updated_at, converged_at
|
|
506
|
+
)
|
|
507
|
+
SELECT
|
|
508
|
+
id, project_name, project_type, stack, mode, status, repository_root, scope_json,
|
|
509
|
+
repository_revision, repository_branch, worktree_state, worktree_digest,
|
|
510
|
+
worktree_files_json, worktree_accepted, known_red_accepted, spec_refs_json,
|
|
511
|
+
evidence_json, verification_json, unknowns_json, gaps_json, classification_json,
|
|
512
|
+
provider_refs_json, research_run_id, approved_by, approval_note, started_at,
|
|
513
|
+
updated_at, converged_at
|
|
514
|
+
FROM baselines;
|
|
515
|
+
DROP TABLE baselines;
|
|
516
|
+
ALTER TABLE baselines_git_upgrade RENAME TO baselines;
|
|
517
|
+
CREATE INDEX baselines_status ON baselines(status, updated_at);
|
|
518
|
+
`);
|
|
519
|
+
const violations = db.pragma('foreign_key_check');
|
|
520
|
+
if (violations.length > 0) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`baseline Git-state migration produced ${violations.length} foreign key violation(s)`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
})();
|
|
526
|
+
} finally {
|
|
527
|
+
db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
|
|
528
|
+
}
|
|
529
|
+
return previousVersion;
|
|
530
|
+
}
|
|
531
|
+
|
|
328
532
|
function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
329
533
|
if (tableSupportsKimi(db, 'events') && tableSupportsKimi(db, 'sessions')) return false;
|
|
330
534
|
const previousVersion = fromVersion || 'unknown';
|
|
@@ -400,6 +604,13 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
|
400
604
|
|
|
401
605
|
function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { legacyState = false } = {}) {
|
|
402
606
|
const runtimeChanged = upgradeRuntimeConstraints(db, fromVersion);
|
|
607
|
+
if (tableNames(db).includes('baselines') && !baselineSupportsUnbornGit(db)) {
|
|
608
|
+
db.transaction(() => {
|
|
609
|
+
applyBaselineUpgrade(db, { legacyState });
|
|
610
|
+
applyBaselineColumns(db);
|
|
611
|
+
})();
|
|
612
|
+
}
|
|
613
|
+
const gitStateChangedFrom = upgradeBaselineWorktreeConstraint(db, fromVersion);
|
|
403
614
|
db.transaction(() => {
|
|
404
615
|
applyCompatibleColumns(db);
|
|
405
616
|
const contextChanged = applyContextSpineUpgrade(db);
|
|
@@ -409,7 +620,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
409
620
|
const needsContextMigration = Boolean(
|
|
410
621
|
fromVersion && ![
|
|
411
622
|
CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
|
|
412
|
-
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
623
|
+
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
624
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
|
|
413
625
|
].includes(fromVersion)
|
|
414
626
|
&& !contextMigration,
|
|
415
627
|
);
|
|
@@ -433,7 +645,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
433
645
|
).get();
|
|
434
646
|
if (fromVersion && ![
|
|
435
647
|
BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
|
|
436
|
-
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
648
|
+
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
649
|
+
GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
|
|
437
650
|
].includes(fromVersion)
|
|
438
651
|
&& !baselineMigration) {
|
|
439
652
|
db.prepare(
|
|
@@ -544,24 +757,57 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
544
757
|
const dialogueMigration = db.prepare(
|
|
545
758
|
"SELECT 1 FROM migration_history WHERE to_version = '16.0' AND status = 'success' LIMIT 1",
|
|
546
759
|
).get();
|
|
547
|
-
if (fromVersion &&
|
|
760
|
+
if (fromVersion && ![DIALOGUE_SCHEMA_VERSION, EXPECTED_VERSION].includes(fromVersion)
|
|
761
|
+
&& !dialogueMigration) {
|
|
548
762
|
db.prepare(
|
|
549
763
|
`INSERT INTO migration_history
|
|
550
764
|
(from_version, to_version, direction, status, notes)
|
|
551
765
|
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
552
766
|
).run(
|
|
553
767
|
fromVersion === SEMANTIC_SCHEMA_VERSION ? fromVersion : SEMANTIC_SCHEMA_VERSION,
|
|
554
|
-
|
|
768
|
+
DIALOGUE_SCHEMA_VERSION,
|
|
555
769
|
'Add durable one-question decision dialogue, owner checkpoints, and workflow alignment gates',
|
|
556
770
|
);
|
|
557
771
|
}
|
|
772
|
+
const gitStateMigration = db.prepare(
|
|
773
|
+
"SELECT 1 FROM migration_history WHERE to_version = '17.0' AND status = 'success' LIMIT 1",
|
|
774
|
+
).get();
|
|
775
|
+
if (fromVersion && ![GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION].includes(fromVersion)
|
|
776
|
+
&& !gitStateMigration) {
|
|
777
|
+
db.prepare(
|
|
778
|
+
`INSERT INTO migration_history
|
|
779
|
+
(from_version, to_version, direction, status, notes)
|
|
780
|
+
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
781
|
+
).run(
|
|
782
|
+
gitStateChangedFrom || DIALOGUE_SCHEMA_VERSION,
|
|
783
|
+
GIT_AUTHORITY_SCHEMA_VERSION,
|
|
784
|
+
'Add explicit unborn Git authority and require an owner-authorized checkpoint before baseline recording',
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
const adaptiveChanged = migrateAdaptiveWorkflowRuns(db, fromVersion);
|
|
788
|
+
const adaptiveMigration = db.prepare(
|
|
789
|
+
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
790
|
+
).get();
|
|
791
|
+
if (fromVersion && fromVersion !== EXPECTED_VERSION && !adaptiveMigration) {
|
|
792
|
+
db.prepare(
|
|
793
|
+
`INSERT INTO migration_history
|
|
794
|
+
(from_version, to_version, direction, status, notes)
|
|
795
|
+
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
796
|
+
).run(
|
|
797
|
+
fromVersion === GIT_AUTHORITY_SCHEMA_VERSION ? fromVersion : GIT_AUTHORITY_SCHEMA_VERSION,
|
|
798
|
+
EXPECTED_VERSION,
|
|
799
|
+
adaptiveChanged
|
|
800
|
+
? 'Migrate active rigid workflows to adaptive capability transitions and independent initialization'
|
|
801
|
+
: 'Add adaptive capability transitions and independent initialization authority',
|
|
802
|
+
);
|
|
803
|
+
}
|
|
558
804
|
db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
|
|
559
805
|
db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
|
|
560
806
|
db.prepare(
|
|
561
807
|
'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
|
|
562
808
|
).run(
|
|
563
809
|
EXPECTED_VERSION,
|
|
564
|
-
'
|
|
810
|
+
'Adaptive capability transitions, independent initialization, and recoverable workflow migration',
|
|
565
811
|
);
|
|
566
812
|
})();
|
|
567
813
|
}
|
|
@@ -590,7 +836,8 @@ function initStateDb(dbPath) {
|
|
|
590
836
|
const fromVersion = latestSchemaVersion(db);
|
|
591
837
|
const legacyState = existing.size > 0
|
|
592
838
|
&& ![
|
|
593
|
-
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
839
|
+
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
840
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
|
|
594
841
|
].includes(fromVersion);
|
|
595
842
|
backupPath = migrationBackup(db, dbPath, fromVersion, existing);
|
|
596
843
|
applyCompatibleColumns(db);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// here apply the BEGIN IMMEDIATE / retry / status-machine guards specified
|
|
9
9
|
// in PLAN §6 Phase 2.3 + docs/STATE-DB-ACCESS-POLICY.md.
|
|
10
10
|
|
|
11
|
+
const crypto = require('node:crypto');
|
|
11
12
|
const fs = require('node:fs');
|
|
12
13
|
const path = require('node:path');
|
|
13
14
|
const { spawnSync } = require('node:child_process');
|
|
@@ -42,6 +43,17 @@ const PATCHABLE_FIELDS = Object.freeze([
|
|
|
42
43
|
'change_id',
|
|
43
44
|
]);
|
|
44
45
|
|
|
46
|
+
const STALE_RECONCILIATION_FIELDS = Object.freeze([
|
|
47
|
+
'deps', 'files_modified', 'trace_to', 'outcome', 'slice_kind', 'public_seam',
|
|
48
|
+
'verification_command', 'acceptance', 'context_refs', 'docs_impact', 'ownership',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const TASK_CONTRACT_PATCH_FIELDS = new Set([
|
|
52
|
+
'priority', 'complexity', 'estimated_days', 'deps', 'files_modified', 'tag',
|
|
53
|
+
'trace_to', 'outcome', 'slice_kind', 'public_seam', 'verification_command',
|
|
54
|
+
'acceptance', 'context_refs', 'docs_impact', 'ownership',
|
|
55
|
+
]);
|
|
56
|
+
|
|
45
57
|
const SESSION_PATCHABLE = Object.freeze([
|
|
46
58
|
'pid', 'status', 'lease_expires_at', 'heartbeat_at', 'worktree_path', 'artifact_dir',
|
|
47
59
|
]);
|
|
@@ -227,6 +239,30 @@ function assertTaskExecutionContract(task) {
|
|
|
227
239
|
return task;
|
|
228
240
|
}
|
|
229
241
|
|
|
242
|
+
function currentChangeAuthorityDigest(db, changeId) {
|
|
243
|
+
if (!changeId) return null;
|
|
244
|
+
const row = db.prepare(
|
|
245
|
+
`SELECT id, kind, intent, docs_impact_json, provider_refs_json, contract_json,
|
|
246
|
+
classification_json, research_disposition_json
|
|
247
|
+
FROM changes WHERE id = ?`,
|
|
248
|
+
).get(changeId);
|
|
249
|
+
if (!row) return null;
|
|
250
|
+
const parse = (value) => {
|
|
251
|
+
try { return JSON.parse(value || '{}'); } catch { return {}; }
|
|
252
|
+
};
|
|
253
|
+
const payload = {
|
|
254
|
+
id: row.id,
|
|
255
|
+
kind: row.kind,
|
|
256
|
+
intent: row.intent,
|
|
257
|
+
docs_impact: parse(row.docs_impact_json),
|
|
258
|
+
provider_refs: parse(row.provider_refs_json),
|
|
259
|
+
contract: parse(row.contract_json),
|
|
260
|
+
classification: parse(row.classification_json),
|
|
261
|
+
research_disposition: parse(row.research_disposition_json),
|
|
262
|
+
};
|
|
263
|
+
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
264
|
+
}
|
|
265
|
+
|
|
230
266
|
function readTask(db, id) {
|
|
231
267
|
const row = db.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
|
|
232
268
|
return rowToTask(row);
|
|
@@ -373,6 +409,19 @@ function patchTask(db, id, patch = {}) {
|
|
|
373
409
|
// active execution authority. Ownership is durable once assigned: it may
|
|
374
410
|
// not be detached or moved to another change by a generic task patch.
|
|
375
411
|
assertChangeAcceptsTasks(db, current.change_id);
|
|
412
|
+
const clearingStale = current.stale === true && patch.stale === false;
|
|
413
|
+
if (clearingStale) {
|
|
414
|
+
const missing = STALE_RECONCILIATION_FIELDS.filter(
|
|
415
|
+
(field) => !Object.prototype.hasOwnProperty.call(patch, field),
|
|
416
|
+
);
|
|
417
|
+
if (missing.length > 0) {
|
|
418
|
+
throw new StateOpsError(
|
|
419
|
+
'TASK_STALE_RECONCILIATION_REQUIRED',
|
|
420
|
+
`task ${id} can clear stale only while rebinding its complete execution contract`,
|
|
421
|
+
{ details: { missing_fields: missing } },
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
376
425
|
const sets = [];
|
|
377
426
|
const params = [];
|
|
378
427
|
let nextStatus = null;
|
|
@@ -440,6 +489,8 @@ function patchTask(db, id, patch = {}) {
|
|
|
440
489
|
params.push(nowIso());
|
|
441
490
|
params.push(id);
|
|
442
491
|
db.prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
492
|
+
const updated = readTask(db, id);
|
|
493
|
+
if (clearingStale) assertTaskExecutionContract(updated);
|
|
443
494
|
|
|
444
495
|
if (nextStatus && nextStatus !== current.status) {
|
|
445
496
|
appendEventInTx(db, {
|
|
@@ -449,7 +500,19 @@ function patchTask(db, id, patch = {}) {
|
|
|
449
500
|
payload: { from: current.status, to: nextStatus },
|
|
450
501
|
});
|
|
451
502
|
}
|
|
452
|
-
|
|
503
|
+
const contractFields = Object.keys(patch).filter((field) => TASK_CONTRACT_PATCH_FIELDS.has(field));
|
|
504
|
+
if (contractFields.length > 0) {
|
|
505
|
+
appendEventInTx(db, {
|
|
506
|
+
type: clearingStale ? 'task_contract_reconciled' : 'task_contract_updated',
|
|
507
|
+
task_id: id,
|
|
508
|
+
change_id: updated.change_id,
|
|
509
|
+
payload: {
|
|
510
|
+
fields: contractFields.sort(),
|
|
511
|
+
change_authority_digest: currentChangeAuthorityDigest(db, updated.change_id),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return updated;
|
|
453
516
|
});
|
|
454
517
|
}
|
|
455
518
|
|