chati-dev 4.5.15 → 4.5.27

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 (124) hide show
  1. package/README.md +8 -3
  2. package/bin/chati.js +124 -66
  3. package/framework/agents/build/dev.md +5 -5
  4. package/framework/agents/deploy/devops.md +7 -7
  5. package/framework/agents/discover/brief.md +4 -4
  6. package/framework/agents/discover/brownfield-wu.md +3 -3
  7. package/framework/agents/discover/greenfield-wu.md +3 -3
  8. package/framework/agents/plan/architect.md +2 -2
  9. package/framework/agents/plan/detail.md +4 -4
  10. package/framework/agents/plan/phases.md +2 -2
  11. package/framework/agents/plan/tasks.md +2 -2
  12. package/framework/agents/plan/ux.md +2 -2
  13. package/framework/agents/quality/qa-implementation.md +11 -10
  14. package/framework/agents/quality/qa-planning.md +3 -3
  15. package/framework/agents/quality/qa-visual.md +1 -1
  16. package/framework/config.yaml +3 -3
  17. package/framework/constitution.md +18 -18
  18. package/framework/context/protocols.md +2 -2
  19. package/framework/context/quality.md +1 -1
  20. package/framework/context/root.md +4 -4
  21. package/framework/data/entity-registry.yaml +1 -1
  22. package/framework/domains/agents/orchestrator.yaml +2 -2
  23. package/framework/domains/constitution.yaml +1 -1
  24. package/framework/hooks/advance-trigger.js +4 -6
  25. package/framework/hooks/git-push-authority.js +45 -37
  26. package/framework/hooks/mode-governance.js +149 -40
  27. package/framework/hooks/model-governance.js +13 -20
  28. package/framework/hooks/prism-engine.js +74 -92
  29. package/framework/hooks/reasoning-escalator.js +23 -40
  30. package/framework/hooks/session-digest.js +12 -13
  31. package/framework/hooks/session-reader.js +224 -0
  32. package/framework/hooks/session-writer.js +195 -0
  33. package/framework/hooks/team-quality-gate.js +34 -24
  34. package/framework/i18n/en.yaml +2 -2
  35. package/framework/i18n/es.yaml +2 -2
  36. package/framework/i18n/fr.yaml +2 -2
  37. package/framework/i18n/pt.yaml +2 -2
  38. package/framework/intelligence/context-engine.md +4 -4
  39. package/framework/intelligence/memory-layer.md +1 -1
  40. package/framework/manifest.json +129 -119
  41. package/framework/manifest.sig +1 -1
  42. package/framework/orchestrator/chati-router.js +278 -24
  43. package/framework/orchestrator/chati.md +250 -68
  44. package/framework/schemas/session.schema.json +21 -1
  45. package/framework/tasks/orchestrator-deviation.md +1 -1
  46. package/framework/tasks/orchestrator-escalate.md +1 -1
  47. package/framework/tasks/orchestrator-handoff.md +6 -6
  48. package/framework/tasks/orchestrator-health.md +5 -9
  49. package/framework/tasks/orchestrator-mode-switch.md +3 -7
  50. package/framework/tasks/orchestrator-resume.md +10 -14
  51. package/framework/tasks/orchestrator-route.md +3 -3
  52. package/framework/tasks/orchestrator-spawn-terminal.md +1 -1
  53. package/framework/tasks/orchestrator-status.md +9 -9
  54. package/framework/tasks/orchestrator-suggest-mode.md +1 -1
  55. package/framework/tasks/qa-impl-consolidate.md +2 -2
  56. package/framework/tasks/qa-impl-performance-test.md +4 -4
  57. package/framework/tasks/qa-impl-regression-check.md +4 -4
  58. package/framework/tasks/qa-impl-sast-scan.md +1 -1
  59. package/framework/tasks/qa-impl-test-execute.md +1 -1
  60. package/framework/tasks/qa-impl-verdict.md +2 -2
  61. package/framework/tasks/qa-planning-consolidate.md +3 -3
  62. package/framework/tasks/qa-planning-coverage-plan.md +2 -2
  63. package/framework/tasks/qa-planning-gate-define.md +4 -4
  64. package/framework/tasks/qa-planning-risk-matrix.md +4 -4
  65. package/framework/tasks/qa-planning-test-strategy.md +2 -2
  66. package/node_modules/@chati/browser-capability/src/index.js +12 -2
  67. package/node_modules/@chati/planning/src/index.js +24 -4
  68. package/node_modules/@chati/provider-registry/src/index.js +11 -0
  69. package/node_modules/@chati/rail/src/index.js +1967 -83
  70. package/node_modules/@chati/release-lane/README.md +12 -8
  71. package/node_modules/@chati/release-lane/package.json +1 -1
  72. package/node_modules/@chati/release-lane/src/index.js +1426 -58
  73. package/node_modules/@chati/review-council/src/index.js +63 -0
  74. package/node_modules/@chati/tracking-clickup/README.md +13 -0
  75. package/node_modules/@chati/tracking-clickup/src/index.js +690 -30
  76. package/package-artifact-manifest.json +1 -0
  77. package/package-artifact-manifest.sig +1 -0
  78. package/package.json +16 -7
  79. package/scripts/verify-real-harness-e2e.js +1581 -0
  80. package/src/config/framework-adapter.js +4 -4
  81. package/src/installer/core.js +157 -56
  82. package/src/installer/manifest.js +140 -9
  83. package/src/installer/package-artifact.js +249 -0
  84. package/src/installer/templates.js +91 -23
  85. package/src/installer-v2/catalog-client.js +531 -23
  86. package/src/installer-v2/index.js +91 -34
  87. package/src/installer-v2/installation-authority.js +327 -0
  88. package/src/installer-v2/provider-executable.js +235 -0
  89. package/src/installer-v2/wizard-installation.js +1 -1
  90. package/src/intelligence/timeline.js +3 -1
  91. package/src/orchestrator/browser-runtime.js +44 -13
  92. package/src/orchestrator/cli.js +1616 -151
  93. package/src/orchestrator/clickup-projection.js +43 -1
  94. package/src/orchestrator/clickup-runtime.js +355 -39
  95. package/src/orchestrator/doctor.js +87 -4
  96. package/src/orchestrator/index.js +16 -0
  97. package/src/orchestrator/planning-runtime.js +20 -2
  98. package/src/orchestrator/rail-adjudication-evidence.js +234 -0
  99. package/src/orchestrator/rail-evidence-authority.js +147 -0
  100. package/src/orchestrator/rail-execution-evidence.js +45 -0
  101. package/src/orchestrator/rail-runtime.js +836 -56
  102. package/src/orchestrator/release-runtime.js +65 -6
  103. package/src/orchestrator/review-runtime.js +186 -41
  104. package/src/orchestrator/runtime-installation-v2.js +236 -20
  105. package/src/orchestrator/session-manager.js +1249 -52
  106. package/src/terminal/adapters/claude-adapter.js +3 -1
  107. package/src/terminal/adapters/codex-adapter.js +2 -0
  108. package/src/terminal/adapters/grok-adapter.js +7 -4
  109. package/src/terminal/handoff-parser.js +19 -1
  110. package/src/terminal/prompt-builder.js +10 -1
  111. package/src/terminal/provider-preflight.js +36 -3
  112. package/src/terminal/rail-execution-worktree.js +213 -0
  113. package/src/terminal/rail-prompts.js +169 -0
  114. package/src/terminal/rail-readonly-workspace.js +324 -0
  115. package/src/terminal/run-agent.js +434 -16
  116. package/src/terminal/run-parallel.js +5 -0
  117. package/src/terminal/run-rail-adjudication.js +323 -0
  118. package/src/terminal/run-rail-review.js +291 -0
  119. package/src/terminal/run-rail-rework.js +325 -0
  120. package/src/terminal/run-rail-task.js +380 -0
  121. package/src/terminal/run-team.js +5 -0
  122. package/src/terminal/spawner.js +1225 -77
  123. package/src/utils/schema-validator.js +5 -0
  124. package/src/wizard/index.js +3 -2
