@open-agent-toolkit/cli 0.1.69 → 0.1.73

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 (79) hide show
  1. package/README.md +1 -0
  2. package/assets/docs/cli-utilities/configuration.md +34 -0
  3. package/assets/docs/cli-utilities/index.md +2 -1
  4. package/assets/docs/cli-utilities/project-log.md +183 -0
  5. package/assets/docs/cli-utilities/workflow-gates.md +126 -3
  6. package/assets/docs/reference/cli-reference.md +6 -1
  7. package/assets/docs/workflows/projects/dispatch-ceiling.md +14 -5
  8. package/assets/docs/workflows/projects/orchestration-model.md +21 -0
  9. package/assets/docs/workflows/projects/review-flavors.md +9 -0
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-dispatch-subagents/SKILL.md +21 -1
  12. package/assets/skills/oat-dispatch-subagents/references/provider-claude.md +21 -0
  13. package/assets/skills/oat-dispatch-subagents/references/provider-codex.md +16 -0
  14. package/assets/skills/oat-dispatch-subagents/references/provider-cursor.md +21 -0
  15. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +6 -2
  16. package/assets/skills/oat-project-complete/SKILL.md +60 -1
  17. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +6 -2
  18. package/assets/skills/oat-project-implement/SKILL.md +18 -2
  19. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +6 -2
  20. package/assets/skills/oat-project-plan-writing/SKILL.md +23 -33
  21. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +6 -2
  22. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +6 -2
  23. package/assets/skills/oat-project-review-provide/SKILL.md +62 -1
  24. package/assets/skills/oat-project-summary/SKILL.md +122 -18
  25. package/assets/templates/project-log.md +47 -0
  26. package/dist/commands/config/index.d.ts.map +1 -1
  27. package/dist/commands/config/index.js +106 -1
  28. package/dist/commands/gate/__fixtures__/fake-runtime.d.mts +3 -0
  29. package/dist/commands/gate/__fixtures__/fake-runtime.d.mts.map +1 -0
  30. package/dist/commands/gate/__fixtures__/fake-runtime.mjs +167 -0
  31. package/dist/commands/gate/activity-probes.d.ts +35 -0
  32. package/dist/commands/gate/activity-probes.d.ts.map +1 -0
  33. package/dist/commands/gate/activity-probes.js +135 -0
  34. package/dist/commands/gate/branch-local-cli.d.ts +31 -0
  35. package/dist/commands/gate/branch-local-cli.d.ts.map +1 -0
  36. package/dist/commands/gate/branch-local-cli.js +116 -0
  37. package/dist/commands/gate/index.d.ts +31 -0
  38. package/dist/commands/gate/index.d.ts.map +1 -1
  39. package/dist/commands/gate/index.js +533 -32
  40. package/dist/commands/gate/route.d.ts +22 -0
  41. package/dist/commands/gate/route.d.ts.map +1 -0
  42. package/dist/commands/gate/route.js +109 -0
  43. package/dist/commands/init/tools/shared/skill-manifest.d.ts +1 -1
  44. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  45. package/dist/commands/init/tools/shared/skill-manifest.js +1 -0
  46. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  47. package/dist/commands/project/dispatch-ceiling/index.js +100 -16
  48. package/dist/commands/project/index.d.ts.map +1 -1
  49. package/dist/commands/project/index.js +2 -0
  50. package/dist/commands/project/log/append.d.ts +41 -0
  51. package/dist/commands/project/log/append.d.ts.map +1 -0
  52. package/dist/commands/project/log/append.js +241 -0
  53. package/dist/commands/project/log/check.d.ts +66 -0
  54. package/dist/commands/project/log/check.d.ts.map +1 -0
  55. package/dist/commands/project/log/check.js +194 -0
  56. package/dist/commands/project/log/grammar.d.ts +28 -0
  57. package/dist/commands/project/log/grammar.d.ts.map +1 -0
  58. package/dist/commands/project/log/grammar.js +31 -0
  59. package/dist/commands/project/log/index.d.ts +9 -0
  60. package/dist/commands/project/log/index.d.ts.map +1 -0
  61. package/dist/commands/project/log/index.js +13 -0
  62. package/dist/commands/project/log/rollup.d.ts +40 -0
  63. package/dist/commands/project/log/rollup.d.ts.map +1 -0
  64. package/dist/commands/project/log/rollup.js +203 -0
  65. package/dist/commands/project/log/synthesize.d.ts +23 -0
  66. package/dist/commands/project/log/synthesize.d.ts.map +1 -0
  67. package/dist/commands/project/log/synthesize.js +112 -0
  68. package/dist/commands/project/new/index.d.ts +1 -0
  69. package/dist/commands/project/new/index.d.ts.map +1 -1
  70. package/dist/commands/project/new/index.js +7 -0
  71. package/dist/commands/project/new/scaffold.d.ts +1 -0
  72. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  73. package/dist/commands/project/new/scaffold.js +24 -0
  74. package/dist/config/oat-config.d.ts +12 -0
  75. package/dist/config/oat-config.d.ts.map +1 -1
  76. package/dist/config/oat-config.js +30 -0
  77. package/dist/config/resolve.d.ts.map +1 -1
  78. package/dist/config/resolve.js +9 -0
  79. package/package.json +2 -2
