chati-dev 4.5.16 → 4.5.28

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 (122) 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 +1 -1
  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 +252 -25
  43. package/framework/orchestrator/chati.md +238 -70
  44. package/framework/schemas/session.schema.json +1 -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 +148 -53
  82. package/src/installer/manifest.js +140 -9
  83. package/src/installer/package-artifact.js +249 -0
  84. package/src/installer/templates.js +65 -20
  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 +247 -0
  89. package/src/installer-v2/wizard-installation.js +1 -1
  90. package/src/orchestrator/browser-runtime.js +44 -13
  91. package/src/orchestrator/cli.js +1545 -174
  92. package/src/orchestrator/clickup-projection.js +43 -1
  93. package/src/orchestrator/clickup-runtime.js +355 -39
  94. package/src/orchestrator/doctor.js +16 -3
  95. package/src/orchestrator/index.js +16 -0
  96. package/src/orchestrator/planning-runtime.js +20 -2
  97. package/src/orchestrator/rail-adjudication-evidence.js +234 -0
  98. package/src/orchestrator/rail-evidence-authority.js +147 -0
  99. package/src/orchestrator/rail-execution-evidence.js +45 -0
  100. package/src/orchestrator/rail-runtime.js +836 -56
  101. package/src/orchestrator/release-runtime.js +65 -6
  102. package/src/orchestrator/review-runtime.js +186 -41
  103. package/src/orchestrator/runtime-installation-v2.js +236 -20
  104. package/src/orchestrator/session-manager.js +1167 -53
  105. package/src/terminal/adapters/claude-adapter.js +3 -1
  106. package/src/terminal/adapters/codex-adapter.js +2 -0
  107. package/src/terminal/adapters/grok-adapter.js +7 -4
  108. package/src/terminal/handoff-parser.js +19 -1
  109. package/src/terminal/prompt-builder.js +9 -1
  110. package/src/terminal/provider-preflight.js +36 -3
  111. package/src/terminal/rail-execution-worktree.js +213 -0
  112. package/src/terminal/rail-prompts.js +169 -0
  113. package/src/terminal/rail-readonly-workspace.js +324 -0
  114. package/src/terminal/run-agent.js +384 -24
  115. package/src/terminal/run-parallel.js +5 -0
  116. package/src/terminal/run-rail-adjudication.js +323 -0
  117. package/src/terminal/run-rail-review.js +291 -0
  118. package/src/terminal/run-rail-rework.js +325 -0
  119. package/src/terminal/run-rail-task.js +380 -0
  120. package/src/terminal/run-team.js +5 -0
  121. package/src/terminal/spawner.js +1225 -77
  122. package/src/wizard/index.js +3 -2
@@ -16,7 +16,9 @@
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
24
  const RAW_ROUTER_ARGS = process.argv.slice(2);
@@ -45,6 +47,7 @@ async function readEnabledHarnesses(projectDir) {
45
47
 
46
48
  const detected = [];
47
49
  if (existsSync(join(projectDir, '.claude', 'commands', 'chati.md'))) detected.push('claude');
50
+ if (existsSync(join(projectDir, '.gemini', 'commands', 'chati.toml'))) detected.push('gemini');
48
51
  if (existsSync(join(projectDir, '.agents', 'skills', 'chati', 'SKILL.md'))) detected.push('codex');
49
52
  if (existsSync(join(projectDir, '.grok', 'commands', 'chati.md'))) detected.push('grok');
50
53
  return { harnesses: detected, error: null };
@@ -69,7 +72,7 @@ export async function validateProviderIntegrity(projectDir) {
69
72
  const { harnesses, error } = await readEnabledHarnesses(projectDir);
70
73
 
71
74
  if (error) missing.push(error);
72
- const supportedHarnesses = new Set(['claude', 'codex', 'grok']);
75
+ const supportedHarnesses = new Set(['claude', 'gemini', 'codex', 'grok']);
73
76
  for (const harness of harnesses) {
74
77
  if (!supportedHarnesses.has(harness)) missing.push(`unsupported harness ${harness}`);
75
78
  }
@@ -137,9 +140,40 @@ export async function validateProviderIntegrity(projectDir) {
137
140
  }
138
141
  }
139
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
+
140
169
  if (harnesses.includes('grok')) {
141
170
  if (!hasContent(join(projectDir, '.grok', 'commands', 'chati.md'))) missing.push('grok command');
142
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
+ }
143
177
  }
