strray-ai 1.15.11 → 1.15.13

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 (75) hide show
  1. package/.opencode/codex.codex +1 -1
  2. package/.opencode/enforcer-config.json +2 -2
  3. package/.opencode/package.json +1 -1
  4. package/.opencode/plugins/strray-codex-injection.js +111 -303
  5. package/.opencode/strray/codex.json +1 -1
  6. package/.opencode/strray/config.json +1 -1
  7. package/.opencode/strray/features.json +1 -1
  8. package/.opencode/strray/integrations.json +3 -3
  9. package/README.md +1 -1
  10. package/dist/analytics/routing-refiner.js +1 -1
  11. package/dist/core/boot-orchestrator.d.ts +0 -1
  12. package/dist/core/boot-orchestrator.d.ts.map +1 -1
  13. package/dist/core/boot-orchestrator.js +34 -114
  14. package/dist/core/boot-orchestrator.js.map +1 -1
  15. package/dist/core/codex-formatter.d.ts +2 -1
  16. package/dist/core/codex-formatter.d.ts.map +1 -1
  17. package/dist/core/codex-formatter.js +5 -2
  18. package/dist/core/codex-formatter.js.map +1 -1
  19. package/dist/core/config-paths.js +8 -7
  20. package/dist/core/config-paths.js.map +1 -1
  21. package/dist/core/features-config.js +1 -1
  22. package/dist/core/index.d.ts +1 -1
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/index.js +1 -1
  25. package/dist/core/index.js.map +1 -1
  26. package/dist/index.d.ts +12 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcps/architect-tools.server.js +1 -1
  31. package/dist/mcps/auto-format.server.js +1 -1
  32. package/dist/mcps/boot-orchestrator.server.js +1 -1
  33. package/dist/mcps/enforcer-tools.server.js +1 -1
  34. package/dist/mcps/estimation.server.js +1 -1
  35. package/dist/mcps/framework-compliance-audit.server.js +1 -1
  36. package/dist/mcps/framework-help.server.js +1 -1
  37. package/dist/mcps/knowledge-skills/api-design.server.js +1 -1
  38. package/dist/mcps/knowledge-skills/architecture-patterns.server.js +1 -1
  39. package/dist/mcps/knowledge-skills/bug-triage-specialist.server.js +1 -1
  40. package/dist/mcps/knowledge-skills/code-analyzer.server.js +1 -1
  41. package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
  42. package/dist/mcps/knowledge-skills/content-creator.server.js +1 -1
  43. package/dist/mcps/knowledge-skills/database-design.server.js +1 -1
  44. package/dist/mcps/knowledge-skills/devops-deployment.server.js +1 -1
  45. package/dist/mcps/knowledge-skills/git-workflow.server.js +1 -1
  46. package/dist/mcps/knowledge-skills/growth-strategist.server.js +1 -1
  47. package/dist/mcps/knowledge-skills/log-monitor.server.js +1 -1
  48. package/dist/mcps/knowledge-skills/mobile-development.server.js +1 -1
  49. package/dist/mcps/knowledge-skills/multimodal-looker.server.js +1 -1
  50. package/dist/mcps/knowledge-skills/performance-optimization.server.js +1 -1
  51. package/dist/mcps/knowledge-skills/project-analysis.server.js +1 -1
  52. package/dist/mcps/knowledge-skills/refactoring-strategies.server.js +1 -1
  53. package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
  54. package/dist/mcps/knowledge-skills/seo-consultant.server.js +1 -1
  55. package/dist/mcps/knowledge-skills/session-management.server.js +1 -1
  56. package/dist/mcps/knowledge-skills/skill-invocation.server.js +1 -1
  57. package/dist/mcps/knowledge-skills/strategist.server.js +1 -1
  58. package/dist/mcps/knowledge-skills/tech-writer.server.js +2 -2
  59. package/dist/mcps/knowledge-skills/testing-best-practices.server.js +1 -1
  60. package/dist/mcps/knowledge-skills/testing-strategy.server.js +1 -1
  61. package/dist/mcps/knowledge-skills/ui-ux-design.server.js +1 -1
  62. package/dist/mcps/lint.server.js +1 -1
  63. package/dist/mcps/model-health-check.server.js +1 -1
  64. package/dist/mcps/performance-analysis.server.js +1 -1
  65. package/dist/mcps/processor-pipeline.server.js +1 -1
  66. package/dist/mcps/researcher.server.js +1 -1
  67. package/dist/mcps/security-scan.server.js +1 -1
  68. package/dist/mcps/state-manager.server.js +1 -1
  69. package/dist/orchestrator/universal-registry-bridge.js +1 -1
  70. package/dist/processors/version-compliance-processor.d.ts.map +1 -1
  71. package/dist/processors/version-compliance-processor.js +3 -2
  72. package/dist/processors/version-compliance-processor.js.map +1 -1
  73. package/package.json +4 -4
  74. package/scripts/node/pre-publish-check.sh +1 -1
  75. package/scripts/node/universal-version-manager.js +2 -2
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.15.11",
2
+ "version": "1.15.13",
3
3
  "terms": [
4
4
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60
5
5
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "framework": "StringRay 1.0.0",
3
- "version": "1.15.11",
3
+ "version": "1.15.13",
4
4
  "description": "Codex-compliant framework configuration for Credible UI project",
5
5
  "thresholds": {
6
6
  "bundleSize": {
@@ -220,7 +220,7 @@
220
220
  }
221
221
  },
222
222
  "codex": {
223
- "version": "1.15.11",
223
+ "version": "1.15.13",
224
224
  "terms": [
225
225
  1,
226
226
  2,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode/OpenCode",
3
- "version": "1.15.11",
3
+ "version": "1.15.13",
4
4
  "description": "OpenCode framework configuration",
5
5
  "main": "OpenCode.json",
6
6
  "scripts": {
@@ -1,64 +1,17 @@
1
1
  /**
2
2
  * StrRay Codex Injection Plugin for OpenCode
3
3
  *
4
- * This plugin automatically injects the Universal Development Codex
4
+ * This plugin automatically injects the Universal Development Codex v1.2.0
5
5
  * into the system prompt for all AI agents, ensuring codex terms are
6
6
  * consistently enforced across the entire development session.
7
7
  *
8
+ * @version 1.0.0
8
9
  * @author StrRay Framework
9
10
  */
10
11
  import * as fs from "fs";
11
12
  import * as path from "path";
12
13
  import { spawn } from "child_process";
13
- // Dynamic imports with absolute paths at runtime
14
- let runQualityGateWithLogging;
15
- let qualityGateDirectory = "";
16
- async function importQualityGate(directory) {
17
- if (!runQualityGateWithLogging || qualityGateDirectory !== directory) {
18
- try {
19
- const qualityGatePath = path.join(directory, "dist", "plugin", "quality-gate.js");
20
- const module = await import(qualityGatePath);
21
- runQualityGateWithLogging = module.runQualityGateWithLogging;
22
- qualityGateDirectory = directory;
23
- }
24
- catch (e) {
25
- // Quality gate not available
26
- }
27
- }
28
- }
29
- // Direct activity logging - writes to activity.log without module isolation issues
30
- let activityLogPath = "";
31
- let activityLogInitialized = false;
32
- function initializeActivityLog(directory) {
33
- if (activityLogInitialized && activityLogPath)
34
- return;
35
- const logDir = path.join(directory, "logs", "framework");
36
- if (!fs.existsSync(logDir)) {
37
- fs.mkdirSync(logDir, { recursive: true });
38
- }
39
- // Use a separate file for plugin tool events to avoid framework overwrites
40
- activityLogPath = path.join(logDir, "plugin-tool-events.log");
41
- activityLogInitialized = true;
42
- }
43
- function logToolActivity(directory, eventType, tool, args, result, error, duration) {
44
- initializeActivityLog(directory);
45
- const timestamp = new Date().toISOString();
46
- const jobId = `plugin-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
47
- if (eventType === "start") {
48
- const entry = `${timestamp} [${jobId}] [agent] tool-started - INFO | {"tool":"${tool}","args":${JSON.stringify(Object.keys(args || {}))}}\n`;
49
- fs.appendFileSync(activityLogPath, entry);
50
- }
51
- else if (eventType === "routing") {
52
- const entry = `${timestamp} [${jobId}] [agent] routing-detected - INFO | {"tool":"${tool}","routing":${JSON.stringify(args)}}\n`;
53
- fs.appendFileSync(activityLogPath, entry);
54
- }
55
- else {
56
- const success = !error;
57
- const level = success ? "SUCCESS" : "ERROR";
58
- const entry = `${timestamp} [${jobId}] [agent] tool-${success ? "complete" : "failed"} - ${level} | {"tool":"${tool}","duration":${duration || 0}${error ? `,"error":"${error}"` : ""}}\n`;
59
- fs.appendFileSync(activityLogPath, entry);
60
- }
61
- }
14
+ import { resolveCodexPath, resolveStateDir } from "../core/config-paths.js";
62
15
  // Import lean system prompt generator
63
16
  let SystemPromptGenerator;
64
17
  async function importSystemPromptGenerator() {
@@ -68,7 +21,8 @@ async function importSystemPromptGenerator() {
68
21
  SystemPromptGenerator = module.generateLeanSystemPrompt;
69
22
  }
70
23
  catch (e) {
71
- // Fallback to original implementation - silent fail
24
+ // Fallback to original implementation if lean generator fails
25
+ console.warn("⚠️ Failed to load lean system prompt generator, using fallback");
72
26
  }
73
27
  }
74
28
  }
@@ -77,14 +31,12 @@ let StrRayStateManager;
77
31
  let featuresConfigLoader;
78
32
  let detectTaskType;
79
33
  async function loadStrRayComponents() {
80
- if (ProcessorManager && StrRayStateManager && featuresConfigLoader) {
34
+ if (ProcessorManager && StrRayStateManager && featuresConfigLoader)
81
35
  return;
82
- }
83
- const tempLogger = await getOrCreateLogger(process.cwd());
84
- tempLogger.log(`[StrRay] 🔄 loadStrRayComponents() called - attempting to load framework components`);
36
+ const logger = await getOrCreateLogger(process.cwd());
85
37
  // Try local dist first (for development)
86
38
  try {
87
- tempLogger.log(`[StrRay] 🔄 Attempting to load from ../../dist/`);
39
+ logger.log(`🔄 Attempting to load from ../../dist/`);
88
40
  const procModule = await import("../../dist/processors/processor-manager.js");
89
41
  const stateModule = await import("../../dist/state/state-manager.js");
90
42
  const featuresModule = await import("../../dist/core/features-config.js");
@@ -92,17 +44,17 @@ async function loadStrRayComponents() {
92
44
  StrRayStateManager = stateModule.StrRayStateManager;
93
45
  featuresConfigLoader = featuresModule.featuresConfigLoader;
94
46
  detectTaskType = featuresModule.detectTaskType;
95
- tempLogger.log(`[StrRay] Loaded from ../../dist/`);
47
+ logger.log(`✅ Loaded from ../../dist/`);
96
48
  return;
97
49
  }
98
50
  catch (e) {
99
- tempLogger.error(`[StrRay] Failed to load from ../../dist/: ${e?.message || e}`);
51
+ logger.error(`❌ Failed to load from ../../dist/: ${e?.message || e}`);
100
52
  }
101
53
  // Try node_modules (for consumer installation)
102
54
  const pluginPaths = ["strray-ai", "strray-framework"];
103
55
  for (const pluginPath of pluginPaths) {
104
56
  try {
105
- tempLogger.log(`[StrRay] 🔄 Attempting to load from ../../node_modules/${pluginPath}/dist/`);
57
+ logger.log(`🔄 Attempting to load from ../../node_modules/${pluginPath}/dist/`);
106
58
  const pm = await import(`../../node_modules/${pluginPath}/dist/processors/processor-manager.js`);
107
59
  const sm = await import(`../../node_modules/${pluginPath}/dist/state/state-manager.js`);
108
60
  const fm = await import(`../../node_modules/${pluginPath}/dist/core/features-config.js`);
@@ -110,137 +62,24 @@ async function loadStrRayComponents() {
110
62
  StrRayStateManager = sm.StrRayStateManager;
111
63
  featuresConfigLoader = fm.featuresConfigLoader;
112
64
  detectTaskType = fm.detectTaskType;
113
- tempLogger.log(`[StrRay] Loaded from ../../node_modules/${pluginPath}/dist/`);
65
+ logger.log(`✅ Loaded from ../../node_modules/${pluginPath}/dist/`);
114
66
  return;
115
67
  }
116
68
  catch (e) {
117
- tempLogger.error(`[StrRay] Failed to load from ../../node_modules/${pluginPath}/dist/: ${e?.message || e}`);
69
+ logger.error(`❌ Failed to load from ../../node_modules/${pluginPath}/dist/: ${e?.message || e}`);
118
70
  continue;
119
71
  }
120
72
  }
121
- tempLogger.error(`[StrRay] ❌ Could not load StrRay components from any path`);
122
- }
123
- /**
124
- * Extract task description from tool input
125
- */
126
- function extractTaskDescription(input) {
127
- const { tool, args } = input;
128
- // Extract meaningful task description from various inputs
129
- if (args?.content) {
130
- const content = String(args.content);
131
- // Get first 200 chars as description
132
- return content.slice(0, 200);
133
- }
134
- if (args?.filePath) {
135
- return `${tool} ${args.filePath}`;
136
- }
137
- if (args?.command) {
138
- return String(args.command);
139
- }
140
- // Fallback: Use tool name as task description for routing
141
- // This enables routing even when OpenCode doesn't pass args
142
- if (tool) {
143
- return `execute ${tool} tool`;
144
- }
145
- return null;
146
- }
147
- /**
148
- * Extract action words from command for better routing
149
- * Maps verbs/intents to skill categories
150
- */
151
- function extractActionWords(command) {
152
- if (!command || command.length < 3)
153
- return null;
154
- // Strip quotes and escape sequences for cleaner matching
155
- const cleanCommand = command.replace(/["']/g, ' ').replace(/\\./g, ' ');
156
- // Action word -> skill mapping (ordered by priority)
157
- const actionMap = [
158
- // Review patterns - check first since user likely wants to review content
159
- { pattern: /\b(review|check|audit|examine|inspect|assess|evaluate)\b/i, skill: "code-review" },
160
- // Analyze patterns
161
- { pattern: /\b(analyze|investigate|study)\b/i, skill: "code-analyzer" },
162
- // Fix patterns
163
- { pattern: /\b(fix|debug|resolve|troubleshoot|repair)\b/i, skill: "bug-triage" },
164
- // Create patterns
165
- { pattern: /\b(create|write|generate|build|make|add)\b/i, skill: "content-creator" },
166
- // Test patterns
167
- { pattern: /\b(test|validate|verify)\b/i, skill: "testing" },
168
- // Design patterns
169
- { pattern: /\b(design|plan|architect)\b/i, skill: "architecture" },
170
- // Optimize patterns
171
- { pattern: /\b(optimize|improve|enhance|speed)\b/i, skill: "performance" },
172
- // Security patterns
173
- { pattern: /\b(scan|secure|vulnerability)\b/i, skill: "security" },
174
- // Refactor patterns
175
- { pattern: /\b(refactor|clean|restructure)\b/i, skill: "refactoring" },
176
- ];
177
- // Search for action words anywhere in the command
178
- for (const { pattern } of actionMap) {
179
- const match = cleanCommand.match(pattern);
180
- if (match) {
181
- // Return the matched word plus context after it
182
- const word = match[0];
183
- const idx = cleanCommand.toLowerCase().indexOf(word.toLowerCase());
184
- const after = cleanCommand.slice(idx + word.length, Math.min(idx + word.length + 25, cleanCommand.length)).trim();
185
- return `${word} ${after}`.trim().slice(0, 40);
186
- }
187
- }
188
- // If no action word found, return null to use default routing
189
- return null;
190
- }
191
- /**
192
- * Estimate complexity score based on message content
193
- * Higher complexity = orchestrator routing
194
- * Lower complexity = code-reviewer routing
195
- */
196
- function estimateComplexity(message) {
197
- const text = message.toLowerCase();
198
- // High complexity indicators
199
- const highComplexityKeywords = [
200
- "architecture", "system", "design", "complex", "multiple",
201
- "integrate", "database", "migration", "refactor",
202
- "performance", "optimize", "security", "audit",
203
- "orchestrate", "coordinate", "workflow"
204
- ];
205
- // Low complexity indicators
206
- const lowComplexityKeywords = [
207
- "review", "check", "simple", "quick", "fix",
208
- "small", "typo", "format", "lint", "test"
209
- ];
210
- let score = 50; // default medium
211
- // Check message length
212
- if (message.length > 200)
213
- score += 10;
214
- if (message.length > 500)
215
- score += 15;
216
- // Check for high complexity keywords
217
- for (const keyword of highComplexityKeywords) {
218
- if (text.includes(keyword))
219
- score += 8;
220
- }
221
- // Check for low complexity keywords
222
- for (const keyword of lowComplexityKeywords) {
223
- if (text.includes(keyword))
224
- score -= 5;
225
- }
226
- // Clamp to 0-100
227
- return Math.max(0, Math.min(100, score));
228
73
  }
229
74
  function spawnPromise(command, args, cwd) {
230
75
  return new Promise((resolve, reject) => {
231
76
  const child = spawn(command, args, {
232
77
  cwd,
233
- stdio: ["ignore", "pipe", "pipe"],
78
+ stdio: ["ignore", "inherit", "pipe"], // Original working stdio - stdout to terminal (ASCII visible)
234
79
  });
235
80
  let stdout = "";
236
81
  let stderr = "";
237
- if (child.stdout) {
238
- child.stdout.on("data", (data) => {
239
- const text = data.toString();
240
- stdout += text;
241
- process.stdout.write(text);
242
- });
243
- }
82
+ // Capture stderr only (stdout goes to inherit/terminal)
244
83
  if (child.stderr) {
245
84
  child.stderr.on("data", (data) => {
246
85
  stderr += data.toString();
@@ -330,19 +169,78 @@ function getFrameworkIdentity() {
330
169
  📖 Documentation: .opencode/strray/ (codex, config, agents docs)
331
170
  `;
332
171
  }
172
+ /**
173
+ * Run Enforcer quality gate check before operations
174
+ */
175
+ async function runEnforcerQualityGate(input, logger) {
176
+ const violations = [];
177
+ const { tool, args } = input;
178
+ // Rule 1: tests-required for new files
179
+ if (tool === "write" && args?.filePath) {
180
+ const filePath = args.filePath;
181
+ // Check if this is a source file (not test, not config)
182
+ if (filePath.endsWith(".ts") &&
183
+ !filePath.includes(".test.") &&
184
+ !filePath.includes(".spec.")) {
185
+ // Check if test file exists
186
+ const testPath = filePath.replace(".ts", ".test.ts");
187
+ const specPath = filePath.replace(".ts", ".spec.ts");
188
+ if (!fs.existsSync(testPath) && !fs.existsSync(specPath)) {
189
+ violations.push(`tests-required: No test file found for ${filePath} (expected ${testPath} or ${specPath})`);
190
+ logger.log(`⚠️ ENFORCER: tests-required violation detected for ${filePath}`);
191
+ }
192
+ }
193
+ }
194
+ // Rule 2: documentation-required for new features
195
+ if (tool === "write" && args?.filePath?.includes("src/")) {
196
+ const docsDir = path.join(process.cwd(), "docs");
197
+ const readmePath = path.join(process.cwd(), "README.md");
198
+ // Check if docs directory exists
199
+ if (!fs.existsSync(docsDir) && !fs.existsSync(readmePath)) {
200
+ violations.push(`documentation-required: No documentation found for new feature`);
201
+ logger.log(`⚠️ ENFORCER: documentation-required violation detected`);
202
+ }
203
+ }
204
+ // Rule 3: resolve-all-errors - check if we're creating code with error patterns
205
+ if (args?.content) {
206
+ const errorPatterns = [
207
+ /console\.log\s*\(/g,
208
+ /TODO\s*:/gi,
209
+ /FIXME\s*:/gi,
210
+ /throw\s+new\s+Error\s*\(\s*['"]test['"]\s*\)/gi,
211
+ ];
212
+ for (const pattern of errorPatterns) {
213
+ if (pattern.test(args.content)) {
214
+ violations.push(`resolve-all-errors: Found debug/error pattern (${pattern.source}) in code`);
215
+ logger.log(`⚠️ ENFORCER: resolve-all-errors violation detected`);
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ const passed = violations.length === 0;
221
+ if (!passed) {
222
+ logger.error(`🚫 Quality Gate FAILED with ${violations.length} violations`);
223
+ }
224
+ else {
225
+ logger.log(`✅ Quality Gate PASSED`);
226
+ }
227
+ return { passed, violations };
228
+ }
333
229
  /**
334
230
  * Global codex context cache (loaded once)
335
231
  */
336
232
  let cachedCodexContexts = null;
337
233
  /**
338
- * Codex file locations to search
234
+ * Codex file locations resolved through the standard priority chain.
235
+ * Falls back to additional OpenCode-specific files not covered by the resolver.
339
236
  */
340
- const CODEX_FILE_LOCATIONS = [
341
- ".opencode/strray/codex.json",
342
- ".opencode/codex.codex",
343
- ".opencode/strray/agents_template.md",
344
- "AGENTS.md",
345
- ];
237
+ function getCodexFileLocations(directory) {
238
+ const root = directory || process.cwd();
239
+ const resolved = resolveCodexPath(root);
240
+ // Add OpenCode-specific fallbacks not in the standard chain
241
+ resolved.push(path.join(root, ".opencode", "codex.codex"), path.join(root, ".strray", "agents_template.md"), path.join(root, "AGENTS.md"));
242
+ return resolved;
243
+ }
346
244
  /**
347
245
  * Read file content safely
348
246
  */
@@ -373,7 +271,7 @@ function extractCodexMetadata(content) {
373
271
  // Not valid JSON, try markdown format
374
272
  }
375
273
  }
376
- // Markdown format (AGENTS.md, .opencode/strray/agents_template.md)
274
+ // Markdown format (AGENTS.md, .strray/agents_template.md)
377
275
  const versionMatch = content.match(/\*\*Version\*\*:\s*(\d+\.\d+\.\d+)/);
378
276
  const version = versionMatch && versionMatch[1] ? versionMatch[1] : "1.6.0";
379
277
  const termMatches = content.match(/####\s*\d+\.\s/g);
@@ -405,8 +303,9 @@ function loadCodexContext(directory) {
405
303
  return cachedCodexContexts;
406
304
  }
407
305
  const codexContexts = [];
408
- for (const relativePath of CODEX_FILE_LOCATIONS) {
409
- const fullPath = path.join(directory, relativePath);
306
+ const locations = getCodexFileLocations(directory);
307
+ for (const fileLocation of locations) {
308
+ const fullPath = path.isAbsolute(fileLocation) ? fileLocation : path.join(directory, fileLocation);
410
309
  const content = readFileContent(fullPath);
411
310
  if (content && content.trim().length > 0) {
412
311
  const entry = createCodexContextEntry(fullPath, content);
@@ -417,7 +316,7 @@ function loadCodexContext(directory) {
417
316
  }
418
317
  cachedCodexContexts = codexContexts;
419
318
  if (codexContexts.length === 0) {
420
- void getOrCreateLogger(directory).then((l) => l.error(`No valid codex files found. Checked: ${CODEX_FILE_LOCATIONS.join(", ")}`));
319
+ void getOrCreateLogger(directory).then((l) => l.error(`No valid codex files found. Checked: ${locations.join(", ")}`));
421
320
  }
422
321
  return codexContexts;
423
322
  }
@@ -439,8 +338,6 @@ function formatCodexContext(contexts) {
439
338
  *
440
339
  * This plugin hooks into experimental.chat.system.transform event
441
340
  * to inject codex terms into system prompt before it's sent to LLM.
442
- *
443
- * OpenCode expects hooks to be nested under a "hooks" key.
444
341
  */
445
342
  export default async function strrayCodexPlugin(input) {
446
343
  const { directory: inputDirectory } = input;
@@ -448,26 +345,30 @@ export default async function strrayCodexPlugin(input) {
448
345
  return {
449
346
  "experimental.chat.system.transform": async (_input, output) => {
450
347
  try {
348
+ // Use lean system prompt generator for token efficiency
451
349
  await importSystemPromptGenerator();
452
350
  let leanPrompt = getFrameworkIdentity();
351
+ // Use lean generator if available, otherwise fall back to minimal logic
453
352
  if (SystemPromptGenerator) {
454
353
  leanPrompt = await SystemPromptGenerator({
455
354
  showWelcomeBanner: true,
456
- showCodexContext: false,
355
+ showCodexContext: false, // Disabled for token efficiency
457
356
  enableTokenOptimization: true,
458
- maxTokenBudget: 3000,
357
+ maxTokenBudget: 3000, // Conservative token budget
459
358
  showCriticalTermsOnly: true,
460
359
  showEssentialLinks: true
461
360
  });
462
361
  }
463
- // Routing is handled in chat.message hook - this hook only does system prompt injection
464
362
  if (output.system && Array.isArray(output.system)) {
363
+ // Replace verbose system prompt with lean version
465
364
  output.system = [leanPrompt];
466
365
  }
467
366
  }
468
367
  catch (error) {
368
+ // Critical failure - log error but don't break the plugin
469
369
  const logger = await getOrCreateLogger(directory);
470
370
  logger.error("System prompt injection failed:", error);
371
+ // Fallback to minimal prompt
471
372
  const fallback = getFrameworkIdentity();
472
373
  if (output.system && Array.isArray(output.system)) {
473
374
  output.system = [fallback];
@@ -476,28 +377,8 @@ export default async function strrayCodexPlugin(input) {
476
377
  },
477
378
  "tool.execute.before": async (input, output) => {
478
379
  const logger = await getOrCreateLogger(directory);
479
- // Retrieve original user message for context preservation (file-based)
480
- let originalMessage = null;
481
- try {
482
- const contextFiles = fs.readdirSync(directory)
483
- .filter(f => f.startsWith("context-") && f.endsWith(".json"))
484
- .map(f => ({
485
- name: f,
486
- time: fs.statSync(path.join(directory, f)).mtime.getTime()
487
- }))
488
- .sort((a, b) => b.time - a.time);
489
- if (contextFiles.length > 0 && contextFiles[0]) {
490
- const latestContext = JSON.parse(fs.readFileSync(path.join(directory, contextFiles[0].name), "utf-8"));
491
- originalMessage = latestContext.userMessage;
492
- }
493
- }
494
- catch (e) {
495
- // Silent fail - context is optional
496
- }
497
- if (originalMessage) {
498
- logger.log(`📌 Original intent: "${originalMessage.slice(0, 80)}..."`);
499
- }
500
- logToolActivity(directory, "start", input.tool, input.args || {});
380
+ logger.log(`🚀 TOOL EXECUTE BEFORE HOOK FIRED: ${input.tool}`);
381
+ logger.log(`📥 Full input: ${JSON.stringify(input)}`);
501
382
  await loadStrRayComponents();
502
383
  if (featuresConfigLoader && detectTaskType) {
503
384
  try {
@@ -516,34 +397,18 @@ export default async function strrayCodexPlugin(input) {
516
397
  }
517
398
  }
518
399
  const { tool, args } = input;
519
- // Extract action words from command for better tool routing
520
- const command = args?.command ? String(args.command) : "";
521
- let taskDescription = null;
522
- if (command) {
523
- const actionWords = extractActionWords(command);
524
- if (actionWords) {
525
- taskDescription = actionWords;
526
- logger.log(`📝 Action words extracted: "${actionWords}"`);
527
- }
528
- }
529
- // Also try to extract from content if no command
530
- if (!taskDescription) {
531
- taskDescription = extractTaskDescription(input);
532
- }
533
400
  // ENFORCER QUALITY GATE CHECK - Block on violations
534
- await importQualityGate(directory);
535
- if (!runQualityGateWithLogging) {
536
- logger.log("Quality gate not available, skipping");
401
+ const qualityGateResult = await runEnforcerQualityGate(input, logger);
402
+ if (!qualityGateResult.passed) {
403
+ logger.error(`🚫 Quality gate failed: ${qualityGateResult.violations.join(", ")}`);
404
+ throw new Error(`ENFORCER BLOCKED: ${qualityGateResult.violations.join("; ")}`);
537
405
  }
538
- else {
539
- const qualityGateResult = await runQualityGateWithLogging({ tool, args }, logger);
540
- if (!qualityGateResult.passed) {
541
- logger.error(`🚫 Quality gate failed: ${qualityGateResult.violations.join(", ")}`);
542
- throw new Error(`ENFORCER BLOCKED: ${qualityGateResult.violations.join("; ")}`);
406
+ logger.log(`✅ Quality gate passed for ${tool}`);
407
+ if (["write", "edit", "multiedit"].includes(tool)) {
408
+ if (!ProcessorManager || !StrRayStateManager) {
409
+ logger.error("ProcessorManager or StrRayStateManager not loaded");
410
+ return;
543
411
  }
544
- }
545
- // Run processors for ALL tools (not just write/edit)
546
- if (ProcessorManager || StrRayStateManager) {
547
412
  // PHASE 1: Connect to booted framework or boot if needed
548
413
  let stateManager;
549
414
  let processorManager;
@@ -556,7 +421,7 @@ export default async function strrayCodexPlugin(input) {
556
421
  else {
557
422
  logger.log("🚀 StrRay framework not booted, initializing...");
558
423
  // Create new state manager (framework not booted yet)
559
- stateManager = new StrRayStateManager(path.join(directory, ".opencode", "state"));
424
+ stateManager = new StrRayStateManager(resolveStateDir(directory));
560
425
  // Store globally for future use
561
426
  globalThis.strRayStateManager = stateManager;
562
427
  }
@@ -602,12 +467,6 @@ export default async function strrayCodexPlugin(input) {
602
467
  priority: 20,
603
468
  enabled: true,
604
469
  });
605
- processorManager.registerProcessor({
606
- name: "agentsMdValidation",
607
- type: "post",
608
- priority: 30,
609
- enabled: true,
610
- });
611
470
  // Store for future use
612
471
  stateManager.set("processor:manager", processorManager);
613
472
  logger.log("✅ Processors registered successfully");
@@ -617,11 +476,6 @@ export default async function strrayCodexPlugin(input) {
617
476
  }
618
477
  // PHASE 2: Execute pre-processors with detailed logging
619
478
  try {
620
- // Check if processorManager and method exist
621
- if (!processorManager || typeof processorManager.executePreProcessors !== 'function') {
622
- logger.log(`⏭️ Pre-processors skipped: processor manager not available`);
623
- return;
624
- }
625
479
  logger.log(`▶️ Executing pre-processors for ${tool}...`);
626
480
  const result = await processorManager.executePreProcessors({
627
481
  tool,
@@ -650,11 +504,6 @@ export default async function strrayCodexPlugin(input) {
650
504
  }
651
505
  // PHASE 3: Execute post-processors after tool completion
652
506
  try {
653
- // Check if processorManager and method exist
654
- if (!processorManager || typeof processorManager.executePostProcessors !== 'function') {
655
- logger.log(`⏭️ Post-processors skipped: processor manager not available`);
656
- return;
657
- }
658
507
  logger.log(`▶️ Executing post-processors for ${tool}...`);
659
508
  logger.log(`📝 Post-processor args: ${JSON.stringify(args)}`);
660
509
  const postResults = await processorManager.executePostProcessors(tool, {
@@ -684,15 +533,15 @@ export default async function strrayCodexPlugin(input) {
684
533
  // Execute POST-processors AFTER tool completes (this is the correct place!)
685
534
  "tool.execute.after": async (input, _output) => {
686
535
  const logger = await getOrCreateLogger(directory);
687
- const { tool, args, result } = input;
688
- // Log tool completion to activity logger (direct write - no module isolation issues)
689
- logToolActivity(directory, "complete", tool, args || {}, result, result?.error, result?.duration);
690
536
  await loadStrRayComponents();
537
+ const { tool, args, result } = input;
691
538
  // Debug: log full input
692
539
  logger.log(`📥 After hook input: ${JSON.stringify({ tool, hasArgs: !!args, args, hasResult: !!result }).slice(0, 200)}`);
693
- // Run post-processors for ALL tools AFTER tool completes
694
- if (ProcessorManager || StrRayStateManager) {
695
- const stateManager = new StrRayStateManager(path.join(directory, ".opencode", "state"));
540
+ // Run post-processors for write/edit operations AFTER tool completes
541
+ if (["write", "edit", "multiedit"].includes(tool)) {
542
+ if (!ProcessorManager || !StrRayStateManager)
543
+ return;
544
+ const stateManager = new StrRayStateManager(resolveStateDir(directory));
696
545
  const processorManager = new ProcessorManager(stateManager);
697
546
  // Register post-processors
698
547
  processorManager.registerProcessor({
@@ -714,11 +563,6 @@ export default async function strrayCodexPlugin(input) {
714
563
  enabled: true,
715
564
  });
716
565
  try {
717
- // Check if processorManager and method exist
718
- if (!processorManager || typeof processorManager.executePostProcessors !== 'function') {
719
- logger.log(`⏭️ Post-processors skipped: processor manager not available`);
720
- return;
721
- }
722
566
  // Execute post-processors AFTER tool - with actual filePath for testAutoCreation
723
567
  logger.log(`📝 Post-processor tool: ${tool}`);
724
568
  logger.log(`📝 Post-processor args: ${JSON.stringify(args)}`);
@@ -757,42 +601,6 @@ export default async function strrayCodexPlugin(input) {
757
601
  }
758
602
  }
759
603
  },
760
- /**
761
- * chat.message - Intercept user messages for routing
762
- * Output contains message and parts with user content
763
- */
764
- "chat.message": async (input, output) => {
765
- const logger = await getOrCreateLogger(directory);
766
- let userMessage = "";
767
- if (output?.parts && Array.isArray(output.parts)) {
768
- for (const part of output.parts) {
769
- if (part?.type === "text" && part?.text) {
770
- userMessage = part.text;
771
- break;
772
- }
773
- }
774
- }
775
- // Store original user message for tool hooks (context preservation)
776
- const sessionId = output?.message?.sessionID || "default";
777
- try {
778
- const contextData = JSON.stringify({
779
- sessionId,
780
- userMessage,
781
- timestamp: new Date().toISOString()
782
- });
783
- const contextPath = path.join(directory, `context-${sessionId}.json`);
784
- fs.writeFileSync(contextPath, contextData, "utf-8");
785
- }
786
- catch (e) {
787
- // Silent fail - context is optional
788
- }
789
- globalThis.__strRayOriginalMessage = userMessage;
790
- logger.log(`userMessage: "${userMessage.slice(0, 100)}"`);
791
- if (!userMessage || userMessage.length === 0) {
792
- return;
793
- }
794
- logger.log(`👤 User message: "${userMessage.slice(0, 50)}..."`);
795
- },
796
604
  config: async (_config) => {
797
605
  const logger = await getOrCreateLogger(directory);
798
606
  logger.log("🔧 Plugin config hook triggered - initializing StrRay integration");
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.15.11",
2
+ "version": "1.15.13",
3
3
  "lastUpdated": "2026-03-09",
4
4
  "errorPreventionTarget": 0.996,
5
5
  "terms": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./config.schema.json",
3
- "version": "1.15.11",
3
+ "version": "1.15.13",
4
4
  "description": "StringRay Framework - Token Management & Performance Configuration",
5
5
 
6
6
  "token_management": {