ultra-builder-pro-cli 0.15.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.
Files changed (71) hide show
  1. package/.ultra-template/specs/research-distillate.md +4 -3
  2. package/CHANGELOG.md +120 -1
  3. package/README.md +67 -30
  4. package/adapters/_shared/codex-assets.cjs +10 -5
  5. package/adapters/_shared/handbook.cjs +3 -2
  6. package/adapters/_shared/interaction-contract.cjs +61 -0
  7. package/adapters/claude.js +1 -1
  8. package/adapters/opencode.js +1 -1
  9. package/bin/orchestrator.js +175 -19
  10. package/commands/ultra-change.md +1 -1
  11. package/commands/ultra-deliver.md +3 -3
  12. package/commands/ultra-init.md +1 -1
  13. package/commands/ultra-plan.md +1 -1
  14. package/commands/ultra-research.md +2 -2
  15. package/commands/ultra-review.md +10 -0
  16. package/commands/ultra-status.md +1 -1
  17. package/commands/ultra-test.md +1 -1
  18. package/commands/ultra-think.md +2 -2
  19. package/docs/AGENT-CONTEXT.md +38 -18
  20. package/docs/ARCHITECTURE.md +91 -13
  21. package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
  22. package/docs/PLAN.zh-CN.md +19 -12
  23. package/docs/ROADMAP.md +5 -3
  24. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  25. package/docs/STATE-DB-ACCESS-POLICY.md +8 -6
  26. package/docs/USER-HANDBOOK-CONTRACT.md +8 -2
  27. package/docs/WORKFLOW-LIFECYCLE.md +179 -39
  28. package/hooks/pre_stop_check.py +3 -2
  29. package/mcp-server/lib/baseline-workflow.cjs +84 -16
  30. package/mcp-server/lib/change-workflow.cjs +107 -39
  31. package/mcp-server/lib/context-spine.cjs +215 -87
  32. package/mcp-server/lib/decision-dialogue.cjs +713 -0
  33. package/mcp-server/lib/doctor.cjs +9 -0
  34. package/mcp-server/lib/git-bootstrap.cjs +240 -0
  35. package/mcp-server/lib/init-project.cjs +178 -41
  36. package/mcp-server/lib/project-breadcrumb.cjs +23 -14
  37. package/mcp-server/lib/state-db.cjs +279 -9
  38. package/mcp-server/lib/state-ops.cjs +64 -1
  39. package/mcp-server/lib/workflow-state.cjs +313 -205
  40. package/mcp-server/server.cjs +112 -34
  41. package/orchestrator/auto-merge.cjs +19 -0
  42. package/orchestrator/daemon.cjs +104 -0
  43. package/orchestrator/parallel-orchestrator.cjs +371 -49
  44. package/orchestrator/session-runner.cjs +354 -25
  45. package/package.json +2 -1
  46. package/skills/ultra-change/SKILL.md +69 -65
  47. package/skills/ultra-deliver/SKILL.md +37 -58
  48. package/skills/ultra-dev/SKILL.md +55 -49
  49. package/skills/ultra-doctor/SKILL.md +44 -49
  50. package/skills/ultra-init/SKILL.md +78 -100
  51. package/skills/ultra-plan/SKILL.md +78 -79
  52. package/skills/ultra-research/SKILL.md +99 -98
  53. package/skills/ultra-review/SKILL.md +38 -47
  54. package/skills/ultra-review/references/review-modes.md +5 -3
  55. package/skills/ultra-review/references/unified-schema.md +26 -6
  56. package/skills/ultra-review/scripts/review_wait.py +12 -0
  57. package/skills/ultra-status/SKILL.md +34 -40
  58. package/skills/ultra-test/SKILL.md +61 -48
  59. package/skills/ultra-think/SKILL.md +47 -47
  60. package/skills/ultra-think/references/decision-dialogue.md +106 -0
  61. package/spec/cli-protocol.md +46 -2
  62. package/spec/mcp-tools.yaml +444 -29
  63. package/spec/schemas/delivery-report.v1.schema.json +1 -46
  64. package/spec/schemas/mcp-tools.schema.json +1 -1
  65. package/spec/schemas/state-db.sql +66 -2
  66. package/spec/schemas/test-report.v1.schema.json +45 -14
  67. package/ultra-tools/cli.cjs +1 -1
  68. package/ultra-tools/commands/session.cjs +10 -3
  69. package/ultra-tools/commands/status.cjs +34 -14
  70. package/ultra-tools/commands/system.cjs +2 -2
  71. package/ultra-tools/commands/task.cjs +3 -0