144
178
 
145
179
  return { valid: missing.length === 0, missing };
@@ -320,7 +354,20 @@ const VALID_ORCHESTRATE_SUBS = new Set([
320
354
  'activate-harness',
321
355
  ]);
322
356
 
323
- 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
+ ]);
324
371
 
325
372
  /**
326
373
  * Parse --flag value pairs and standalone --flag bools from argv.
@@ -386,8 +433,17 @@ async function dispatchRail(subCommand, argv) {
386
433
  }
387
434
 
388
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
+ };
389
445
  if (subCommand === 'rail-next') {
390
- 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' };
391
447
  try {
392
448
  const { selectReadyRailTasks } = await import(runtimePath);
393
449
  return selectReadyRailTasks({ projectDir: PROJECT_DIR, handoff_id: flags['handoff-id'] });
@@ -395,6 +451,173 @@ async function dispatchRail(subCommand, argv) {
395
451
  return { ok: false, action: 'error', error: error.code || error.message };
396
452
  }
397
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
+ }
398
621
  return { ok: false, action: 'error', error: `Unsupported RAIL command: ${subCommand}` };
399
622
  }
400
623
 
@@ -495,8 +718,15 @@ async function main() {
495
718
  active_harness: ACTIVE_HARNESS,
496
719
  };
497
720
 
498
- const canonicalSessionPath = join(PROJECT_DIR, '.chati', 'session.yaml');
499
- if (ACTIVE_HARNESS && existsSync(canonicalSessionPath)) {
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) {
500
730
  const cliMod = await findCliModule(PROJECT_DIR);
501
731
  if (!cliMod) {
502
732
  result.ok = false;
@@ -518,25 +748,16 @@ async function main() {
518
748
  // -----------------------------------------------------------------------
519
749
  // 1. Session state
520
750
  // -----------------------------------------------------------------------
521
- try {
522
- const sessionPath = canonicalSessionPath;
523
- if (existsSync(sessionPath)) {
524
- const raw = readFileSync(sessionPath, 'utf-8');
525
- const extract = (key) => {
526
- const m = raw.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
527
- return m ? m[1].trim().replace(/^["']|["']$/g, '') : null;
528
- };
529
- result.session = {
530
- language: extract('language') || 'en',
531
- project_name: extract('name') || '',
532
- project_type: extract('type') || 'greenfield',
533
- state: extract('state') || 'discover',
534
- current_agent: extract('current_agent') || '',
535
- workflow: extract('workflow') || 'full',
536
- };
537
- }
538
- } catch {
539
- 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
+ };
540
761
  }
541
762
 
542
763
  // -----------------------------------------------------------------------
@@ -548,14 +769,20 @@ async function main() {
548
769
  const cliMod = await findCliModule(PROJECT_DIR);
549
770
  if (findCliModule._mismatch) result.version_mismatch = findCliModule._mismatch;
550
771
  if (!cliMod) {
772
+ result.ok = false;
551
773
  result.action = 'error';
552
774
  result.error = 'cli_module_not_found';
553
775
  result.reason = 'Bundled CLI missing in .chati.dev/_cli/. Reinstall with: npx chati-dev upgrade';
554
776
  } else {
555
777
  result.pipeline = await captureOrchestrate(cliMod, 'next', [], PROJECT_DIR);
556
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
+ }
557
783
  }
558
784
  } catch (err) {
785
+ result.ok = false;
559
786
  result.action = 'error';
560
787
  result.error = `pipeline_routing_failed: ${err.message?.slice(0, 200)}`;
561
788
  }