@@ -7,7 +7,7 @@ import { readGlobalOptions } from '../shared/shared.utils.js';
7
7
  import { compileDispatchCeilingPreset } from '../../config/dispatch-ceiling-preset.js';
8
8
  import { normalizeDispatchMatrix, validateDispatchRouteTarget, walkDispatchMatrix, } from '../../config/dispatch-matrix.js';
9
9
  import { dispatchPolicyModeDescription, dispatchPolicyPolicyDescription, managedDispatchPolicyValueList, } from '../../config/dispatch-policy-options.js';
10
- import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, normalizeWorkflowPostImplementSequence, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
10
+ import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
11
11
  import { resolveEffectiveConfig, } from '../../config/resolve.js';
12
12
  import { resolveAssetsRoot } from '../../fs/assets.js';
13
13
  import { resolveProjectRoot } from '../../fs/paths.js';
@@ -57,12 +57,16 @@ const KEY_ORDER = [
57
57
  'workflow.autoNarrowReReviewScope',
58
58
  'workflow.autoArtifactReview.plan',
59
59
  'workflow.autoArtifactReview.analysis',
60
+ 'workflow.projectLog',
61
+ 'workflow.projectLogLedgerPath',
60
62
  'workflow.designMode',
61
63
  'workflow.dispatchPolicy.mode',
62
64
  'workflow.dispatchPolicy.policy',
63
65
  'workflow.dispatchCeiling.preset',
64
66
  'workflow.dispatchCeiling.providers.codex',
65
67
  'workflow.dispatchCeiling.providers.claude',
68
+ 'workflow.gateTimeouts.code',
69
+ 'workflow.gateTimeouts.artifact',
66
70
  'worktrees.root',
67
71
  ];
