erne-universal 0.10.2 → 0.10.4

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.
@@ -431,8 +431,13 @@ function handleContextApi(req, res, urlPath, body) {
431
431
  }
432
432
 
433
433
  try {
434
- // POST /api/context/execute — sandbox execution
434
+ // POST /api/context/execute — sandbox execution (env-gated)
435
435
  if (urlPath === '/api/context/execute' && req.method === 'POST') {
436
+ if (process.env.ERNE_ALLOW_EXECUTE !== 'true') {
437
+ res.writeHead(403, { 'Content-Type': 'application/json' });
438
+ res.end(JSON.stringify({ error: 'Execute endpoint disabled. Set ERNE_ALLOW_EXECUTE=true to enable.' }));
439
+ return;
440
+ }
436
441
  const { command, original_tool, timeout = 30000 } = JSON.parse(body);
437
442
  const cwd = process.env.ERNE_PROJECT_DIR || process.cwd();
438
443
  execFile('sh', ['-c', command], { cwd, timeout, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
@@ -601,8 +606,23 @@ const server = http.createServer(async (req, res) => {
601
606
  const urlPath = req.url.split('?')[0];
602
607
  if (urlPath.startsWith('/api/context/')) {
603
608
  let body = '';
604
- req.on('data', chunk => body += chunk);
605
- req.on('end', () => handleContextApi(req, res, urlPath, body));
609
+ let bodyBytes = 0;
610
+ req.on('data', chunk => {
611
+ bodyBytes += chunk.length;
612
+ if (bodyBytes > MAX_PAYLOAD_BYTES) {
613
+ req.destroy();
614
+ return;
615
+ }
616
+ body += chunk;
617
+ });
618
+ req.on('end', () => {
619
+ if (bodyBytes > MAX_PAYLOAD_BYTES) {
620
+ res.writeHead(413, { 'Content-Type': 'application/json' });
621
+ res.end(JSON.stringify({ error: 'Payload too large' }));
622
+ return;
623
+ }
624
+ handleContextApi(req, res, urlPath, body);
625
+ });
606
626
  return;
607
627
  }
608
628
 
package/lib/audit-cli.js CHANGED
@@ -295,7 +295,7 @@ function postDashboardEvent(data) {
295
295
 
296
296
  const req = http.request({
297
297
  hostname: 'localhost',
298
- port: 3333,
298
+ port: parseInt(process.env.ERNE_DASHBOARD_PORT || '3333', 10),
299
299
  path: '/api/events',
300
300
  method: 'POST',
301
301
  headers: {
package/lib/dashboard.js CHANGED
@@ -96,14 +96,14 @@ async function ensureHooksConfigured() {
96
96
  event: 'PreToolUse',
97
97
  pattern: 'Agent',
98
98
  script: 'dashboard-event.js',
99
- command: 'node scripts/hooks/run-with-flags.js dashboard-event.js',
99
+ command: 'node node_modules/erne-universal/scripts/hooks/run-with-flags.js dashboard-event.js',
100
100
  profiles: ['minimal', 'standard', 'strict'],
101
101
  },
102
102
  {
103
103
  event: 'PostToolUse',
104
104
  pattern: 'Agent',
105
105
  script: 'dashboard-event.js',
106
- command: 'node scripts/hooks/run-with-flags.js dashboard-event.js',
106
+ command: 'node node_modules/erne-universal/scripts/hooks/run-with-flags.js dashboard-event.js',
107
107
  profiles: ['minimal', 'standard', 'strict'],
108
108
  }
109
109
  );
@@ -112,7 +112,7 @@ async function ensureHooksConfigured() {
112
112
  const dashboardHook = {
113
113
  pattern: 'Agent',
114
114
  script: 'dashboard-event.js',
115
- command: 'node scripts/hooks/run-with-flags.js dashboard-event.js',
115
+ command: 'node node_modules/erne-universal/scripts/hooks/run-with-flags.js dashboard-event.js',
116
116
  profiles: ['minimal', 'standard', 'strict'],
117
117
  };
118
118
  if (!data.PreToolUse) data.PreToolUse = [];
@@ -157,7 +157,7 @@ async function dashboard() {
157
157
 
158
158
  // Ensure ws dependency is installed
159
159
  const dashboardDir = path.resolve(__dirname, '..', 'dashboard');
160
- if (!fs.existsSync(path.join(dashboardDir, 'node_modules', 'ws'))) {
160
+ if (!fs.existsSync(path.join(dashboardDir, 'node_modules', 'better-sqlite3'))) {
161
161
  console.log(' Installing dashboard dependencies...');
162
162
  require('child_process').execSync('npm install --production', { cwd: dashboardDir, stdio: 'ignore' });
163
163
  }
package/lib/doctor.js CHANGED
@@ -146,7 +146,6 @@ async function autoFix(cwd, findings) {
146
146
  } catch {
147
147
  // tsconfig.json may contain comments (JSON5) — skip auto-fix
148
148
  fixes.push('Skipped strict mode: tsconfig.json contains comments (edit manually)');
149
- return fixes;
150
149
  }
151
150
  // If tsconfig extends a base config, the base likely already sets strict
152
151
  if (tsconfig.extends) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erne-universal",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Complete AI coding agent harness for React Native and Expo development",
5
5
  "keywords": [
6
6
  "react-native",
@@ -45,7 +45,6 @@
45
45
  "docs/commands.md",
46
46
  "docs/hooks-profiles.md",
47
47
  "docs/creating-skills.md",
48
- "docs/assets/",
49
48
  "install.sh",
50
49
  "CLAUDE.md",
51
50
  "AGENTS.md",
@@ -60,7 +59,7 @@
60
59
  "node": ">=18"
61
60
  },
62
61
  "scripts": {
63
- "test": "node --test tests/*.test.js tests/hooks/*.test.js tests/context/*.test.js tests/worker/*.test.js",
62
+ "test": "node --test tests/*.test.js tests/hooks/*.test.js tests/worker/*.test.js",
64
63
  "lint": "eslint lib/ scripts/ bin/",
65
64
  "lint:agents": "node scripts/lint-agents.js",
66
65
  "lint:content": "node scripts/lint-content.js",
@@ -25,6 +25,7 @@ const AGENT_KEYWORDS = [
25
25
  'architect', 'code-reviewer', 'tdd-guide', 'performance-profiler',
26
26
  'native-bridge-builder', 'expo-config-resolver', 'ui-designer', 'upgrade-assistant',
27
27
  'senior-developer', 'feature-builder', 'pipeline-orchestrator', 'visual-debugger',
28
+ 'documentation-generator',
28
29
  ];
29
30
 
30
31
  function detectAgent(text) {
@@ -27,11 +27,13 @@ function sanitizeTitle(title) {
27
27
  * Build standard template variables from a ticket and agent name.
28
28
  */
29
29
  function buildTaskVars(ticket, agent) {
30
- const id = ticket && ticket.id ? String(ticket.id) : '';
30
+ const rawId = ticket && ticket.id ? String(ticket.id) : '';
31
+ const id = rawId.replace(/[`'$\\]/g, '').replace(/[\r\n]+/g, ' ').trim();
31
32
  const rawTitle = ticket && ticket.title ? String(ticket.title) : '';
32
33
  const title = sanitizeTitle(rawTitle);
33
34
  const provider = ticket && ticket.provider ? String(ticket.provider) : '';
34
- const url = ticket && ticket.url ? String(ticket.url) : '';
35
+ const rawUrl = ticket && ticket.url ? String(ticket.url) : '';
36
+ const url = rawUrl.replace(/[`'$\\]/g, '').replace(/[\r\n]+/g, ' ').trim();
35
37
 
36
38
  const branchId = id.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
37
39
  const branch = `${agent || 'worker'}/task-${branchId}`;
@@ -11,6 +11,7 @@ const { selfReview } = require('./self-reviewer');
11
11
  const { runTests } = require('./test-verifier');
12
12
  const { calculateHealthDelta } = require('./health-delta');
13
13
  const { publishDashboardEvent } = require('./dashboard-events');
14
+ const { calculateConfidence } = require('./confidence-scorer');
14
15
 
15
16
  /**
16
17
  * Execute the full per-ticket pipeline.
@@ -89,6 +90,7 @@ async function executePipeline({ ticket, config, provider, auditData, stackInfo,
89
90
  passed: testResults.passed,
90
91
  });
91
92
 
93
+ let testsFailing = false;
92
94
  if (!testResults.passed) {
93
95
  // Auto-fix attempt: feed test errors back to Claude and retry once
94
96
  logger.info('Tests failed — attempting auto-fix');
@@ -108,10 +110,13 @@ async function executePipeline({ ticket, config, provider, auditData, stackInfo,
108
110
  autoFixAttempt: true,
109
111
  });
110
112
  }
113
+ if (!testResults.passed) {
114
+ testsFailing = true;
115
+ logger.warn('Tests still failing after auto-fix attempt');
116
+ }
111
117
  }
112
118
 
113
119
  // 5g. Health delta
114
- const { calculateConfidence } = require('./confidence-scorer');
115
120
  const confidenceResult = calculateConfidence(ticket, auditData, context);
116
121
  const healthDelta = calculateHealthDelta({
117
122
  testResults,
@@ -137,13 +142,14 @@ async function executePipeline({ ticket, config, provider, auditData, stackInfo,
137
142
  }
138
143
  }
139
144
 
140
- // 5i. Return success
145
+ // 5i. Return success (PR is still created even if tests fail per spec)
141
146
  return {
142
147
  success: true,
143
- output: execResult.output,
148
+ output: testsFailing ? `[TESTS FAILING] ${execResult.output}` : execResult.output,
144
149
  agent,
145
150
  confidence: confidenceResult,
146
151
  testResults,
152
+ testsFailing,
147
153
  healthDelta,
148
154
  selfReview: reviewResult,
149
155
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { execSync } = require('child_process');
3
+ const { execFileSync } = require('child_process');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
 
@@ -13,7 +13,7 @@ function createWorktree(repoPath, branch, logger) {
13
13
 
14
14
  try {
15
15
  // Try creating a new branch from main
16
- execSync(`git worktree add -b ${branch} ${worktreePath} main`, {
16
+ execFileSync('git', ['worktree', 'add', '-b', branch, worktreePath, 'main'], {
17
17
  cwd: repoPath,
18
18
  stdio: 'pipe',
19
19
  });
@@ -21,7 +21,7 @@ function createWorktree(repoPath, branch, logger) {
21
21
  } catch {
22
22
  // Branch may already exist — try attaching to existing branch
23
23
  try {
24
- execSync(`git worktree add ${worktreePath} ${branch}`, {
24
+ execFileSync('git', ['worktree', 'add', worktreePath, branch], {
25
25
  cwd: repoPath,
26
26
  stdio: 'pipe',
27
27
  });
@@ -40,7 +40,7 @@ function createWorktree(repoPath, branch, logger) {
40
40
  */
41
41
  function removeWorktree(repoPath, worktreePath, logger) {
42
42
  try {
43
- execSync(`git worktree remove --force ${worktreePath}`, {
43
+ execFileSync('git', ['worktree', 'remove', '--force', worktreePath], {
44
44
  cwd: repoPath,
45
45
  stdio: 'pipe',
46
46
  });
Binary file