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.
- package/.opencode/codex.codex +1 -1
- package/.opencode/enforcer-config.json +2 -2
- package/.opencode/package.json +1 -1
- package/.opencode/plugins/strray-codex-injection.js +111 -303
- package/.opencode/strray/codex.json +1 -1
- package/.opencode/strray/config.json +1 -1
- package/.opencode/strray/features.json +1 -1
- package/.opencode/strray/integrations.json +3 -3
- package/README.md +1 -1
- package/dist/analytics/routing-refiner.js +1 -1
- package/dist/core/boot-orchestrator.d.ts +0 -1
- package/dist/core/boot-orchestrator.d.ts.map +1 -1
- package/dist/core/boot-orchestrator.js +34 -114
- package/dist/core/boot-orchestrator.js.map +1 -1
- package/dist/core/codex-formatter.d.ts +2 -1
- package/dist/core/codex-formatter.d.ts.map +1 -1
- package/dist/core/codex-formatter.js +5 -2
- package/dist/core/codex-formatter.js.map +1 -1
- package/dist/core/config-paths.js +8 -7
- package/dist/core/config-paths.js.map +1 -1
- package/dist/core/features-config.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/mcps/architect-tools.server.js +1 -1
- package/dist/mcps/auto-format.server.js +1 -1
- package/dist/mcps/boot-orchestrator.server.js +1 -1
- package/dist/mcps/enforcer-tools.server.js +1 -1
- package/dist/mcps/estimation.server.js +1 -1
- package/dist/mcps/framework-compliance-audit.server.js +1 -1
- package/dist/mcps/framework-help.server.js +1 -1
- package/dist/mcps/knowledge-skills/api-design.server.js +1 -1
- package/dist/mcps/knowledge-skills/architecture-patterns.server.js +1 -1
- package/dist/mcps/knowledge-skills/bug-triage-specialist.server.js +1 -1
- package/dist/mcps/knowledge-skills/code-analyzer.server.js +1 -1
- package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
- package/dist/mcps/knowledge-skills/content-creator.server.js +1 -1
- package/dist/mcps/knowledge-skills/database-design.server.js +1 -1
- package/dist/mcps/knowledge-skills/devops-deployment.server.js +1 -1
- package/dist/mcps/knowledge-skills/git-workflow.server.js +1 -1
- package/dist/mcps/knowledge-skills/growth-strategist.server.js +1 -1
- package/dist/mcps/knowledge-skills/log-monitor.server.js +1 -1
- package/dist/mcps/knowledge-skills/mobile-development.server.js +1 -1
- package/dist/mcps/knowledge-skills/multimodal-looker.server.js +1 -1
- package/dist/mcps/knowledge-skills/performance-optimization.server.js +1 -1
- package/dist/mcps/knowledge-skills/project-analysis.server.js +1 -1
- package/dist/mcps/knowledge-skills/refactoring-strategies.server.js +1 -1
- package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
- package/dist/mcps/knowledge-skills/seo-consultant.server.js +1 -1
- package/dist/mcps/knowledge-skills/session-management.server.js +1 -1
- package/dist/mcps/knowledge-skills/skill-invocation.server.js +1 -1
- package/dist/mcps/knowledge-skills/strategist.server.js +1 -1
- package/dist/mcps/knowledge-skills/tech-writer.server.js +2 -2
- package/dist/mcps/knowledge-skills/testing-best-practices.server.js +1 -1
- package/dist/mcps/knowledge-skills/testing-strategy.server.js +1 -1
- package/dist/mcps/knowledge-skills/ui-ux-design.server.js +1 -1
- package/dist/mcps/lint.server.js +1 -1
- package/dist/mcps/model-health-check.server.js +1 -1
- package/dist/mcps/performance-analysis.server.js +1 -1
- package/dist/mcps/processor-pipeline.server.js +1 -1
- package/dist/mcps/researcher.server.js +1 -1
- package/dist/mcps/security-scan.server.js +1 -1
- package/dist/mcps/state-manager.server.js +1 -1
- package/dist/orchestrator/universal-registry-bridge.js +1 -1
- package/dist/processors/version-compliance-processor.d.ts.map +1 -1
- package/dist/processors/version-compliance-processor.js +3 -2
- package/dist/processors/version-compliance-processor.js.map +1 -1
- package/package.json +4 -4
- package/scripts/node/pre-publish-check.sh +1 -1
- package/scripts/node/universal-version-manager.js +2 -2
package/.opencode/codex.codex
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.15.
|
|
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.
|
|
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.
|
|
223
|
+
"version": "1.15.13",
|
|
224
224
|
"terms": [
|
|
225
225
|
1,
|
|
226
226
|
2,
|
package/.opencode/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
logger.log(`✅ Loaded from ../../dist/`);
|
|
96
48
|
return;
|
|
97
49
|
}
|
|
98
50
|
catch (e) {
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
logger.log(`✅ Loaded from ../../node_modules/${pluginPath}/dist/`);
|
|
114
66
|
return;
|
|
115
67
|
}
|
|
116
68
|
catch (e) {
|
|
117
|
-
|
|
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", "
|
|
78
|
+
stdio: ["ignore", "inherit", "pipe"], // Original working stdio - stdout to terminal (ASCII visible)
|
|
234
79
|
});
|
|
235
80
|
let stdout = "";
|
|
236
81
|
let stderr = "";
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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, .
|
|
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
|
-
|
|
409
|
-
|
|
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: ${
|
|
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
|
-
|
|
480
|
-
|
|
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
|
|
535
|
-
if (!
|
|
536
|
-
logger.
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
if (!
|
|
541
|
-
logger.error(
|
|
542
|
-
|
|
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(
|
|
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
|
|
694
|
-
if (
|
|
695
|
-
|
|
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");
|