@@ -9,8 +9,8 @@ const { EXPECTED_VERSION, REQUIRED_TABLES } = require('./state-db.cjs');
9
9
  const { readBreadcrumb } = require('./context-spine.cjs');
10
10
 
11
11
  function blockedBreadcrumb(code, {
12
- nextAction,
13
- workflow,
12
+ allowedTransitions = [],
13
+ requiredTransition = null,
14
14
  } = {}) {
15
15
  return {
16
16
  change_id: null,
@@ -20,8 +20,8 @@ function blockedBreadcrumb(code, {
20
20
  readiness: 'blocked',
21
21
  blockers: [code],
22
22
  warnings: [],
23
- next_action: nextAction,
24
- recommended_workflow: workflow,
23
+ allowed_transitions: allowedTransitions,
24
+ required_transition: requiredTransition,
25
25
  context_manifest_path: null,
26
26
  context_manifest_hash: null,
27
27
  git_head: null,
@@ -31,15 +31,15 @@ function blockedBreadcrumb(code, {
31
31
 
32
32
  function migrationBreadcrumb(code) {
33
33
  return blockedBreadcrumb(code, {
34
- nextAction: 'Resume project initialization to migrate and re-establish the Ultra baseline.',
35
- workflow: 'ultra-init',
34
+ allowedTransitions: ['ultra-init', 'ultra-status'],
35
+ requiredTransition: 'ultra-init',
36
36
  });
37
37
  }
38
38
 
39
39
  function doctorBreadcrumb(code) {
40
40
  return blockedBreadcrumb(code, {
41
- nextAction: 'Run ultra-doctor to inspect and recover the authoritative Ultra state database.',
42
- workflow: 'ultra-doctor',
41
+ allowedTransitions: ['ultra-doctor', 'ultra-status'],
42
+ requiredTransition: 'ultra-doctor',
43
43
  });
44
44
  }
45
45
 
@@ -62,8 +62,8 @@ function readProjectBreadcrumb(rootDir) {
62
62
  const dbPath = path.join(ultraDir, 'state.db');
63
63
  if (!fs.existsSync(dbPath)) {
64
64
  return blockedBreadcrumb('STATE_DB_MISSING', {
65
- nextAction: 'Initialize or resume this Ultra project through ultra-init.',
66
- workflow: 'ultra-init',
65
+ allowedTransitions: ['ultra-init', 'ultra-status'],
66
+ requiredTransition: 'ultra-init',
67
67
  });
68
68
  }
69
69
 
@@ -126,10 +126,19 @@ function renderProjectBreadcrumb(rootDir, breadcrumb) {
126
126
  `Step: ${breadcrumb.workflow.current_step || 'finalize'}`,
127
127
  );
128
128
  }
129
- lines.push(
130
- `Next: ${breadcrumb.next_action || 'Inspect Ultra status.'}`,
131
- `Route: ${breadcrumb.recommended_workflow || 'ultra-doctor'}`,
132
- );
129
+ if (breadcrumb.decision) {
130
+ const current = breadcrumb.decision.current;
131
+ lines.push(
132
+ `Decision thread: ${breadcrumb.decision.thread_id} (${breadcrumb.decision.status}/${breadcrumb.decision.mode})`,
133
+ current
134
+ ? `Decision: ${current.id} — ${current.question}`
135
+ : 'Decision: checkpoint confirmation required',
136
+ );
137
+ }
138
+ lines.push(`Allowed transitions: ${(breadcrumb.allowed_transitions || []).join(', ') || 'none'}`);
139
+ if (breadcrumb.required_transition) {
140
+ lines.push(`Required transition: ${breadcrumb.required_transition}`);
141
+ }
133
142
  if (breadcrumb.context_manifest_path) {
134
143
  lines.push(
135
144
  `Context: ${breadcrumb.context_manifest_path} sha256=${breadcrumb.context_manifest_hash || 'unknown'}`,
@@ -9,13 +9,16 @@ 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 = '15.0';
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';
16
16
  const ADOPTION_SCHEMA_VERSION = '12.0';
17
17
  const WORKFLOW_SCHEMA_VERSION = '13.0';
18
18
  const AUTHORITY_SCHEMA_VERSION = '14.0';
19
+ const SEMANTIC_SCHEMA_VERSION = '15.0';
20
+ const DIALOGUE_SCHEMA_VERSION = '16.0';
21
+ const GIT_AUTHORITY_SCHEMA_VERSION = '17.0';
19
22
 
20
23
  const MIGRATED_GAPS = Object.freeze([{
21
24
  id: 'legacy-rebaseline-required',
@@ -47,6 +50,8 @@ const REQUIRED_TABLES = Object.freeze([
47
50
  'event_consumers',
48
51
  'workflow_runs',
49
52
  'workflow_steps',
53
+ 'decision_threads',
54
+ 'decision_items',
50
55
  ]);
51
56
 
52
57
  function applyBaselineUpgrade(db, { legacyState = false } = {}) {
@@ -64,7 +69,7 @@ function applyBaselineUpgrade(db, { legacyState = false } = {}) {
64
69
  repository_revision TEXT,
65
70
  repository_branch TEXT,
66
71
  worktree_state TEXT NOT NULL DEFAULT 'unavailable'
67
- CHECK (worktree_state IN ('clean', 'dirty', 'unavailable')),
72
+ CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
68
73
  worktree_digest TEXT,
69
74
  worktree_files_json TEXT NOT NULL DEFAULT '[]',
70
75
  worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
@@ -111,7 +116,7 @@ function applyBaselineColumns(db) {
111
116
  changed = addColumnIfMissing(db, 'baselines', columns, 'repository_branch', 'TEXT') || changed;
112
117
  changed = addColumnIfMissing(
113
118
  db, 'baselines', columns, 'worktree_state',
114
- "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'))",
115
120
  ) || changed;
116
121
  changed = addColumnIfMissing(db, 'baselines', columns, 'worktree_digest', 'TEXT') || changed;
117
122
  changed = addColumnIfMissing(
@@ -143,6 +148,10 @@ function applyChangeColumns(db) {
143
148
  changed = addColumnIfMissing(
144
149
  db, 'changes', columns, 'research_disposition_json', "TEXT NOT NULL DEFAULT '{}'",
145
150
  ) || changed;
151
+ changed = addColumnIfMissing(
152
+ db, 'changes', columns, 'alignment_thread_id',
153
+ 'TEXT REFERENCES decision_threads(id) ON DELETE SET NULL',
154
+ ) || changed;
146
155
  return changed;
147
156
  }
148
157
 
@@ -277,7 +286,9 @@ function applyContextSpineUpgrade(db) {
277
286
  const additions = [
278
287
  ['role', "TEXT NOT NULL DEFAULT 'plan' CHECK (role IN ('plan', 'implement', 'check', 'review'))"],
279
288
  ['gate', "TEXT NOT NULL DEFAULT 'alignment' CHECK (gate IN ('alignment', 'planning', 'implementation', 'verification', 'review', 'convergence', 'recovery'))"],
280
- ['next_action', "TEXT NOT NULL DEFAULT 'Resolve the next Ultra workflow action.'"],
289
+ ['next_action', "TEXT NOT NULL DEFAULT ''"],
290
+ ['allowed_transitions_json', "TEXT NOT NULL DEFAULT '[]'"],
291
+ ['required_transition', 'TEXT'],
281
292
  ['readiness', "TEXT NOT NULL DEFAULT 'ready' CHECK (readiness IN ('ready', 'blocked'))"],
282
293
  ['blockers_json', "TEXT NOT NULL DEFAULT '[]'"],
283
294
  ['context_json', "TEXT NOT NULL DEFAULT '{}'"],
@@ -311,6 +322,125 @@ function applyContextSpineUpgrade(db) {
311
322
  return changed;
312
323
  }
313
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
+
314
444
  function tableSupportsKimi(db, table) {
315
445
  const row = db.prepare(
316
446
  "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
@@ -318,6 +448,87 @@ function tableSupportsKimi(db, table) {
318
448
  return typeof row?.sql === 'string' && row.sql.includes("'kimi'");
319
449
  }
320
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
+
321
532
  function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
322
533
  if (tableSupportsKimi(db, 'events') && tableSupportsKimi(db, 'sessions')) return false;
323
534
  const previousVersion = fromVersion || 'unknown';
@@ -393,6 +604,13 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
393
604
 
394
605
  function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { legacyState = false } = {}) {
395
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);
396
614
  db.transaction(() => {
397
615
  applyCompatibleColumns(db);
398
616
  const contextChanged = applyContextSpineUpgrade(db);
@@ -402,7 +620,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
402
620
  const needsContextMigration = Boolean(
403
621
  fromVersion && ![
404
622
  CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
405
- WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
623
+ WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
624
+ DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
406
625
  ].includes(fromVersion)
407
626
  && !contextMigration,
408
627
  );
@@ -426,7 +645,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
426
645
  ).get();
427
646
  if (fromVersion && ![
428
647
  BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
429
- AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
648
+ AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
649
+ GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
430
650
  ].includes(fromVersion)
431
651
  && !baselineMigration) {
432
652
  db.prepare(
@@ -530,17 +750,64 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
530
750
  VALUES (?, ?, 'forward', 'success', ?)`,
531
751
  ).run(
532
752
  fromVersion === AUTHORITY_SCHEMA_VERSION ? fromVersion : AUTHORITY_SCHEMA_VERSION,
533
- EXPECTED_VERSION,
753
+ SEMANTIC_SCHEMA_VERSION,
534
754
  'Add typed research semantics, complete change contracts, verified learning application, and reconciliation provenance',
535
755
  );
536
756
  }
757
+ const dialogueMigration = db.prepare(
758
+ "SELECT 1 FROM migration_history WHERE to_version = '16.0' AND status = 'success' LIMIT 1",
759
+ ).get();
760
+ if (fromVersion && ![DIALOGUE_SCHEMA_VERSION, EXPECTED_VERSION].includes(fromVersion)
761
+ && !dialogueMigration) {
762
+ db.prepare(
763
+ `INSERT INTO migration_history
764
+ (from_version, to_version, direction, status, notes)
765
+ VALUES (?, ?, 'forward', 'success', ?)`,
766
+ ).run(
767
+ fromVersion === SEMANTIC_SCHEMA_VERSION ? fromVersion : SEMANTIC_SCHEMA_VERSION,
768
+ DIALOGUE_SCHEMA_VERSION,
769
+ 'Add durable one-question decision dialogue, owner checkpoints, and workflow alignment gates',
770
+ );
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
+ }
537
804
  db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
538
805
  db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
539
806
  db.prepare(
540
807
  'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
541
808
  ).run(
542
809
  EXPECTED_VERSION,
543
- 'Typed research semantics, complete change contracts, verified learning application, and reconciliation provenance',
810
+ 'Adaptive capability transitions, independent initialization, and recoverable workflow migration',
544
811
  );
545
812
  })();
546
813
  }
@@ -568,7 +835,10 @@ function initStateDb(dbPath) {
568
835
  const existing = new Set(tableNames(db));
569
836
  const fromVersion = latestSchemaVersion(db);
570
837
  const legacyState = existing.size > 0
571
- && ![WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION].includes(fromVersion);
838
+ && ![
839
+ WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
840
+ DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, EXPECTED_VERSION,
841
+ ].includes(fromVersion);
572
842
  backupPath = migrationBackup(db, dbPath, fromVersion, existing);
573
843
  applyCompatibleColumns(db);
574
844
  const missing = REQUIRED_TABLES.filter((t) => !existing.has(t));
@@ -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
- return readTask(db, id);
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