@bookedsolid/rea 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/SECURITY.md +104 -0
  4. package/THREAT_MODEL.md +245 -0
  5. package/agents/accessibility-engineer.md +101 -0
  6. package/agents/backend-engineer.md +126 -0
  7. package/agents/code-reviewer.md +144 -0
  8. package/agents/codex-adversarial.md +107 -0
  9. package/agents/frontend-specialist.md +84 -0
  10. package/agents/qa-engineer.md +138 -0
  11. package/agents/rea-orchestrator.md +101 -0
  12. package/agents/security-engineer.md +108 -0
  13. package/agents/technical-writer.md +140 -0
  14. package/agents/typescript-specialist.md +111 -0
  15. package/commands/codex-review.md +104 -0
  16. package/commands/freeze.md +81 -0
  17. package/commands/halt-check.md +120 -0
  18. package/commands/rea.md +52 -0
  19. package/commands/review.md +79 -0
  20. package/dist/cli/check.d.ts +1 -0
  21. package/dist/cli/check.js +66 -0
  22. package/dist/cli/doctor.d.ts +1 -0
  23. package/dist/cli/doctor.js +93 -0
  24. package/dist/cli/freeze.d.ts +8 -0
  25. package/dist/cli/freeze.js +61 -0
  26. package/dist/cli/index.d.ts +2 -0
  27. package/dist/cli/index.js +65 -0
  28. package/dist/cli/init.d.ts +6 -0
  29. package/dist/cli/init.js +237 -0
  30. package/dist/cli/serve.d.ts +1 -0
  31. package/dist/cli/serve.js +19 -0
  32. package/dist/cli/utils.d.ts +23 -0
  33. package/dist/cli/utils.js +51 -0
  34. package/dist/config/tier-map.d.ts +11 -0
  35. package/dist/config/tier-map.js +108 -0
  36. package/dist/config/types.d.ts +24 -0
  37. package/dist/config/types.js +1 -0
  38. package/dist/gateway/circuit-breaker.d.ts +43 -0
  39. package/dist/gateway/circuit-breaker.js +86 -0
  40. package/dist/gateway/middleware/audit-types.d.ts +16 -0
  41. package/dist/gateway/middleware/audit-types.js +1 -0
  42. package/dist/gateway/middleware/audit.d.ts +12 -0
  43. package/dist/gateway/middleware/audit.js +98 -0
  44. package/dist/gateway/middleware/blocked-paths.d.ts +12 -0
  45. package/dist/gateway/middleware/blocked-paths.js +117 -0
  46. package/dist/gateway/middleware/chain.d.ts +28 -0
  47. package/dist/gateway/middleware/chain.js +40 -0
  48. package/dist/gateway/middleware/circuit-breaker.d.ts +11 -0
  49. package/dist/gateway/middleware/circuit-breaker.js +43 -0
  50. package/dist/gateway/middleware/injection.d.ts +22 -0
  51. package/dist/gateway/middleware/injection.js +128 -0
  52. package/dist/gateway/middleware/kill-switch.d.ts +10 -0
  53. package/dist/gateway/middleware/kill-switch.js +58 -0
  54. package/dist/gateway/middleware/policy.d.ts +12 -0
  55. package/dist/gateway/middleware/policy.js +70 -0
  56. package/dist/gateway/middleware/rate-limit.d.ts +12 -0
  57. package/dist/gateway/middleware/rate-limit.js +31 -0
  58. package/dist/gateway/middleware/redact.d.ts +16 -0
  59. package/dist/gateway/middleware/redact.js +128 -0
  60. package/dist/gateway/middleware/result-size-cap.d.ts +13 -0
  61. package/dist/gateway/middleware/result-size-cap.js +48 -0
  62. package/dist/gateway/middleware/session.d.ts +10 -0
  63. package/dist/gateway/middleware/session.js +18 -0
  64. package/dist/gateway/middleware/tier.d.ts +6 -0
  65. package/dist/gateway/middleware/tier.js +10 -0
  66. package/dist/gateway/rate-limiter.d.ts +36 -0
  67. package/dist/gateway/rate-limiter.js +75 -0
  68. package/dist/index.d.ts +3 -0
  69. package/dist/index.js +2 -0
  70. package/dist/policy/loader.d.ts +80 -0
  71. package/dist/policy/loader.js +146 -0
  72. package/dist/policy/types.d.ts +34 -0
  73. package/dist/policy/types.js +19 -0
  74. package/hooks/_lib/common.sh +105 -0
  75. package/hooks/_lib/halt-check.sh +39 -0
  76. package/hooks/_lib/policy-read.sh +79 -0
  77. package/hooks/architecture-review-gate.sh +84 -0
  78. package/hooks/attribution-advisory.sh +126 -0
  79. package/hooks/blocked-paths-enforcer.sh +176 -0
  80. package/hooks/changeset-security-gate.sh +143 -0
  81. package/hooks/commit-review-gate.sh +166 -0
  82. package/hooks/dangerous-bash-interceptor.sh +362 -0
  83. package/hooks/dependency-audit-gate.sh +118 -0
  84. package/hooks/env-file-protection.sh +110 -0
  85. package/hooks/pr-issue-link-gate.sh +65 -0
  86. package/hooks/push-review-gate.sh +120 -0
  87. package/hooks/secret-scanner.sh +229 -0
  88. package/hooks/security-disclosure-gate.sh +146 -0
  89. package/hooks/settings-protection.sh +147 -0
  90. package/package.json +93 -0