@@ -16,10 +16,17 @@
16
16
  */
17
17
 
18
18
  import { existsSync, readFileSync } from 'fs';
19
- import { join } from 'path';
19
+ import { randomUUID } from 'crypto';
20
+ import { join, resolve } from 'path';
21
+ import { readHookSession } from '../hooks/session-reader.js';
20
22
 
21
23
  const PROJECT_DIR = process.cwd();
22
- const ROUTER_ARGS = process.argv.slice(2);
24
+ const RAW_ROUTER_ARGS = process.argv.slice(2);
25
+ const harnessFlagIndex = RAW_ROUTER_ARGS.indexOf('--harness');
26
+ const ACTIVE_HARNESS = harnessFlagIndex >= 0 ? RAW_ROUTER_ARGS[harnessFlagIndex + 1] : null;
27
+ const ROUTER_ARGS = harnessFlagIndex >= 0
28
+ ? RAW_ROUTER_ARGS.filter((_, index) => index !== harnessFlagIndex && index !== harnessFlagIndex + 1)
29
+ : RAW_ROUTER_ARGS;
23
30
 
24
31
  async function readEnabledHarnesses(projectDir) {
25
32
  const installationPath = join(projectDir, '.chati', 'v2', 'installation.json');
@@ -40,6 +47,7 @@ async function readEnabledHarnesses(projectDir) {
40
47
 
41
48
  const detected = [];
42
49
  if (existsSync(join(projectDir, '.claude', 'commands', 'chati.md'))) detected.push('claude');
50
+ if (existsSync(join(projectDir, '.gemini', 'commands', 'chati.toml'))) detected.push('gemini');
43
51
  if (existsSync(join(projectDir, '.agents', 'skills', 'chati', 'SKILL.md'))) detected.push('codex');
44
52
  if (existsSync(join(projectDir, '.grok', 'commands', 'chati.md'))) detected.push('grok');
45
53
  return { harnesses: detected, error: null };
@@ -64,7 +72,7 @@ export async function validateProviderIntegrity(projectDir) {
64
72
  const { harnesses, error } = await readEnabledHarnesses(projectDir);
65
73
 
66
74
  if (error) missing.push(error);
67
- const supportedHarnesses = new Set(['claude', 'codex', 'grok']);
75
+ const supportedHarnesses = new Set(['claude', 'gemini', 'codex', 'grok']);
68
76
  for (const harness of harnesses) {
69
77
  if (!supportedHarnesses.has(harness)) missing.push(`unsupported harness ${harness}`);
70
78
  }
@@ -132,9 +140,40 @@ export async function validateProviderIntegrity(projectDir) {
132
140
  }
133
141
  }
134
142
 
143
+ if (harnesses.includes('gemini')) {
144
+ for (const [label, relativePath] of [
145
+ ['gemini command', '.gemini/commands/chati.toml'],
146
+ ['gemini session lock', '.gemini/session-lock.md'],
147
+ ['gemini governance context', '.gemini/context/governance.md'],
148
+ ['gemini license hook file', '.gemini/hooks/license-guard.js'],
149
+ ]) {
150
+ if (!hasContent(join(projectDir, relativePath))) missing.push(label);
151
+ }
152
+ const settingsPath = join(projectDir, '.gemini', 'settings.json');
153
+ if (!hasContent(settingsPath)) {
154
+ missing.push('gemini settings');
155
+ } else {
156
+ try {
157
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
158
+ const firstHook = settings.hooks?.BeforeModel?.[0]?.hooks?.[0];
159
+ if (firstHook?.name !== 'license-guard'
160
+ || firstHook?.command !== 'node .gemini/hooks/license-guard.js') {
161
+ missing.push('gemini license hook');
162
+ }
163
+ } catch {
164
+ missing.push('gemini settings invalid');
165
+ }
166
+ }
167
+ }
168
+
135
169
  if (harnesses.includes('grok')) {
136
170
  if (!hasContent(join(projectDir, '.grok', 'commands', 'chati.md'))) missing.push('grok command');
137
171
  if (!hasContent(join(projectDir, '.grok', 'session-lock.md'))) missing.push('grok session lock');
172
+ const governancePath = join(projectDir, '.grok', 'rules', 'chati-governance.md');
173
+ if (!hasContent(governancePath)
174
+ || !readFileSync(governancePath, 'utf-8').includes('chati-router.js --harness grok')) {
175
+ missing.push('grok governance rule');
176
+ }
138
177
  }
139
178
 
140
179
  return { valid: missing.length === 0, missing };
@@ -312,9 +351,23 @@ const VALID_ORCHESTRATE_SUBS = new Set([
312
351
  'spawn-team', 'team-status', 'team-dissolve',
313
352
  'wait-for-capture', 'wave-status', 'assess-coverage',
314
353
  'wait-for-license', 'doctor', 'run-script-task',
354
+ 'activate-harness',
315
355
  ]);
316
356
 
317
- const VALID_RAIL_SUBS = new Set(['rail-next']);
357
+ const VALID_RAIL_SUBS = new Set([
358
+ 'rail-next',
359
+ 'rail-dispatch',
360
+ 'rail-review-run',
361
+ 'rail-rework',
362
+ 'rail-adjudicate',
363
+ 'rail-recover',
364
+ 'rail-complete',
365
+ 'rail-release-ready',
366
+ 'rail-tracking-reconcile',
367
+ 'rail-tracking-pending',
368
+ 'rail-tracking-ack',
369
+ 'rail-tracking-lookup',
370
+ ]);
318
371
 
319
372
  /**
320
373
  * Parse --flag value pairs and standalone --flag bools from argv.
@@ -380,8 +433,17 @@ async function dispatchRail(subCommand, argv) {
380
433
  }
381
434
 
382
435
  const flags = parseFlagArgs(argv);
436
+ const parseJsonFlag = (name, { required = false } = {}) => {
437
+ const raw = flags[name];
438
+ if (raw === undefined) {
439
+ if (required) throw Object.assign(new Error(`Missing --${name}`), { code: 'RAIL_INPUT_REQUIRED' });
440
+ return undefined;
441
+ }
442
+ try { return JSON.parse(raw); }
443
+ catch (error) { throw Object.assign(new Error(`Invalid JSON in --${name}: ${error.message}`), { code: 'RAIL_INPUT_INVALID' }); }
444
+ };
383
445
  if (subCommand === 'rail-next') {
384
- if (!flags['handoff-id']) return { ok: false, error: 'Missing --handoff-id' };
446
+ if (!flags['handoff-id']) return { ok: false, action: 'error', error: 'RAIL_INPUT_REQUIRED', detail: 'Missing --handoff-id' };
385
447
  try {
386
448
  const { selectReadyRailTasks } = await import(runtimePath);
387
449
  return selectReadyRailTasks({ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'] });
@@ -389,6 +451,173 @@ async function dispatchRail(subCommand, argv) {
389
451
  return { ok: false, action: 'error', error: error.code || error.message };
390
452
  }
391
453
  }
454
+ if (subCommand === 'rail-dispatch') {
455
+ if (!flags['handoff-id'] || !flags['task-id']) return { ok: false, action: 'error', error: 'RAIL_INPUT_REQUIRED', detail: 'Missing --handoff-id or --task-id' };
456
+ const attemptId = flags['attempt-id'] || `attempt-${randomUUID()}`;
457
+ try {
458
+ const runnerPath = join(PROJECT_DIR, fwDir, '_cli', 'terminal', 'run-rail-task.js');
459
+ if (!existsSync(runnerPath)) return { ok: false, action: 'error', error: 'rail_dispatch_runtime_not_found' };
460
+ const { executeReadyRailTask } = await import(runnerPath);
461
+ const browserRequest = flags['browser-request-json']
462
+ ? JSON.parse(flags['browser-request-json'])
463
+ : undefined;
464
+ return await executeReadyRailTask({
465
+ projectDir: PROJECT_DIR,
466
+ handoff_id: flags['handoff-id'],
467
+ task_id: flags['task-id'],
468
+ attempt_id: attemptId,
469
+ operator_id: flags['operator-id'] || `operator://chati/${ACTIVE_HARNESS || 'runtime'}`,
470
+ ...(flags.timeout ? { timeout: Number(flags.timeout) } : {}),
471
+ ...(browserRequest === undefined ? {} : { browser_request: browserRequest }),
472
+ });
473
+ } catch (error) {
474
+ let nextRequiredAction = 'rail_retry_same_attempt';
475
+ try {
476
+ const runtime = await import(runtimePath);
477
+ runtime.resolveRailAttemptContext({ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'], attempt_id: attemptId });
478
+ nextRequiredAction = 'rail_recover_same_attempt_after_lease_expiry';
479
+ } catch {
480
+ // The dispatch never created a canonical claim.
481
+ }
482
+ return {
483
+ ok: false, action: 'error', error: error.code || 'RAIL_DISPATCH_FAILED',
484
+ detail: error.message, attempt_id: error.details?.attempt_id || attemptId,
485
+ next_required_action: nextRequiredAction,
486
+ };
487
+ }
488
+ }
489
+ if (subCommand === 'rail-recover') {
490
+ if (!flags['handoff-id'] || !flags['attempt-id'] || !flags['operator-id']) {
491
+ return { ok: false, action: 'error', error: 'RAIL_INPUT_REQUIRED', detail: 'Missing --handoff-id, --attempt-id or --operator-id' };
492
+ }
493
+ const attemptId = flags['attempt-id'];
494
+ try {
495
+ const runnerPath = join(PROJECT_DIR, fwDir, '_cli', 'terminal', 'run-rail-task.js');
496
+ if (!existsSync(runnerPath)) return { ok: false, action: 'error', error: 'rail_dispatch_runtime_not_found' };
497
+ const runtime = await import(runtimePath);
498
+ const { executeReadyRailTask } = await import(runnerPath);
499
+ const context = runtime.resolveRailAttemptContext({
500
+ projectDir: PROJECT_DIR,
501
+ handoff_id: flags['handoff-id'],
502
+ attempt_id: attemptId,
503
+ });
504
+ const browserRequest = flags['browser-request-json']
505
+ ? JSON.parse(flags['browser-request-json'])
506
+ : undefined;
507
+ return await executeReadyRailTask({
508
+ projectDir: PROJECT_DIR,
509
+ handoff_id: flags['handoff-id'],
510
+ task_id: context.task.task_id,
511
+ attempt_id: attemptId,
512
+ operator_id: flags['operator-id'],
513
+ recover_expired: true,
514
+ ...(flags.timeout ? { timeout: Number(flags.timeout) } : {}),
515
+ ...(browserRequest === undefined ? {} : { browser_request: browserRequest }),
516
+ });
517
+ } catch (error) {
518
+ return {
519
+ ok: false, action: 'error', error: error.code || 'RAIL_RECOVERY_FAILED',
520
+ detail: error.message, attempt_id: error.details?.attempt_id || attemptId,
521
+ next_required_action: 'rail_recover_and_dispatch_same_attempt',
522
+ };
523
+ }
524
+ }
525
+ if (subCommand === 'rail-review-run') {
526
+ if (!flags['handoff-id'] || !flags['attempt-id']) return { ok: false, action: 'error', error: 'Missing --handoff-id or --attempt-id' };
527
+ try {
528
+ const runnerPath = join(PROJECT_DIR, fwDir, '_cli', 'terminal', 'run-rail-review.js');
529
+ if (!existsSync(runnerPath)) return { ok: false, action: 'error', error: 'rail_review_runtime_not_found' };
530
+ const { executeRailReview } = await import(runnerPath);
531
+ return await executeRailReview({
532
+ projectDir: PROJECT_DIR,
533
+ handoff_id: flags['handoff-id'],
534
+ attempt_id: flags['attempt-id'],
535
+ ...(flags.timeout ? { timeout: Number(flags.timeout) } : {}),
536
+ });
537
+ } catch (error) {
538
+ return { ok: false, action: 'error', error: error.code || 'RAIL_REVIEW_FAILED', detail: error.message };
539
+ }
540
+ }
541
+ if (subCommand === 'rail-rework') {
542
+ if (!flags['handoff-id'] || !flags['attempt-id']) return { ok: false, action: 'error', error: 'Missing --handoff-id or --attempt-id' };
543
+ try {
544
+ const runnerPath = join(PROJECT_DIR, fwDir, '_cli', 'terminal', 'run-rail-rework.js');
545
+ if (!existsSync(runnerPath)) return { ok: false, action: 'error', error: 'rail_rework_runtime_not_found' };
546
+ const { executeRailRework } = await import(runnerPath);
547
+ return await executeRailRework({
548
+ projectDir: PROJECT_DIR,
549
+ handoff_id: flags['handoff-id'],
550
+ attempt_id: flags['attempt-id'],
551
+ ...(flags.timeout ? { timeout: Number(flags.timeout) } : {}),
552
+ });
553
+ } catch (error) {
554
+ return { ok: false, action: 'error', error: error.code || 'RAIL_REWORK_FAILED', detail: error.message };
555
+ }
556
+ }
557
+ if (subCommand === 'rail-adjudicate') {
558
+ if (!flags['handoff-id'] || !flags['attempt-id']) return { ok: false, action: 'error', error: 'Missing --handoff-id or --attempt-id' };
559
+ try {
560
+ const runnerPath = join(PROJECT_DIR, fwDir, '_cli', 'terminal', 'run-rail-adjudication.js');
561
+ if (!existsSync(runnerPath)) return { ok: false, action: 'error', error: 'rail_adjudication_runtime_not_found' };
562
+ const { executeRailAdjudication } = await import(runnerPath);
563
+ return await executeRailAdjudication({
564
+ projectDir: PROJECT_DIR,
565
+ handoff_id: flags['handoff-id'],
566
+ attempt_id: flags['attempt-id'],
567
+ ...(flags.timeout ? { timeout: Number(flags.timeout) } : {}),
568
+ });
569
+ } catch (error) {
570
+ return { ok: false, action: 'error', error: error.code || 'RAIL_ADJUDICATION_FAILED', detail: error.message };
571
+ }
572
+ }
573
+ try {
574
+ const runtime = await import(runtimePath);
575
+ if (subCommand === 'rail-complete') {
576
+ const completion = parseJsonFlag('completion-json', { required: true });
577
+ const { completion_id, attempt_id, claim_id, operator_id, state, git, ci, reviews, acceptance_evidence_refs, acceptance_authority_receipt, completed_at, metrics } = completion;
578
+ return runtime.completeRailTask({
579
+ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'], completion_id, attempt_id, claim_id, operator_id, state, git, ci,
580
+ reviews, acceptance_evidence_refs, acceptance_authority_receipt, ...(completed_at === undefined ? {} : { completed_at }), ...(metrics === undefined ? {} : { metrics }),
581
+ });
582
+ }
583
+ if (subCommand === 'rail-release-ready') {
584
+ return runtime.selectRailReleaseReadiness({ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'] });
585
+ }
586
+ if (subCommand === 'rail-tracking-reconcile') {
587
+ return runtime.reconcileProjectClickUpTracking({ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'] });
588
+ }
589
+ if (subCommand === 'rail-tracking-pending') {
590
+ const clickUpRuntimePath = join(PROJECT_DIR, fwDir, '_cli', 'orchestrator', 'clickup-runtime.js');
591
+ const clickUpRuntime = await import(clickUpRuntimePath);
592
+ return clickUpRuntime.preparePendingClickUpProjections({ projectDir: PROJECT_DIR });
593
+ }
594
+ if (subCommand === 'rail-tracking-ack') {
595
+ if (!flags['handoff-id'] || !flags['projection-id'] || !flags.receipt) {
596
+ throw Object.assign(new Error('Missing --handoff-id, --projection-id or --receipt'), { code: 'RAIL_INPUT_REQUIRED' });
597
+ }
598
+ const clickUpRuntimePath = join(PROJECT_DIR, fwDir, '_cli', 'orchestrator', 'clickup-runtime.js');
599
+ const clickUpRuntime = await import(clickUpRuntimePath);
600
+ const receipt = JSON.parse(readFileSync(resolve(flags.receipt), 'utf8'));
601
+ return clickUpRuntime.acknowledgeClickUpProjection({
602
+ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'],
603
+ projection_id: flags['projection-id'], receipt,
604
+ });
605
+ }
606
+ if (subCommand === 'rail-tracking-lookup') {
607
+ if (!flags['handoff-id'] || !flags['projection-id'] || !flags.result) {
608
+ throw Object.assign(new Error('Missing --handoff-id, --projection-id or --result'), { code: 'RAIL_INPUT_REQUIRED' });
609
+ }
610
+ const clickUpRuntimePath = join(PROJECT_DIR, fwDir, '_cli', 'orchestrator', 'clickup-runtime.js');
611
+ const clickUpRuntime = await import(clickUpRuntimePath);
612
+ const result = JSON.parse(readFileSync(resolve(flags.result), 'utf8'));
613
+ return clickUpRuntime.resolveClickUpProjectionLookup({
614
+ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'],
615
+ projection_id: flags['projection-id'], result,
616
+ });
617
+ }
618
+ } catch (error) {
619
+ return { ok: false, action: 'error', error: error.code || 'RAIL_RUNTIME_FAILED', detail: error.message };
620
+ }
392
621
  return { ok: false, action: 'error', error: `Unsupported RAIL command: ${subCommand}` };
393
622
  }
394
623
 
@@ -486,30 +715,49 @@ async function main() {
486
715
  pipeline: null,
487
716
  team: null,
488
717
  error: null,
718
+ active_harness: ACTIVE_HARNESS,
489
719
  };
490
720
 
721
+ const sessionRead = readHookSession(PROJECT_DIR);
722
+ if (!sessionRead.loaded && !sessionRead.missing) {
723
+ result.ok = false;
724
+ result.action = 'error';
725
+ result.error = `unsafe_session: ${sessionRead.error}`;
726
+ console.log(JSON.stringify(result));
727
+ return;
728
+ }
729
+ if (ACTIVE_HARNESS && sessionRead.loaded) {
730
+ const cliMod = await findCliModule(PROJECT_DIR);
731
+ if (!cliMod) {
732
+ result.ok = false;
733
+ result.action = 'error';
734
+ result.error = 'cli_module_not_found';
735
+ console.log(JSON.stringify(result));
736
+ return;
737
+ }
738
+ const activation = await captureOrchestrate(cliMod, 'activate-harness', ['--harness', ACTIVE_HARNESS], PROJECT_DIR);
739
+ if (!activation.activated) {
740
+ result.ok = false;
741
+ result.action = 'error';
742
+ result.error = activation.error || 'harness_activation_failed';
743
+ console.log(JSON.stringify(result));
744
+ return;
745
+ }
746
+ }
747
+
491
748
  // -----------------------------------------------------------------------
492
749
  // 1. Session state
493
750
  // -----------------------------------------------------------------------
494
- try {
495
- const sessionPath = join(PROJECT_DIR, '.chati', 'session.yaml');
496
- if (existsSync(sessionPath)) {
497
- const raw = readFileSync(sessionPath, 'utf-8');
498
- const extract = (key) => {
499
- const m = raw.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
500
- return m ? m[1].trim().replace(/^["']|["']$/g, '') : null;
501
- };
502
- result.session = {
503
- language: extract('language') || 'en',
504
- project_name: extract('name') || '',
505
- project_type: extract('type') || 'greenfield',
506
- state: extract('state') || 'discover',
507
- current_agent: extract('current_agent') || '',
508
- workflow: extract('workflow') || 'full',
509
- };
510
- }
511
- } catch {
512
- result.session = null;
751
+ if (sessionRead.loaded) {
752
+ const session = sessionRead.session;
753
+ result.session = {
754
+ language: session.language || 'en',
755
+ project_name: session.project?.name || '',
756
+ project_type: session.project?.type || session.project_type || 'greenfield',
757
+ state: session.project?.state || session.mode || 'discover',
758
+ current_agent: session.current_agent || '',
759
+ workflow: session.workflow || 'full',
760
+ };
513
761
  }
514
762
 
515
763
  // -----------------------------------------------------------------------
@@ -521,14 +769,20 @@ async function main() {
521
769
  const cliMod = await findCliModule(PROJECT_DIR);
522
770
  if (findCliModule._mismatch) result.version_mismatch = findCliModule._mismatch;
523
771
  if (!cliMod) {
772
+ result.ok = false;
524
773
  result.action = 'error';
525
774
  result.error = 'cli_module_not_found';
526
775
  result.reason = 'Bundled CLI missing in .chati.dev/_cli/. Reinstall with: npx chati-dev upgrade';
527
776
  } else {
528
777
  result.pipeline = await captureOrchestrate(cliMod, 'next', [], PROJECT_DIR);
529
778
  result.action = result.pipeline.action;
779
+ if (result.action === 'error' || result.pipeline?.ok === false) {
780
+ result.ok = false;
781
+ result.error = result.pipeline?.error || result.pipeline?.code || 'pipeline_routing_failed';
782
+ }
530
783
  }
531
784
  } catch (err) {
785
+ result.ok = false;
532
786
  result.action = 'error';
533
787
  result.error = `pipeline_routing_failed: ${err.message?.slice(0, 200)}`;
534
788
  }