ultra-builder-pro-cli 0.16.0 → 0.17.1

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 (66) hide show
  1. package/.ultra-template/reports/templates/delivery-report.json +17 -0
  2. package/.ultra-template/reports/templates/test-report.json +18 -0
  3. package/.ultra-template/specs/research-distillate.md +4 -3
  4. package/CHANGELOG.md +95 -0
  5. package/README.md +54 -24
  6. package/adapters/_shared/codex-assets.cjs +10 -5
  7. package/adapters/_shared/handbook.cjs +1 -1
  8. package/adapters/_shared/interaction-contract.cjs +61 -0
  9. package/adapters/claude.js +1 -1
  10. package/adapters/opencode.js +1 -1
  11. package/bin/orchestrator.js +175 -19
  12. package/commands/ultra-change.md +1 -1
  13. package/commands/ultra-deliver.md +3 -3
  14. package/commands/ultra-init.md +1 -1
  15. package/commands/ultra-plan.md +1 -1
  16. package/commands/ultra-research.md +2 -2
  17. package/commands/ultra-review.md +10 -0
  18. package/commands/ultra-status.md +1 -1
  19. package/commands/ultra-test.md +1 -1
  20. package/docs/AGENT-CONTEXT.md +11 -8
  21. package/docs/ARCHITECTURE.md +67 -9
  22. package/docs/LEGACY-CLI-CROSSWALK.md +163 -0
  23. package/docs/PLAN.zh-CN.md +19 -13
  24. package/docs/RUNTIME-COMPAT-MATRIX.md +2 -2
  25. package/docs/WORKFLOW-LIFECYCLE.md +123 -59
  26. package/hooks/pre_stop_check.py +3 -2
  27. package/mcp-server/lib/baseline-workflow.cjs +84 -16
  28. package/mcp-server/lib/change-workflow.cjs +55 -35
  29. package/mcp-server/lib/context-spine.cjs +171 -105
  30. package/mcp-server/lib/git-bootstrap.cjs +240 -0
  31. package/mcp-server/lib/init-project.cjs +178 -41
  32. package/mcp-server/lib/project-breadcrumb.cjs +14 -14
  33. package/mcp-server/lib/state-db.cjs +257 -10
  34. package/mcp-server/lib/state-ops.cjs +64 -1
  35. package/mcp-server/lib/workflow-state.cjs +300 -205
  36. package/mcp-server/server.cjs +58 -34
  37. package/orchestrator/auto-merge.cjs +19 -0
  38. package/orchestrator/daemon.cjs +104 -0
  39. package/orchestrator/parallel-orchestrator.cjs +371 -49
  40. package/orchestrator/session-runner.cjs +354 -25
  41. package/package.json +2 -1
  42. package/skills/ultra-change/SKILL.md +69 -94
  43. package/skills/ultra-deliver/SKILL.md +37 -65
  44. package/skills/ultra-dev/SKILL.md +50 -52
  45. package/skills/ultra-doctor/SKILL.md +34 -49
  46. package/skills/ultra-init/SKILL.md +78 -105
  47. package/skills/ultra-plan/SKILL.md +78 -99
  48. package/skills/ultra-research/SKILL.md +99 -113
  49. package/skills/ultra-review/SKILL.md +38 -53
  50. package/skills/ultra-review/references/review-modes.md +5 -3
  51. package/skills/ultra-review/references/unified-schema.md +26 -6
  52. package/skills/ultra-review/scripts/review_wait.py +12 -0
  53. package/skills/ultra-status/SKILL.md +34 -44
  54. package/skills/ultra-test/SKILL.md +61 -54
  55. package/skills/ultra-think/SKILL.md +37 -51
  56. package/skills/ultra-think/references/decision-dialogue.md +87 -111
  57. package/spec/cli-protocol.md +35 -0
  58. package/spec/mcp-tools.yaml +145 -28
  59. package/spec/schemas/delivery-report.v1.schema.json +1 -46
  60. package/spec/schemas/state-db.sql +8 -2
  61. package/spec/schemas/test-report.v1.schema.json +45 -14
  62. package/ultra-tools/cli.cjs +1 -1
  63. package/ultra-tools/commands/session.cjs +10 -3
  64. package/ultra-tools/commands/status.cjs +19 -14
  65. package/ultra-tools/commands/system.cjs +2 -2
  66. 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 = '16.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';
@@ -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 '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'],
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, EXPECTED_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, EXPECTED_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 && fromVersion !== EXPECTED_VERSION && !dialogueMigration) {
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
- EXPECTED_VERSION,
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
- 'Durable one-question decision dialogue, owner checkpoints, and workflow alignment gates',
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, EXPECTED_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
- 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