68
72
  const CONFIG_CATALOG = [
@@ -462,6 +466,28 @@ const CONFIG_CATALOG = [
462
466
  owningCommand: 'oat config set workflow.autoArtifactReview.analysis <true|false>',
463
467
  description: 'Automatically run the bounded accuracy-review loop for generated analysis artifacts before apply workflows consume them. Resolution: local > shared > user > default.',
464
468
  },
469
+ {
470
+ key: 'workflow.projectLog',
471
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
472
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
473
+ scope: 'workflow',
474
+ type: 'true | false | auto',
475
+ defaultValue: 'auto',
476
+ mutability: 'read/write',
477
+ owningCommand: 'oat config set workflow.projectLog <true|false|auto>',
478
+ description: 'Controls the append-only per-project observation log. auto creates it on first append; an existing artifact always remains enabled. Resolution: local > shared > user > default.',
479
+ },
480
+ {
481
+ key: 'workflow.projectLogLedgerPath',
482
+ group: 'Workflow Preferences (3-layer: local > shared > user)',
483
+ file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
484
+ scope: 'workflow',
485
+ type: 'string',
486
+ defaultValue: '.oat/repo/reference/project-observations.md',
487
+ mutability: 'read/write',
488
+ owningCommand: 'oat config set workflow.projectLogLedgerPath <repo-relative-path>',
489
+ description: 'Repo-relative ledger path used by project-log rollup. Resolution: local > shared > user > default.',
490
+ },
465
491
  {
466
492
  key: 'workflow.designMode',
467
493
  group: 'Workflow Preferences (3-layer: local > shared > user)',
@@ -583,6 +609,8 @@ const DEFAULT_DEPENDENCIES = {
583
609
  };
584
610
  function isConfigKey(value) {
585
611
  return (KEY_ORDER.includes(value) ||
612
+ value === 'workflow.dispatchCeiling' ||
613
+ value === 'workflow.dispatchCeiling.providers' ||
586
614
  isDispatchCeilingProviderKey(value));
587
615
  }
588
616
  function isDispatchCeilingProviderKey(value) {
@@ -741,6 +769,23 @@ function parseWorkflowValue(key, rawValue) {
741
769
  if (WORKFLOW_BOOLEAN_KEYS.has(key)) {
742
770
  return parseBooleanValue(key, rawValue);
743
771
  }
772
+ if (key === 'workflow.projectLog') {
773
+ const normalized = rawValue.trim().toLowerCase();
774
+ if (normalized === 'true' || normalized === 'false') {
775
+ return normalized === 'true';
776
+ }
777
+ if (normalized === 'auto') {
778
+ return normalized;
779
+ }
780
+ throw new Error(`Invalid value for ${key}: expected one of true | false | auto, got '${rawValue}'`);
781
+ }
782
+ if (key === 'workflow.projectLogLedgerPath') {
783
+ const normalized = rawValue.trim();
784
+ if (!normalized) {
785
+ throw new Error(`Invalid value for ${key}: expected a non-empty string path`);
786
+ }
787
+ return normalized;
788
+ }
744
789
  if (key === 'workflow.postImplementSequence') {
745
790
  const normalized = rawValue.trim();
746
791
  if (normalized.startsWith('{') || normalized.startsWith('[')) {
@@ -758,6 +803,14 @@ function parseWorkflowValue(key, rawValue) {
758
803
  return sequence;
759
804
  }
760
805
  }
806
+ if (key === 'workflow.gateTimeouts.code' ||
807
+ key === 'workflow.gateTimeouts.artifact') {
808
+ const value = Number(rawValue);
809
+ if (!isValidGateTimeoutMs(value)) {
810
+ throw new Error(`Invalid value for ${key}: expected an integer between ${MIN_GATE_TIMEOUT_MS} and ${MAX_GATE_TIMEOUT_MS}`);
811
+ }
812
+ return value;
813
+ }
761
814
  const allowed = WORKFLOW_ENUM_VALUES[key];
762
815
  if (allowed) {
763
816
  const normalized = rawValue.trim();
@@ -875,6 +928,16 @@ function applyWorkflowValue(workflow, key, value) {
875
928
  postImplementSequence: value,
876
929
  };
877
930
  }
931
+ if (subKey.startsWith('gateTimeouts.')) {
932
+ const timeoutKey = subKey.slice('gateTimeouts.'.length);
933
+ return {
934
+ ...workflow,
935
+ gateTimeouts: {
936
+ ...workflow.gateTimeouts,
937
+ [timeoutKey]: value,
938
+ },
939
+ };
940
+ }
878
941
  if (subKey === 'dispatchPolicy.mode') {
879
942
  const mode = value;
880
943
  if (mode === 'inherit') {
@@ -991,6 +1054,10 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies, preser
991
1054
  const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
992
1055
  const entry = resolved.resolved[key];
993
1056
  if (!entry) {
1057
+ const aggregate = buildResolvedConfigAggregate(resolved, key);
1058
+ if (aggregate) {
1059
+ return { key, ...aggregate };
1060
+ }
994
1061
  return { key, value: null, source: 'default' };
995
1062
  }
996
1063
  return {
@@ -1001,6 +1068,44 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies, preser
1001
1068
  source: entry.source,
1002
1069
  };
1003
1070
  }
1071
+ function buildResolvedConfigAggregate(resolved, key) {
1072
+ const prefix = `${key}.`;
1073
+ const entries = Object.entries(resolved.resolved).filter(([entryKey]) => entryKey.startsWith(prefix));
1074
+ if (entries.length === 0) {
1075
+ return null;
1076
+ }
1077
+ const value = {};
1078
+ let source = 'default';
1079
+ const sourceRank = {
1080
+ default: 0,
1081
+ user: 1,
1082
+ shared: 2,
1083
+ local: 3,
1084
+ env: 4,
1085
+ };
1086
+ for (const [entryKey, entry] of entries) {
1087
+ if (sourceRank[entry.source] > sourceRank[source]) {
1088
+ source = entry.source;
1089
+ }
1090
+ const parts = entryKey.slice(prefix.length).split('.');
1091
+ let cursor = value;
1092
+ for (const [index, part] of parts.entries()) {
1093
+ if (index === parts.length - 1) {
1094
+ cursor[part] = entry.value;
1095
+ }
1096
+ else {
1097
+ const nested = cursor[part];
1098
+ if (typeof nested !== 'object' ||
1099
+ nested === null ||
1100
+ Array.isArray(nested)) {
1101
+ cursor[part] = {};
1102
+ }
1103
+ cursor = cursor[part];
1104
+ }
1105
+ }
1106
+ }
1107
+ return { value, source };
1108
+ }
1004
1109
  async function listConfigKeys(repoRoot, userConfigDir, dependencies) {
1005
1110
  const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
1006
1111
  const staticKeys = new Set(KEY_ORDER);
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=fake-runtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-runtime.d.mts","sourceRoot":"","sources":["../../../../src/commands/gate/__fixtures__/fake-runtime.mjs"],"names":[],"mappings":""}
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { writeSync } from 'node:fs';
4
+ import { appendFile, mkdir, writeFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ const prompt = process.argv.at(-1) ?? '';
7
+ const delayMs = Number(process.env.FAKE_GATE_DELAY_MS ?? '0');
8
+ const exitCode = Number(process.env.FAKE_GATE_EXIT_CODE ?? '0');
9
+ const artifactMode = process.env.FAKE_GATE_ARTIFACT ?? 'none';
10
+ const transcriptIntervalMs = Number(process.env.FAKE_GATE_TRANSCRIPT_INTERVAL_MS ?? '0');
11
+ function promptValue(key) {
12
+ return prompt.match(new RegExp(`^${key}: (.+)$`, 'm'))?.[1] ?? 'unknown';
13
+ }
14
+ function projectPath() {
15
+ return (prompt.match(/^Resolved OAT project path: (.+)\. Run the review/m)?.[1] ??
16
+ '.oat/projects/shared/demo');
17
+ }
18
+ async function writeTranscript() {
19
+ const encodedCwd = process.cwd().split(/[/.]/u).filter(Boolean).join('-');
20
+ const transcriptRoot = process.env.FAKE_GATE_TRANSCRIPT_DIR ??
21
+ join(process.env.HOME ?? '', '.cursor', 'projects', encodedCwd, 'agent-transcripts');
22
+ const transcript = join(transcriptRoot, 'fake-session', 'fake-session.jsonl');
23
+ await mkdir(join(transcript, '..'), { recursive: true });
24
+ await appendFile(transcript, `${Date.now()}\n`);
25
+ }
26
+ async function writeArtifact() {
27
+ const runId = artifactMode === 'wrong-run'
28
+ ? 'wrong-run-id'
29
+ : promptValue('oat_gate_run_id');
30
+ const project = projectPath();
31
+ const reviewsDir = join(process.cwd(), project, 'reviews');
32
+ await mkdir(reviewsDir, { recursive: true });
33
+ await writeFile(join(reviewsDir, `fake-review-${Date.now()}.md`), [
34
+ '---',
35
+ 'oat_generated: true',
36
+ `oat_generated_at: ${new Date().toISOString()}`,
37
+ `oat_review_type: ${prompt.match(/^Review type: (.+)\.$/m)?.[1] ?? 'code'}`,
38
+ `oat_review_scope: ${prompt.match(/^Review scope: (.+)\.$/m)?.[1] ?? 'final'}`,
39
+ 'oat_review_invocation: gate',
40
+ `oat_project: ${project}`,
41
+ `oat_gate_run_id: ${runId}`,
42
+ `oat_gate_target: ${promptValue('oat_gate_target')}`,
43
+ `oat_gate_runtime: ${promptValue('oat_gate_runtime')}`,
44
+ `oat_invocation_model: ${promptValue('oat_invocation_model')}`,
45
+ `oat_invocation_reasoning_effort: ${promptValue('oat_invocation_reasoning_effort')}`,
46
+ `oat_invocation_source: ${promptValue('oat_invocation_source')}`,
47
+ 'oat_review_critical_count: 0',
48
+ 'oat_review_important_count: 0',
49
+ 'oat_review_medium_count: 0',
50
+ 'oat_review_minor_count: 0',
51
+ '---',
52
+ '',
53
+ '# Fake runtime review',
54
+ '',
55
+ '## Findings',
56
+ '',
57
+ '### Critical',
58
+ '',
59
+ 'None',
60
+ '',
61
+ '### Important',
62
+ '',
63
+ 'None',
64
+ '',
65
+ '### Medium',
66
+ '',
67
+ 'None',
68
+ '',
69
+ '### Minor',
70
+ '',
71
+ 'None',
72
+ '',
73
+ ].join('\n'));
74
+ }
75
+ if (process.env.FAKE_GATE_REQUIRE_HEADLESS === '1') {
76
+ if (process.env.OAT_GATE_HEADLESS !== '1' ||
77
+ process.env.OAT_NON_INTERACTIVE !== '1' ||
78
+ !process.env.OAT_GATE_RUN_ID) {
79
+ process.stdout.write('OAT_GATE_REFUSAL: fake runtime did not receive headless context\n');
80
+ process.exit(9);
81
+ }
82
+ }
83
+ if (process.env.FAKE_GATE_WRITE_ROUTE_RECEIPT_RUNTIME &&
84
+ !process.env.FAKE_GATE_REQUIRE_ROUTE_RUNTIME) {
85
+ await writeFile(process.env.OAT_GATE_ROUTE_RECEIPT_PATH, JSON.stringify({
86
+ route: 'inline',
87
+ reason: 'deterministic fake route receipt',
88
+ cliRoot: process.env.OAT_GATE_CLI_ROOT,
89
+ runtime: process.env.FAKE_GATE_WRITE_ROUTE_RECEIPT_RUNTIME,
90
+ }));
91
+ }
92
+ if (process.env.FAKE_GATE_REQUIRE_ROUTE_RUNTIME) {
93
+ const cliPath = process.env.OAT_GATE_CLI_PATH;
94
+ const cliRoot = process.env.OAT_GATE_CLI_ROOT;
95
+ const configuredRuntime = process.env.FAKE_GATE_REQUIRE_ROUTE_RUNTIME;
96
+ const runtime = process.env.OAT_GATE_RUNTIME;
97
+ const model = process.env.OAT_INVOCATION_MODEL;
98
+ const promptRuntime = promptValue('oat_gate_runtime');
99
+ const promptModel = promptValue('oat_invocation_model');
100
+ if (!runtime ||
101
+ !model ||
102
+ runtime !== configuredRuntime ||
103
+ runtime !== promptRuntime ||
104
+ model !== promptModel) {
105
+ writeSync(1, `FAKE_GATE_ROUTE_INPUT_ERROR:${String(runtime)}:${String(model)}:${configuredRuntime}:${promptRuntime}:${promptModel}\n`);
106
+ process.stdout.write('OAT_GATE_REFUSAL: canonical gate route inputs were absent or inconsistent\n');
107
+ process.exit(10);
108
+ }
109
+ const marker = runtime === 'claude'
110
+ ? { CLAUDECODE: '1' }
111
+ : runtime === 'cursor'
112
+ ? { CURSOR_AGENT: '1' }
113
+ : { CODEX_SESSION_ID: 'fake-session' };
114
+ const result = cliPath
115
+ ? spawnSync(cliPath, [
116
+ 'gate',
117
+ 'route',
118
+ '--json',
119
+ '--expect-runtime',
120
+ runtime,
121
+ '--expect-model',
122
+ model,
123
+ '--can-await',
124
+ 'false',
125
+ ], {
126
+ encoding: 'utf8',
127
+ env: { ...process.env, ...marker },
128
+ })
129
+ : undefined;
130
+ let route;
131
+ try {
132
+ route = result?.status === 0 ? JSON.parse(result.stdout) : undefined;
133
+ }
134
+ catch {
135
+ route = undefined;
136
+ }
137
+ if (route?.route !== 'inline' ||
138
+ route?.cliRoot !== cliRoot ||
139
+ typeof route?.reason !== 'string') {
140
+ writeSync(1, `FAKE_GATE_ROUTE_ERROR:${result?.status ?? 'absent'}:${JSON.stringify(result?.stdout ?? '')}:${JSON.stringify(result?.stderr ?? '')}:${String(route?.cliRoot)}:${String(cliRoot)}\n`);
141
+ process.stdout.write('OAT_GATE_REFUSAL: branch-local gate route was unavailable or invalid\n');
142
+ process.exit(10);
143
+ }
144
+ if (process.env.FAKE_GATE_REPORT_ROUTE === '1') {
145
+ process.stdout.write(`FAKE_GATE_ROUTE:${route.route}:${runtime}:${model}:${route.cliRoot}\n`);
146
+ }
147
+ }
148
+ if (process.env.FAKE_GATE_STDOUT) {
149
+ process.stdout.write(`${process.env.FAKE_GATE_STDOUT}\n`);
150
+ }
151
+ if (process.env.FAKE_GATE_REFUSAL) {
152
+ process.stdout.write(`OAT_GATE_REFUSAL: ${process.env.FAKE_GATE_REFUSAL}\n`);
153
+ }
154
+ let transcriptTimer;
155
+ if (transcriptIntervalMs > 0) {
156
+ await writeTranscript();
157
+ transcriptTimer = setInterval(() => {
158
+ void writeTranscript();
159
+ }, transcriptIntervalMs);
160
+ }
161
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
162
+ if (transcriptTimer)
163
+ clearInterval(transcriptTimer);
164
+ if (artifactMode === 'correlated' || artifactMode === 'wrong-run') {
165
+ await writeArtifact();
166
+ }
167
+ process.exit(exitCode);
@@ -0,0 +1,35 @@
1
+ export interface GateActivityEvidence {
2
+ source: 'transcript-dir';
3
+ runtime: string;
4
+ scope: 'project-dir' | 'ambient-runtime';
5
+ observedPath: string;
6
+ lastChangeAt: number | null;
7
+ totalSizeBytes: number | null;
8
+ changedSinceBaseline: boolean;
9
+ observedAt: number;
10
+ }
11
+ export interface GateActivityProbeStatus {
12
+ status: 'available' | 'path-absent' | 'error';
13
+ runtime: string;
14
+ scope: 'project-dir' | 'ambient-runtime';
15
+ attemptedPath: string;
16
+ observedAt: number;
17
+ evidence?: GateActivityEvidence;
18
+ }
19
+ export interface GateActivityProbe {
20
+ runtime: 'claude' | 'codex' | 'cursor';
21
+ probe(observedAt?: number): Promise<GateActivityEvidence | null>;
22
+ observe(observedAt?: number): Promise<GateActivityProbeStatus>;
23
+ }
24
+ export interface GateActivityProbeContext {
25
+ runtime: string;
26
+ cwd: string;
27
+ home: string;
28
+ spawnedAt: number;
29
+ realCwd?: string;
30
+ }
31
+ export declare function encodeClaudeProjectPath(cwd: string): string;
32
+ export declare function encodeCursorProjectPath(cwd: string): string;
33
+ export declare function resolveGateActivityPaths(context: GateActivityProbeContext, observedAt?: number): string[];
34
+ export declare function createGateActivityProbe(context: GateActivityProbeContext): Promise<GateActivityProbe | null>;
35
+ //# sourceMappingURL=activity-probes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-probes.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/activity-probes.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,GAAG,iBAAiB,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,GAAG,iBAAiB,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAOD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3D;AAWD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,wBAAwB,EACjC,UAAU,SAAoB,GAC7B,MAAM,EAAE,CA6BV;AA+DD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAyDnC"}
@@ -0,0 +1,135 @@
1
+ import { readdir, realpath, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const MAX_TRAVERSAL_DEPTH = 2;
4
+ export function encodeClaudeProjectPath(cwd) {
5
+ return cwd.replace(/[/._]/gu, '-');
6
+ }
7
+ export function encodeCursorProjectPath(cwd) {
8
+ return cwd.split(/[/._]/u).filter(Boolean).join('-');
9
+ }
10
+ function utcDatePath(timestamp) {
11
+ const date = new Date(timestamp);
12
+ return [
13
+ String(date.getUTCFullYear()),
14
+ String(date.getUTCMonth() + 1).padStart(2, '0'),
15
+ String(date.getUTCDate()).padStart(2, '0'),
16
+ ];
17
+ }
18
+ export function resolveGateActivityPaths(context, observedAt = context.spawnedAt) {
19
+ const cwdVariants = [...new Set([context.cwd, context.realCwd])].filter((cwd) => Boolean(cwd));
20
+ if (context.runtime === 'claude') {
21
+ return cwdVariants.map((cwd) => join(context.home, '.claude', 'projects', encodeClaudeProjectPath(cwd)));
22
+ }
23
+ if (context.runtime === 'cursor') {
24
+ return cwdVariants.map((cwd) => join(context.home, '.cursor', 'projects', encodeCursorProjectPath(cwd), 'agent-transcripts'));
25
+ }
26
+ if (context.runtime === 'codex') {
27
+ const sessionsRoot = join(context.home, '.codex', 'sessions');
28
+ const paths = [
29
+ join(sessionsRoot, ...utcDatePath(context.spawnedAt)),
30
+ join(sessionsRoot, ...utcDatePath(observedAt)),
31
+ ];
32
+ return [...new Set(paths)];
33
+ }
34
+ return [];
35
+ }
36
+ async function collectMetadata(path, depth) {
37
+ const pathStat = await stat(path);
38
+ let lastChangeAt = pathStat.mtimeMs;
39
+ let totalSizeBytes = pathStat.size;
40
+ if (!pathStat.isDirectory() || depth >= MAX_TRAVERSAL_DEPTH) {
41
+ return { lastChangeAt, totalSizeBytes };
42
+ }
43
+ const entries = await readdir(path, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const child = await collectMetadata(join(path, entry.name), depth + 1);
46
+ lastChangeAt = Math.max(lastChangeAt, child.lastChangeAt);
47
+ totalSizeBytes += child.totalSizeBytes;
48
+ }
49
+ return { lastChangeAt, totalSizeBytes };
50
+ }
51
+ async function snapshotPaths(paths) {
52
+ let found = false;
53
+ let lastChangeAt = 0;
54
+ let totalSizeBytes = 0;
55
+ try {
56
+ for (const path of paths) {
57
+ try {
58
+ const snapshot = await collectMetadata(path, 0);
59
+ found = true;
60
+ lastChangeAt = Math.max(lastChangeAt, snapshot.lastChangeAt);
61
+ totalSizeBytes += snapshot.totalSizeBytes;
62
+ }
63
+ catch (error) {
64
+ if (error instanceof Error &&
65
+ 'code' in error &&
66
+ error.code === 'ENOENT') {
67
+ continue;
68
+ }
69
+ return { status: 'error' };
70
+ }
71
+ }
72
+ }
73
+ catch {
74
+ return { status: 'error' };
75
+ }
76
+ return found
77
+ ? {
78
+ status: 'available',
79
+ snapshot: { lastChangeAt, totalSizeBytes },
80
+ }
81
+ : { status: 'path-absent' };
82
+ }
83
+ export async function createGateActivityProbe(context) {
84
+ const resolvedContext = {
85
+ ...context,
86
+ realCwd: await realpath(context.cwd).catch(() => context.cwd),
87
+ };
88
+ const initialPaths = resolveGateActivityPaths(resolvedContext);
89
+ if (initialPaths.length === 0)
90
+ return null;
91
+ const baseline = await snapshotPaths(initialPaths);
92
+ const scope = context.runtime === 'codex' ? 'ambient-runtime' : 'project-dir';
93
+ const observe = async (observedAt = Date.now()) => {
94
+ const paths = resolveGateActivityPaths(resolvedContext, observedAt);
95
+ const current = await snapshotPaths(paths);
96
+ const attemptedPath = paths.join(',');
97
+ if (current.status !== 'available' || !current.snapshot) {
98
+ return {
99
+ status: current.status,
100
+ runtime: context.runtime,
101
+ scope,
102
+ attemptedPath,
103
+ observedAt,
104
+ };
105
+ }
106
+ const evidence = {
107
+ source: 'transcript-dir',
108
+ runtime: context.runtime,
109
+ scope,
110
+ observedPath: attemptedPath,
111
+ lastChangeAt: current.snapshot.lastChangeAt,
112
+ totalSizeBytes: current.snapshot.totalSizeBytes,
113
+ changedSinceBaseline: baseline.status !== 'available' ||
114
+ !baseline.snapshot ||
115
+ current.snapshot.lastChangeAt !== baseline.snapshot.lastChangeAt ||
116
+ current.snapshot.totalSizeBytes !== baseline.snapshot.totalSizeBytes,
117
+ observedAt,
118
+ };
119
+ return {
120
+ status: 'available',
121
+ runtime: context.runtime,
122
+ scope,
123
+ attemptedPath,
124
+ observedAt,
125
+ evidence,
126
+ };
127
+ };
128
+ return {
129
+ runtime: context.runtime,
130
+ observe,
131
+ async probe(observedAt = Date.now()) {
132
+ return (await observe(observedAt)).evidence ?? null;
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,31 @@
1
+ export interface GateCliLaunch {
2
+ command: string;
3
+ args: string[];
4
+ cwd?: string;
5
+ }
6
+ export interface BranchLocalGateCli {
7
+ cliPath: string;
8
+ cliRoot: string;
9
+ routeReceiptPath: string;
10
+ shimRoot: string;
11
+ }
12
+ export interface ValidatedGateRouteEnvelope {
13
+ route: 'inline' | 'delegate-sync' | 'refuse';
14
+ reason: string;
15
+ cliRoot: string;
16
+ }
17
+ export interface GateRouteReceipt extends ValidatedGateRouteEnvelope {
18
+ runtime: string;
19
+ }
20
+ export declare function currentGateCliRoot(): string;
21
+ export declare function currentGateCliLaunch(): GateCliLaunch;
22
+ export declare function createBranchLocalGateCli(input: {
23
+ runId: string;
24
+ launch: GateCliLaunch;
25
+ cliRoot?: string;
26
+ tempRoot?: string;
27
+ }): Promise<BranchLocalGateCli>;
28
+ export declare function removeBranchLocalGateCli(shim: BranchLocalGateCli): Promise<void>;
29
+ export declare function validateGateRouteEnvelope(output: string, expectedCliRoot: string): ValidatedGateRouteEnvelope;
30
+ export declare function readGateRouteReceipt(path: string, expectedCliRoot: string, expectedRuntime: string): Promise<GateRouteReceipt>;
31
+ //# sourceMappingURL=branch-local-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-local-cli.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/branch-local-cli.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,QAAQ,GAAG,eAAe,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAiB,SAAQ,0BAA0B;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAUpD;AA4CD,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+B9B;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,0BAA0B,CA2B5B;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAU3B"}
@@ -0,0 +1,116 @@
1
+ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ export function currentGateCliRoot() {
7
+ return resolve(dirname(fileURLToPath(import.meta.url)), '../../../../..');
8
+ }
9
+ export function currentGateCliLaunch() {
10
+ const entrypoint = process.argv[1];
11
+ if (!entrypoint) {
12
+ throw new Error('Unable to resolve the running OAT CLI entrypoint.');
13
+ }
14
+ return {
15
+ command: process.execPath,
16
+ args: [...resolveCurrentLoaderArgs(process.execArgv), entrypoint],
17
+ cwd: process.cwd(),
18
+ };
19
+ }
20
+ function resolveCurrentLoaderArgs(args) {
21
+ const resolved = [];
22
+ for (let index = 0; index < args.length; index += 1) {
23
+ const argument = args[index];
24
+ if (argument === '--import' || argument === '--require') {
25
+ const specifier = args[index + 1];
26
+ resolved.push(argument);
27
+ if (specifier) {
28
+ resolved.push(resolveLoaderSpecifier(argument, specifier));
29
+ index += 1;
30
+ }
31
+ continue;
32
+ }
33
+ const inline = argument.match(/^(--import|--require)=(.+)$/);
34
+ if (inline) {
35
+ resolved.push(`${inline[1]}=${resolveLoaderSpecifier(inline[1], inline[2])}`);
36
+ continue;
37
+ }
38
+ resolved.push(argument);
39
+ }
40
+ return resolved;
41
+ }
42
+ function resolveLoaderSpecifier(flag, specifier) {
43
+ if (specifier.startsWith('/') ||
44
+ specifier.startsWith('.') ||
45
+ specifier.startsWith('file:')) {
46
+ return specifier;
47
+ }
48
+ try {
49
+ return flag === '--require'
50
+ ? createRequire(import.meta.url).resolve(specifier)
51
+ : import.meta.resolve(specifier);
52
+ }
53
+ catch {
54
+ return specifier;
55
+ }
56
+ }
57
+ export async function createBranchLocalGateCli(input) {
58
+ const cliRoot = input.cliRoot ?? currentGateCliRoot();
59
+ const shimRoot = join(input.tempRoot ?? tmpdir(), 'oat-gate-runs', input.runId);
60
+ const cliPath = join(shimRoot, 'bin', 'oat');
61
+ const routeReceiptPath = join(shimRoot, 'route-receipt.json');
62
+ await mkdir(dirname(cliPath), { recursive: true });
63
+ await writeFile(cliPath, [
64
+ '#!/usr/bin/env node',
65
+ "import { spawnSync } from 'node:child_process';",
66
+ `const command = ${JSON.stringify(input.launch.command)};`,
67
+ `const args = ${JSON.stringify(input.launch.args)};`,
68
+ 'const result = spawnSync(command, [...args, ...process.argv.slice(2)], {',
69
+ " stdio: 'inherit',",
70
+ ' env: process.env,',
71
+ ` cwd: ${JSON.stringify(input.launch.cwd)},`,
72
+ '});',
73
+ 'if (result.error) {',
74
+ ' process.stderr.write(`${result.error.message}\\n`);',
75
+ '}',
76
+ 'process.exit(result.status ?? 1);',
77
+ '',
78
+ ].join('\n'));
79
+ await chmod(cliPath, 0o700);
80
+ return { cliPath, cliRoot, routeReceiptPath, shimRoot };
81
+ }
82
+ export async function removeBranchLocalGateCli(shim) {
83
+ await rm(shim.shimRoot, { recursive: true, force: true });
84
+ }
85
+ export function validateGateRouteEnvelope(output, expectedCliRoot) {
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(output);
89
+ }
90
+ catch (error) {
91
+ throw new Error('Branch-local gate route did not return JSON.', {
92
+ cause: error,
93
+ });
94
+ }
95
+ if (!parsed || typeof parsed !== 'object') {
96
+ throw new Error('Branch-local gate route returned no decision envelope.');
97
+ }
98
+ const envelope = parsed;
99
+ if (!['inline', 'delegate-sync', 'refuse'].includes(String(envelope.route)) ||
100
+ typeof envelope.reason !== 'string') {
101
+ throw new Error('Branch-local gate route returned help or an invalid decision envelope.');
102
+ }
103
+ if (envelope.cliRoot !== expectedCliRoot) {
104
+ throw new Error(`Branch-local gate route resolved outside the expected checkout (${String(envelope.cliRoot)} != ${expectedCliRoot}).`);
105
+ }
106
+ return envelope;
107
+ }
108
+ export async function readGateRouteReceipt(path, expectedCliRoot, expectedRuntime) {
109
+ const output = await readFile(path, 'utf8').catch(() => '');
110
+ const envelope = validateGateRouteEnvelope(output, expectedCliRoot);
111
+ const parsed = JSON.parse(output);
112
+ if (parsed.runtime !== expectedRuntime) {
113
+ throw new Error(`Branch-local gate route receipt runtime did not match (${String(parsed.runtime)} != ${expectedRuntime}).`);
114
+ }
115
+ return { ...envelope, runtime: expectedRuntime };
116
+ }