reflexes 0.0.0-stage → 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.
package/package.json CHANGED
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "name": "reflexes",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Pre-action verification for AI agents. Read .reflex files and check rules before actions.",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "node test/index.test.js"
8
+ },
9
+ "keywords": [
10
+ "ai",
11
+ "reflex",
12
+ "verification",
13
+ "safety"
14
+ ],
15
+ "license": "MIT"
6
16
  }
package/src/index.js ADDED
@@ -0,0 +1,173 @@
1
+ // Reflex — Pre-action verification for AI agents
2
+ // Usage: const reflex = require('ai-reflex'); reflex.check(action, context);
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execSync } = require('child_process');
7
+
8
+ class Reflex {
9
+ constructor(options = {}) {
10
+ this.rules = [];
11
+ this.directory = options.directory || process.cwd();
12
+ this.globalPath = options.globalPath || path.join(process.env.HOME || '', '.reflex');
13
+ this.load();
14
+ }
15
+
16
+ load() {
17
+ this.rules = [];
18
+ // Load global
19
+ if (fs.existsSync(this.globalPath)) {
20
+ this.rules.push(...this.parse(fs.readFileSync(this.globalPath, 'utf-8')));
21
+ }
22
+ // Load project
23
+ const projectPath = path.join(this.directory, '.reflex');
24
+ if (fs.existsSync(projectPath)) {
25
+ this.rules.push(...this.parse(fs.readFileSync(projectPath, 'utf-8')));
26
+ }
27
+ // Load from .reflex.d
28
+ const dirPath = path.join(this.directory, '.reflex.d');
29
+ if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
30
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.reflex'));
31
+ for (const file of files) {
32
+ this.rules.push(...this.parse(fs.readFileSync(path.join(dirPath, file), 'utf-8')));
33
+ }
34
+ }
35
+ }
36
+
37
+ parse(content) {
38
+ const lines = content.split('\n');
39
+ const reflexes = [];
40
+ let current = null;
41
+ let currentKey = null;
42
+
43
+ for (const rawLine of lines) {
44
+ const line = rawLine.trimEnd();
45
+ if (!line.trim() || line.trim().startsWith('#')) continue;
46
+ const trimmed = line.trim();
47
+
48
+ if (trimmed.startsWith('reflex ')) {
49
+ if (current) reflexes.push(current);
50
+ current = { name: trimmed.slice(7).trim(), rules: {} };
51
+ currentKey = null;
52
+ continue;
53
+ }
54
+
55
+ if (current && /^\s+\w+:/.test(rawLine)) {
56
+ const colonIdx = trimmed.indexOf(':');
57
+ const key = trimmed.slice(0, colonIdx).trim();
58
+ const value = trimmed.slice(colonIdx + 1).trim();
59
+ current.rules[key] = value;
60
+ currentKey = key;
61
+ continue;
62
+ }
63
+
64
+ if (current && currentKey && /^\s+/.test(rawLine) && !trimmed.includes(':')) {
65
+ current.rules[currentKey] += ' ' + trimmed;
66
+ continue;
67
+ }
68
+ }
69
+
70
+ if (current) reflexes.push(current);
71
+ return reflexes;
72
+ }
73
+
74
+ check(context) {
75
+ const results = [];
76
+ for (const reflex of this.rules) {
77
+ const r = reflex.rules;
78
+ const trigger = r.trigger;
79
+ const check = r.check;
80
+ const condition = r.condition;
81
+ const onPass = r.on_pass || 'allow';
82
+ const onFail = r.on_fail || 'allow';
83
+ const message = r.message || '';
84
+
85
+ // Check trigger
86
+ if (trigger && trigger !== context.triggerType && trigger !== 'always') {
87
+ continue;
88
+ }
89
+
90
+ // Check condition
91
+ if (condition) {
92
+ const parts = condition.split(/\s+/);
93
+ if (parts[1] === 'is' && context.action !== parts[2]) continue;
94
+ if (parts[1] === 'matches') {
95
+ const pattern = parts[2].replace(/\*/g, '.*');
96
+ if (!new RegExp('^' + pattern + '$').test(context.target || '')) continue;
97
+ }
98
+ }
99
+
100
+ // Run check
101
+ let checkResult = this.runCheck(check, context);
102
+
103
+ const action = checkResult ? onPass : onFail;
104
+ results.push({
105
+ name: reflex.name,
106
+ check,
107
+ result: checkResult,
108
+ action,
109
+ message: message.replace(/^"|"$/g, '')
110
+ });
111
+
112
+ if (action === 'block') break;
113
+ }
114
+ return results;
115
+ }
116
+
117
+ runCheck(check, context) {
118
+ switch (check) {
119
+ case 'file_exists': return context.fileExists || false;
120
+ case 'file_not_exists': return !context.fileExists;
121
+ case 'has_tests': return context.hasTests || false;
122
+ case 'tests_pass': return context.testsPass || false;
123
+ case 'git_clean': return context.gitClean || false;
124
+ case 'no_secrets': return !context.hasSecrets;
125
+ case 'rate_limit': return !context.rateLimitExceeded;
126
+ case 'backup_exists': return context.hasBackup || false;
127
+ case 'user_confirmed': return context.userConfirmed || false;
128
+ default:
129
+ if (check.startsWith('custom ')) {
130
+ const cmdMatch = check.match(/"([^"]*)"/);
131
+ if (cmdMatch) {
132
+ try {
133
+ execSync(cmdMatch[1], { stdio: 'pipe' });
134
+ return true;
135
+ } catch (e) {
136
+ return false;
137
+ }
138
+ }
139
+ }
140
+ return true;
141
+ }
142
+ }
143
+
144
+ // Convenience methods
145
+ beforeAction(action, target, extra = {}) {
146
+ return this.check({
147
+ triggerType: 'before_action',
148
+ action,
149
+ target,
150
+ ...extra
151
+ });
152
+ }
153
+
154
+ beforeCommit(extra = {}) {
155
+ return this.check({
156
+ triggerType: 'before_commit',
157
+ action: 'commit',
158
+ ...extra
159
+ });
160
+ }
161
+
162
+ beforeDeploy(extra = {}) {
163
+ return this.check({
164
+ triggerType: 'before_deploy',
165
+ action: 'deploy',
166
+ ...extra
167
+ });
168
+ }
169
+ }
170
+
171
+ module.exports = Reflex;
172
+ module.exports.Reflex = Reflex;
173
+ module.exports.default = Reflex;
@@ -0,0 +1,87 @@
1
+ const Reflex = require('../src/index.js');
2
+
3
+ let passed = 0;
4
+ let failed = 0;
5
+
6
+ function test(name, fn) {
7
+ try {
8
+ fn();
9
+ passed++;
10
+ console.log(` ✓ ${name}`);
11
+ } catch (e) {
12
+ failed++;
13
+ console.log(` ✗ ${name}: ${e.message}`);
14
+ }
15
+ }
16
+
17
+ function assert(condition, message) {
18
+ if (!condition) throw new Error(message || 'Assertion failed');
19
+ }
20
+
21
+ console.log('Reflex Tests');
22
+
23
+ const reflexContent = `
24
+ reflex prevent_overwrite
25
+ trigger: before_action
26
+ check: file_exists
27
+ condition: action is write_file
28
+ on_fail: allow
29
+ on_pass: ask_user
30
+ message: "File exists. Overwrite?"
31
+
32
+ reflex block_dangerous
33
+ trigger: before_action
34
+ check: custom "echo test"
35
+ on_pass: block
36
+ on_fail: allow
37
+ message: "Dangerous command blocked"
38
+
39
+ reflex require_tests
40
+ trigger: before_commit
41
+ check: tests_pass
42
+ on_pass: allow
43
+ on_fail: block
44
+ message: "Tests must pass"
45
+ `;
46
+
47
+ test('parse reflex file', () => {
48
+ const r = new Reflex({ directory: '/tmp' });
49
+ const parsed = r.parse(reflexContent);
50
+ assert(parsed.length === 3, `Expected 3 reflexes, got ${parsed.length}`);
51
+ assert(parsed[0].name === 'prevent_overwrite', 'Wrong name');
52
+ });
53
+
54
+ test('check file_exists when file exists', () => {
55
+ const r = new Reflex({ directory: '/tmp' });
56
+ r.rules = r.parse(reflexContent);
57
+ const results = r.beforeAction('write_file', 'app.js', { fileExists: true });
58
+ assert(results.length > 0, 'No reflex fired');
59
+ assert(results[0].action === 'ask_user', `Expected ask_user, got ${results[0].action}`);
60
+ });
61
+
62
+ test('check file_exists when file does not exist', () => {
63
+ const r = new Reflex({ directory: '/tmp' });
64
+ r.rules = r.parse(reflexContent);
65
+ const results = r.beforeAction('write_file', 'app.js', { fileExists: false });
66
+ const preventResult = results.find(r => r.name === 'prevent_overwrite');
67
+ assert(preventResult && preventResult.action === 'allow', 'Should allow when file does not exist');
68
+ });
69
+
70
+ test('beforeCommit triggers require_tests', () => {
71
+ const r = new Reflex({ directory: '/tmp' });
72
+ r.rules = r.parse(reflexContent);
73
+ const results = r.beforeCommit({ testsPass: false });
74
+ assert(results.length > 0, 'No reflex fired');
75
+ assert(results[0].action === 'block', `Expected block, got ${results[0].action}`);
76
+ });
77
+
78
+ test('beforeCommit passes when tests pass', () => {
79
+ const r = new Reflex({ directory: '/tmp' });
80
+ r.rules = r.parse(reflexContent);
81
+ const results = r.beforeCommit({ testsPass: true });
82
+ const reqResult = results.find(r => r.name === 'require_tests');
83
+ assert(reqResult && reqResult.action === 'allow', 'Should allow when tests pass');
84
+ });
85
+
86
+ console.log(`\n${passed} passed, ${failed} failed`);
87
+ process.exit(failed > 0 ? 1 : 0);
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.