@@ -0,0 +1,93 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { loadPolicy } from '../policy/loader.js';
4
+ import { POLICY_FILE, REA_DIR, REGISTRY_FILE, log, reaPath } from './utils.js';
5
+ function checkFileExists(label, filePath, fatal) {
6
+ const exists = fs.existsSync(filePath);
7
+ if (exists)
8
+ return { label, status: 'pass' };
9
+ return { label, status: fatal ? 'fail' : 'warn', detail: `missing: ${filePath}` };
10
+ }
11
+ function checkPolicyParses(baseDir, policyPath) {
12
+ if (!fs.existsSync(policyPath)) {
13
+ return {
14
+ label: 'policy.yaml parses',
15
+ status: 'fail',
16
+ detail: `missing: ${policyPath} — run \`npx rea init\``,
17
+ };
18
+ }
19
+ try {
20
+ const policy = loadPolicy(baseDir);
21
+ return {
22
+ label: 'policy.yaml parses',
23
+ status: 'pass',
24
+ detail: `profile=${policy.profile}, autonomy=${policy.autonomy_level}`,
25
+ };
26
+ }
27
+ catch (e) {
28
+ return {
29
+ label: 'policy.yaml parses',
30
+ status: 'fail',
31
+ detail: e instanceof Error ? e.message : String(e),
32
+ };
33
+ }
34
+ }
35
+ function checkCodexPlugin(baseDir) {
36
+ const commandsDir = path.join(baseDir, '.claude', 'commands');
37
+ if (!fs.existsSync(commandsDir)) {
38
+ return {
39
+ label: 'Codex plugin command(s)',
40
+ status: 'warn',
41
+ detail: 'no .claude/commands/ directory — Codex adversarial review not wired',
42
+ };
43
+ }
44
+ const entries = fs.readdirSync(commandsDir);
45
+ const codexEntry = entries.find((name) => name.toLowerCase().startsWith('codex'));
46
+ if (codexEntry !== undefined) {
47
+ return { label: 'Codex plugin command(s)', status: 'pass', detail: codexEntry };
48
+ }
49
+ return {
50
+ label: 'Codex plugin command(s)',
51
+ status: 'warn',
52
+ detail: 'no .claude/commands/codex* found — /codex-review will not be available',
53
+ };
54
+ }
55
+ function formatSymbol(status) {
56
+ if (status === 'pass')
57
+ return '[ok] ';
58
+ if (status === 'warn')
59
+ return '[warn]';
60
+ return '[fail]';
61
+ }
62
+ export function runDoctor() {
63
+ const baseDir = process.cwd();
64
+ const policyPath = reaPath(baseDir, POLICY_FILE);
65
+ const registryPath = reaPath(baseDir, REGISTRY_FILE);
66
+ const reaDir = path.join(baseDir, REA_DIR);
67
+ const commitMsgHook = path.join(baseDir, '.git', 'hooks', 'commit-msg');
68
+ const checks = [
69
+ checkFileExists('.rea/ directory exists', reaDir, true),
70
+ checkPolicyParses(baseDir, policyPath),
71
+ checkFileExists('.rea/registry.yaml exists', registryPath, false),
72
+ checkFileExists('.git/hooks/commit-msg installed', commitMsgHook, false),
73
+ checkCodexPlugin(baseDir),
74
+ ];
75
+ console.log('');
76
+ log(`Doctor — ${baseDir}`);
77
+ console.log('');
78
+ let hardFail = false;
79
+ for (const c of checks) {
80
+ const detail = c.detail !== undefined ? ` (${c.detail})` : '';
81
+ console.log(` ${formatSymbol(c.status)} ${c.label}${detail}`);
82
+ if (c.status === 'fail')
83
+ hardFail = true;
84
+ }
85
+ console.log('');
86
+ if (hardFail) {
87
+ log('Doctor: one or more hard checks failed.');
88
+ console.log('');
89
+ process.exit(1);
90
+ }
91
+ log('Doctor: OK (warnings do not fail the check).');
92
+ console.log('');
93
+ }
@@ -0,0 +1,8 @@
1
+ export interface FreezeOptions {
2
+ reason?: string | undefined;
3
+ }
4
+ export interface UnfreezeOptions {
5
+ yes?: boolean | undefined;
6
+ }
7
+ export declare function runFreeze(options: FreezeOptions): void;
8
+ export declare function runUnfreeze(options: UnfreezeOptions): Promise<void>;
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as p from '@clack/prompts';
4
+ import { HALT_FILE, REA_DIR, err, log, reaPath } from './utils.js';
5
+ /**
6
+ * Strip control characters (terminal escape injection defense).
7
+ */
8
+ function sanitize(input) {
9
+ return input.replace(/[\x00-\x1f\x7f]/g, '').trim();
10
+ }
11
+ export function runFreeze(options) {
12
+ const reason = options.reason !== undefined ? sanitize(options.reason) : '';
13
+ if (!reason) {
14
+ err('`rea freeze` requires `--reason "..."`');
15
+ console.error('');
16
+ console.error(' Example: rea freeze --reason "security incident — pausing agent work"');
17
+ console.error('');
18
+ process.exit(1);
19
+ }
20
+ const targetDir = process.cwd();
21
+ const reaDir = path.join(targetDir, REA_DIR);
22
+ const haltFile = reaPath(targetDir, HALT_FILE);
23
+ if (!fs.existsSync(reaDir)) {
24
+ fs.mkdirSync(reaDir, { recursive: true });
25
+ }
26
+ const timestamp = new Date().toISOString();
27
+ const content = `${reason} (frozen at ${timestamp})\n`;
28
+ fs.writeFileSync(haltFile, content, 'utf8');
29
+ console.log('');
30
+ log('REA FROZEN');
31
+ console.log(` Reason: ${reason}`);
32
+ console.log(` File: .rea/HALT`);
33
+ console.log(` Effect: all middleware + PreToolUse hooks will block agent operations.`);
34
+ console.log('');
35
+ console.log(' To resume: rea unfreeze');
36
+ console.log('');
37
+ }
38
+ export async function runUnfreeze(options) {
39
+ const targetDir = process.cwd();
40
+ const haltFile = reaPath(targetDir, HALT_FILE);
41
+ if (!fs.existsSync(haltFile)) {
42
+ log('Not frozen — no .rea/HALT file found.');
43
+ return;
44
+ }
45
+ const existingReason = fs.readFileSync(haltFile, 'utf8').trim();
46
+ if (options.yes !== true) {
47
+ const confirmed = await p.confirm({
48
+ message: `Remove .rea/HALT and resume agent operations?\n Current freeze: ${existingReason}`,
49
+ initialValue: false,
50
+ });
51
+ if (p.isCancel(confirmed) || confirmed !== true) {
52
+ log('Unfreeze cancelled — HALT remains in place.');
53
+ return;
54
+ }
55
+ }
56
+ fs.unlinkSync(haltFile);
57
+ console.log('');
58
+ log('REA UNFROZEN');
59
+ console.log(' .rea/HALT removed — agent operations resumed.');
60
+ console.log('');
61
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { runCheck } from './check.js';
4
+ import { runDoctor } from './doctor.js';
5
+ import { runFreeze, runUnfreeze } from './freeze.js';
6
+ import { runInit } from './init.js';
7
+ import { runServe } from './serve.js';
8
+ import { err, getPkgVersion } from './utils.js';
9
+ async function main() {
10
+ const program = new Command();
11
+ program
12
+ .name('rea')
13
+ .description('Agentic governance layer for Claude Code — policy, hooks, middleware, audit.')
14
+ .version(getPkgVersion(), '-v, --version', 'print rea version');
15
+ program
16
+ .command('init')
17
+ .description('Interactive wizard — write .rea/policy.yaml and .rea/registry.yaml')
18
+ .option('-y, --yes', 'non-interactive mode — accept defaults for all prompts')
19
+ .option('--from-reagent', 'migrate from a .reagent/ directory if present')
20
+ .option('--profile <name>', 'profile: minimal | client-engagement | bst-internal | lit-wc | open-source')
21
+ .action(async (opts) => {
22
+ await runInit({
23
+ yes: opts.yes,
24
+ fromReagent: opts.fromReagent,
25
+ profile: opts.profile,
26
+ });
27
+ });
28
+ program
29
+ .command('serve')
30
+ .description('Start the MCP gateway (stub — prints status, verifies policy loads).')
31
+ .action(async () => {
32
+ await runServe();
33
+ });
34
+ program
35
+ .command('freeze')
36
+ .description('Write .rea/HALT to block agent operations. Requires --reason.')
37
+ .requiredOption('--reason <text>', 'why you are freezing (stored in .rea/HALT)')
38
+ .action((opts) => {
39
+ runFreeze({ reason: opts.reason });
40
+ });
41
+ program
42
+ .command('unfreeze')
43
+ .description('Remove .rea/HALT. Confirms interactively unless --yes is set.')
44
+ .option('-y, --yes', 'skip confirmation prompt')
45
+ .action(async (opts) => {
46
+ await runUnfreeze({ yes: opts.yes });
47
+ });
48
+ program
49
+ .command('check')
50
+ .description('Read-only status — autonomy, HALT, profile, recent audit entries.')
51
+ .action(() => {
52
+ runCheck();
53
+ });
54
+ program
55
+ .command('doctor')
56
+ .description('Validate the install: policy parses, .rea/ layout, hooks, Codex plugin.')
57
+ .action(() => {
58
+ runDoctor();
59
+ });
60
+ await program.parseAsync(process.argv);
61
+ }
62
+ main().catch((e) => {
63
+ err(e instanceof Error ? e.message : String(e));
64
+ process.exit(1);
65
+ });
@@ -0,0 +1,6 @@
1
+ export interface InitOptions {
2
+ yes?: boolean | undefined;
3
+ fromReagent?: boolean | undefined;
4
+ profile?: string | undefined;
5
+ }
6
+ export declare function runInit(options: InitOptions): Promise<void>;
@@ -0,0 +1,237 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import * as p from '@clack/prompts';
5
+ import { AutonomyLevel } from '../policy/types.js';
6
+ import { POLICY_FILE, REA_DIR, REGISTRY_FILE, err, getPkgVersion, log, } from './utils.js';
7
+ const PROFILES = [
8
+ 'minimal',
9
+ 'client-engagement',
10
+ 'bst-internal',
11
+ 'lit-wc',
12
+ 'open-source',
13
+ ];
14
+ const AUTONOMY_LEVELS = [
15
+ AutonomyLevel.L0,
16
+ AutonomyLevel.L1,
17
+ AutonomyLevel.L2,
18
+ AutonomyLevel.L3,
19
+ ];
20
+ function detectReagentPolicy(targetDir) {
21
+ const reagentPolicy = path.join(targetDir, '.reagent', 'policy.yaml');
22
+ return fs.existsSync(reagentPolicy) ? reagentPolicy : null;
23
+ }
24
+ function detectProjectName(targetDir) {
25
+ const pkgPath = path.join(targetDir, 'package.json');
26
+ if (fs.existsSync(pkgPath)) {
27
+ try {
28
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
29
+ if (typeof pkg.name === 'string' && pkg.name.length > 0)
30
+ return pkg.name;
31
+ }
32
+ catch {
33
+ // fall through
34
+ }
35
+ }
36
+ return path.basename(targetDir);
37
+ }
38
+ function levelRank(level) {
39
+ return { L0: 0, L1: 1, L2: 2, L3: 3 }[level];
40
+ }
41
+ function isValidProfile(value) {
42
+ return PROFILES.includes(value);
43
+ }
44
+ function cancel(message) {
45
+ p.cancel(message);
46
+ process.exit(0);
47
+ }
48
+ async function runWizard(options, targetDir, reagentPolicyPath) {
49
+ const projectName = detectProjectName(targetDir);
50
+ p.intro(`rea init — ${projectName}`);
51
+ let fromReagent = options.fromReagent === true;
52
+ if (!fromReagent && reagentPolicyPath !== null) {
53
+ const migrate = await p.confirm({
54
+ message: `Found ${path.relative(targetDir, reagentPolicyPath)} — migrate from reagent?`,
55
+ initialValue: true,
56
+ });
57
+ if (p.isCancel(migrate))
58
+ cancel('Init cancelled.');
59
+ fromReagent = migrate === true;
60
+ }
61
+ // Profile selection
62
+ let profile;
63
+ if (options.profile !== undefined) {
64
+ if (!isValidProfile(options.profile)) {
65
+ p.cancel(`Unknown profile: "${options.profile}". Valid: ${PROFILES.join(', ')}`);
66
+ process.exit(1);
67
+ }
68
+ profile = options.profile;
69
+ p.log.info(`Profile: ${profile} (from --profile)`);
70
+ }
71
+ else {
72
+ const picked = await p.select({
73
+ message: 'Pick a profile',
74
+ initialValue: 'minimal',
75
+ options: [
76
+ { value: 'minimal', label: 'minimal', hint: 'bare policy, no extras (default)' },
77
+ {
78
+ value: 'client-engagement',
79
+ label: 'client-engagement',
80
+ hint: 'zero-trust client project',
81
+ },
82
+ { value: 'bst-internal', label: 'bst-internal', hint: 'internal BST projects' },
83
+ { value: 'lit-wc', label: 'lit-wc', hint: 'Lit / web component libraries' },
84
+ { value: 'open-source', label: 'open-source', hint: 'public OSS repos' },
85
+ ],
86
+ });
87
+ if (p.isCancel(picked))
88
+ cancel('Init cancelled.');
89
+ profile = picked;
90
+ }
91
+ // Autonomy level
92
+ const autonomyPick = await p.select({
93
+ message: 'Starting autonomy_level',
94
+ initialValue: AutonomyLevel.L1,
95
+ options: [
96
+ { value: AutonomyLevel.L0, label: 'L0', hint: 'read-only; every write needs approval' },
97
+ { value: AutonomyLevel.L1, label: 'L1', hint: 'default — writes allowed, destructive gated' },
98
+ { value: AutonomyLevel.L2, label: 'L2', hint: 'wider latitude; destructive ops allowed' },
99
+ { value: AutonomyLevel.L3, label: 'L3', hint: 'full autonomy (rare — supervised only)' },
100
+ ],
101
+ });
102
+ if (p.isCancel(autonomyPick))
103
+ cancel('Init cancelled.');
104
+ const autonomyLevel = autonomyPick;
105
+ // Max autonomy ceiling — constrain to levels >= autonomy
106
+ const maxCandidates = AUTONOMY_LEVELS.filter((lvl) => levelRank(lvl) >= levelRank(autonomyLevel));
107
+ const defaultMax = maxCandidates.find((l) => l === AutonomyLevel.L2) ?? autonomyLevel;
108
+ const maxOptions = maxCandidates.map((lvl) => {
109
+ if (lvl === autonomyLevel) {
110
+ return { value: lvl, label: lvl, hint: 'same as starting level' };
111
+ }
112
+ return { value: lvl, label: lvl };
113
+ });
114
+ const maxPick = await p.select({
115
+ message: 'max_autonomy_level (ceiling — cannot be exceeded at runtime)',
116
+ initialValue: defaultMax,
117
+ // Cast: clack's Option type is a discriminated union over the literal values,
118
+ // but here we build it dynamically from the AutonomyLevel enum.
119
+ options: maxOptions,
120
+ });
121
+ if (p.isCancel(maxPick))
122
+ cancel('Init cancelled.');
123
+ const maxAutonomyLevel = maxPick;
124
+ // block_ai_attribution
125
+ const attribPick = await p.confirm({
126
+ message: 'Enforce block_ai_attribution (reject AI-authored commit trailers)?',
127
+ initialValue: true,
128
+ });
129
+ if (p.isCancel(attribPick))
130
+ cancel('Init cancelled.');
131
+ const blockAiAttribution = attribPick === true;
132
+ p.outro('Config collected — writing files.');
133
+ return {
134
+ profile,
135
+ autonomyLevel,
136
+ maxAutonomyLevel,
137
+ blockAiAttribution,
138
+ fromReagent,
139
+ reagentPolicyPath,
140
+ };
141
+ }
142
+ function writePolicyYaml(targetDir, config) {
143
+ const policyPath = path.join(targetDir, REA_DIR, POLICY_FILE);
144
+ const installedBy = process.env.USER ?? os.userInfo().username ?? 'unknown';
145
+ const installedAt = new Date().toISOString();
146
+ const lines = [
147
+ `# .rea/policy.yaml — managed by rea v${getPkgVersion()}`,
148
+ `# Edit carefully: tightening takes effect on next load; loosening requires human approval.`,
149
+ `version: "1"`,
150
+ `profile: ${JSON.stringify(config.profile)}`,
151
+ `installed_by: ${JSON.stringify(installedBy)}`,
152
+ `installed_at: ${JSON.stringify(installedAt)}`,
153
+ `autonomy_level: ${config.autonomyLevel}`,
154
+ `max_autonomy_level: ${config.maxAutonomyLevel}`,
155
+ `promotion_requires_human_approval: true`,
156
+ `block_ai_attribution: ${config.blockAiAttribution ? 'true' : 'false'}`,
157
+ `blocked_paths:`,
158
+ ` - ".env"`,
159
+ ` - ".env.*"`,
160
+ `notification_channel: ""`,
161
+ ``,
162
+ ];
163
+ fs.writeFileSync(policyPath, lines.join('\n'), 'utf8');
164
+ return policyPath;
165
+ }
166
+ function writeRegistryYaml(targetDir) {
167
+ const registryPath = path.join(targetDir, REA_DIR, REGISTRY_FILE);
168
+ const content = [
169
+ `# .rea/registry.yaml — downstream MCP servers proxied through rea serve.`,
170
+ `# Every entry below is subject to the same middleware chain as native tool calls.`,
171
+ `version: "1"`,
172
+ `servers: []`,
173
+ ``,
174
+ ].join('\n');
175
+ fs.writeFileSync(registryPath, content, 'utf8');
176
+ return registryPath;
177
+ }
178
+ export async function runInit(options) {
179
+ const targetDir = process.cwd();
180
+ const reagentPolicyPath = detectReagentPolicy(targetDir);
181
+ const reaDir = path.join(targetDir, REA_DIR);
182
+ const policyPath = path.join(reaDir, POLICY_FILE);
183
+ if (fs.existsSync(policyPath) && options.yes !== true) {
184
+ err(`.rea/policy.yaml already exists at ${policyPath}`);
185
+ console.error('');
186
+ console.error(' Refusing to overwrite. Pass --yes to force, or remove the file first.');
187
+ console.error('');
188
+ process.exit(1);
189
+ }
190
+ let config;
191
+ if (options.yes === true) {
192
+ const profile = options.profile !== undefined && isValidProfile(options.profile)
193
+ ? options.profile
194
+ : 'minimal';
195
+ config = {
196
+ profile,
197
+ autonomyLevel: AutonomyLevel.L1,
198
+ maxAutonomyLevel: AutonomyLevel.L2,
199
+ blockAiAttribution: true,
200
+ fromReagent: options.fromReagent === true,
201
+ reagentPolicyPath,
202
+ };
203
+ log(`Non-interactive init: profile=${profile}, autonomy=L1, max=L2, attribution-block=true`);
204
+ }
205
+ else {
206
+ config = await runWizard(options, targetDir, reagentPolicyPath);
207
+ }
208
+ if (!fs.existsSync(reaDir)) {
209
+ fs.mkdirSync(reaDir, { recursive: true });
210
+ }
211
+ const written = [];
212
+ written.push(writePolicyYaml(targetDir, config));
213
+ written.push(writeRegistryYaml(targetDir));
214
+ // TODO: copy hooks/commands/agents once templates directory ships
215
+ // TODO: merge .claude/settings.json once hook registration is defined
216
+ // TODO: install .husky/commit-msg + .git/hooks/commit-msg for block_ai_attribution
217
+ console.log('');
218
+ log('init complete');
219
+ for (const file of written) {
220
+ console.log(` + ${path.relative(targetDir, file)}`);
221
+ }
222
+ console.log('');
223
+ console.log('Next steps:');
224
+ console.log(' 1. Review .rea/policy.yaml and commit it.');
225
+ console.log(' 2. Run `rea doctor` to validate the install.');
226
+ console.log(' 3. Run `rea check` to see current status.');
227
+ if (config.fromReagent) {
228
+ console.log('');
229
+ console.log('Reagent migration:');
230
+ console.log(` Source: ${config.reagentPolicyPath ?? '(none)'}`);
231
+ console.log(' Field-for-field translation is not yet automated — review both files manually.');
232
+ console.log(' Once satisfied, you can remove the .reagent/ directory.');
233
+ }
234
+ console.log('');
235
+ console.log(' Hooks, slash commands, and agents are not installed yet — coming in a follow-up.');
236
+ console.log('');
237
+ }
@@ -0,0 +1 @@
1
+ export declare function runServe(): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { loadPolicy } from '../policy/loader.js';
2
+ import { POLICY_FILE, err, exitWithMissingPolicy, log, reaPath } from './utils.js';
3
+ export async function runServe() {
4
+ const baseDir = process.cwd();
5
+ const policyPath = reaPath(baseDir, POLICY_FILE);
6
+ try {
7
+ const policy = loadPolicy(baseDir);
8
+ log(`MCP gateway not yet implemented — install complete, policy loaded (profile=${policy.profile}, autonomy=${policy.autonomy_level}).`);
9
+ process.exit(0);
10
+ }
11
+ catch (e) {
12
+ const message = e instanceof Error ? e.message : String(e);
13
+ if (message.includes('not found')) {
14
+ exitWithMissingPolicy(policyPath);
15
+ }
16
+ err(`Failed to load policy: ${message}`);
17
+ process.exit(1);
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Package root, resolved from the compiled CLI module location.
3
+ * Compiled path: dist/cli/utils.js → package root is two levels up.
4
+ */
5
+ export declare const PKG_ROOT: string;
6
+ export declare function getPkgVersion(): string;
7
+ export declare const REA_DIR = ".rea";
8
+ export declare const POLICY_FILE = "policy.yaml";
9
+ export declare const REGISTRY_FILE = "registry.yaml";
10
+ export declare const HALT_FILE = "HALT";
11
+ export declare const AUDIT_FILE = "audit.jsonl";
12
+ export declare function reaPath(baseDir: string, ...segments: string[]): string;
13
+ /**
14
+ * Standard log prefix so users notice the transition from reagent → rea.
15
+ */
16
+ export declare function log(message: string): void;
17
+ export declare function warn(message: string): void;
18
+ export declare function err(message: string): void;
19
+ /**
20
+ * Print a "policy missing — run init" message and exit 1.
21
+ * Used by commands that require a policy file to operate.
22
+ */
23
+ export declare function exitWithMissingPolicy(policyPath: string): never;
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ /**
7
+ * Package root, resolved from the compiled CLI module location.
8
+ * Compiled path: dist/cli/utils.js → package root is two levels up.
9
+ */
10
+ export const PKG_ROOT = path.resolve(__dirname, '..', '..');
11
+ export function getPkgVersion() {
12
+ try {
13
+ const pkgPath = path.join(PKG_ROOT, 'package.json');
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
15
+ return pkg.version ?? '0.0.0';
16
+ }
17
+ catch {
18
+ return '0.0.0';
19
+ }
20
+ }
21
+ export const REA_DIR = '.rea';
22
+ export const POLICY_FILE = 'policy.yaml';
23
+ export const REGISTRY_FILE = 'registry.yaml';
24
+ export const HALT_FILE = 'HALT';
25
+ export const AUDIT_FILE = 'audit.jsonl';
26
+ export function reaPath(baseDir, ...segments) {
27
+ return path.join(baseDir, REA_DIR, ...segments);
28
+ }
29
+ /**
30
+ * Standard log prefix so users notice the transition from reagent → rea.
31
+ */
32
+ export function log(message) {
33
+ console.log(`[rea] ${message}`);
34
+ }
35
+ export function warn(message) {
36
+ console.warn(`[rea] WARN: ${message}`);
37
+ }
38
+ export function err(message) {
39
+ console.error(`[rea] ERROR: ${message}`);
40
+ }
41
+ /**
42
+ * Print a "policy missing — run init" message and exit 1.
43
+ * Used by commands that require a policy file to operate.
44
+ */
45
+ export function exitWithMissingPolicy(policyPath) {
46
+ err(`Policy file not found: ${policyPath}`);
47
+ console.error('');
48
+ console.error(' Run `npx rea init` to initialize REA in this directory.');
49
+ console.error('');
50
+ process.exit(1);
51
+ }
@@ -0,0 +1,11 @@
1
+ import { Tier } from '../policy/types.js';
2
+ import type { GatewayConfig } from './types.js';
3
+ /**
4
+ * Classify a tool by its tier. Checks gateway config overrides first,
5
+ * then static map, then naming conventions, then defaults to Write.
6
+ */
7
+ export declare function classifyTool(toolName: string, serverName: string, gatewayConfig?: GatewayConfig): Tier;
8
+ /**
9
+ * Check if a tool is explicitly blocked in gateway config.
10
+ */
11
+ export declare function isToolBlocked(toolName: string, serverName: string, gatewayConfig?: GatewayConfig): boolean;