0xray 4.0.36 → 4.0.37
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/.grok-plugin/plugin.json +1 -1
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +15 -0
- package/dist/analytics/emerging-pattern-detector.d.ts +12 -1
- package/dist/analytics/emerging-pattern-detector.js +37 -12
- package/dist/analytics/pattern-learning-engine.js +1 -9
- package/dist/analytics/predictive-analytics.js +51 -33
- package/dist/analytics/prompt-pattern-analyzer.d.ts +1 -0
- package/dist/analytics/prompt-pattern-analyzer.js +39 -20
- package/dist/analytics/routing-performance-analyzer.d.ts +2 -0
- package/dist/analytics/routing-performance-analyzer.js +89 -37
- package/dist/analytics/simple-pattern-analyzer.d.ts +4 -0
- package/dist/analytics/simple-pattern-analyzer.js +66 -28
- package/dist/architect/architect-tools.d.ts +3 -0
- package/dist/architect/architect-tools.js +22 -0
- package/dist/cli/commands/grok-install.js +1 -1
- package/dist/cli/commands/hermes-install.js +11 -5
- package/dist/cli/commands/openclaw-install.js +13 -7
- package/dist/cli/commands/opencode-install.js +7 -4
- package/dist/cli/commands/suit-monitor.d.ts +30 -0
- package/dist/cli/commands/suit-monitor.js +128 -0
- package/dist/cli/index.js +52 -15
- package/dist/cli/suit-pulse.d.ts +29 -0
- package/dist/cli/suit-pulse.js +270 -0
- package/dist/delegation/agent-delegator.d.ts +4 -2
- package/dist/delegation/agent-delegator.js +46 -27
- package/dist/delegation/agent-expertise.js +2 -1
- package/dist/delegation/analytics/outcome-tracker.d.ts +6 -0
- package/dist/delegation/analytics/outcome-tracker.js +17 -3
- package/dist/delegation/ast-code-parser.d.ts +5 -0
- package/dist/delegation/ast-code-parser.js +53 -48
- package/dist/delegation/codebase-context-analyzer.d.ts +3 -7
- package/dist/delegation/codebase-context-analyzer.js +188 -159
- package/dist/delegation/session-coordinator.js +3 -2
- package/dist/enforcement/loaders/codex-loader.js +18 -14
- package/dist/enforcement/validators/architecture-validators.js +115 -9
- package/dist/governance/codex-policy.service.d.ts +12 -2
- package/dist/governance/codex-policy.service.js +91 -16
- package/dist/integrations/grok/hooks/session-start.js +6 -6
- package/dist/integrations/hooks/goggles-pipeline.mjs +169 -28
- package/dist/integrations/hooks/goggles-planes.json +172 -8
- package/dist/integrations/hooks/plates.cjs +138 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
- package/dist/integrations/hooks/write-home.cjs +59 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/architect-tools.server.d.ts +6 -0
- package/dist/mcps/architect-tools.server.js +19 -18
- package/dist/mcps/orchestrator/server.d.ts +4 -3
- package/dist/mcps/orchestrator/server.js +29 -17
- package/dist/memory-routing/lookup-cache.d.ts +8 -0
- package/dist/memory-routing/lookup-cache.js +72 -0
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +26 -0
- package/dist/memory-routing/provider-loader.js +3 -0
- package/dist/metrics/agent-metrics.d.ts +14 -5
- package/dist/metrics/agent-metrics.js +362 -224
- package/dist/nucleus/synthesis-consult-receipt.js +38 -16
- package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
- package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
- package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
- package/dist/orchestrator/orchestrator.js +1 -2
- package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
- package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
- package/dist/processors/implementations/console-log-guard-processor.js +39 -50
- package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
- package/dist/processors/implementations/performance-budget-processor.js +29 -38
- package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
- package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
- package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
- package/dist/reporting/framework-reporting-system.d.ts +3 -0
- package/dist/reporting/framework-reporting-system.js +61 -8
- package/dist/reporting/log-parser.js +188 -105
- package/dist/reporting/metrics.js +42 -18
- package/dist/security/prompt-security-validator.js +8 -7
- package/dist/security/security-auditor.d.ts +4 -0
- package/dist/security/security-auditor.js +132 -57
- package/dist/security/security-hardener.d.ts +2 -0
- package/dist/security/security-hardener.js +31 -6
- package/dist/security/security-headers.d.ts +2 -0
- package/dist/security/security-headers.js +23 -21
- package/dist/security/security-scanner.js +18 -4
- package/dist/session/session-cleanup-manager.d.ts +2 -0
- package/dist/session/session-cleanup-manager.js +29 -8
- package/dist/session/session-monitor.d.ts +14 -4
- package/dist/session/session-monitor.js +208 -106
- package/dist/session/session-state-manager.d.ts +11 -0
- package/dist/session/session-state-manager.js +90 -8
- package/docs-site/docs/plates/activity-log.md +38 -0
- package/docs-site/docs/plates/cursor-compact.md +35 -0
- package/docs-site/docs/plates/grok-compact.md +35 -0
- package/docs-site/docs/plates/index.md +25 -3
- package/docs-site/docs/plates/inference-files.md +35 -0
- package/docs-site/docs/plates/kept-line.md +35 -0
- package/docs-site/docs/plates/lens-gate.md +35 -0
- package/docs-site/docs/plates/lens-page.md +35 -0
- package/docs-site/docs/plates/notes-page.md +35 -0
- package/docs-site/docs/plates/package-face.md +35 -0
- package/docs-site/docs/plates/payload-heat.md +35 -0
- package/docs-site/docs/plates/pickup-stamp.md +35 -0
- package/docs-site/docs/plates/pre-tool.md +35 -0
- package/docs-site/docs/plates/record-map.md +41 -0
- package/docs-site/docs/plates/reflection-page.md +35 -0
- package/docs-site/docs/plates/review.md +57 -0
- package/docs-site/docs/plates/session-capture.md +38 -0
- package/docs-site/docs/plates/site-manual.md +35 -0
- package/docs-site/docs/plates/stamp-plate.md +35 -0
- package/docs-site/docs/plates/station-card.md +35 -0
- package/docs-site/docs/plates/station-heat.md +35 -0
- package/docs-site/docs/plates/suit-organs.md +38 -0
- package/docs-site/docs/plates/suit-settings.md +35 -0
- package/docs-site/docs/plates/suit-wear.md +37 -0
- package/docs-site/docs/plates/trail-state.md +35 -0
- package/docs-site/docs/plates/work-fresh.md +35 -0
- package/docs-site/docs/plates/write-home.md +38 -0
- package/package.json +3 -2
- package/scripts/node/install-bridges.cjs +302 -78
- package/scripts/node/postinstall.cjs +14 -3
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/architect-tools.server.ts +25 -20
- package/src/mcps/orchestrator/server.ts +33 -19
- package/xray/features.json +1 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
- package/vendor/@0xray/repertoire/dist/types.js.map +0 -1
|
@@ -72,24 +72,46 @@ export function hasValidSynthesisConsultReceipt(todoId, projectRoot = process.cw
|
|
|
72
72
|
return false;
|
|
73
73
|
return validateSynthesisConsultReceipt(receipt, todoId, expected);
|
|
74
74
|
}
|
|
75
|
+
function verdictFromToken(token) {
|
|
76
|
+
const value = token.toLowerCase();
|
|
77
|
+
if (value === 'approve' || value === 'approved' || value === 'pass')
|
|
78
|
+
return 'PASS';
|
|
79
|
+
if (value === 'reject' || value === 'rejected' || value === 'fail')
|
|
80
|
+
return 'FAIL';
|
|
81
|
+
if (value === 'abstain' ||
|
|
82
|
+
value === 'needs_revision' ||
|
|
83
|
+
value === 'conditional' ||
|
|
84
|
+
value === 'revise') {
|
|
85
|
+
return 'CONDITIONAL';
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/** The last single Verdict or DECISION line wins. A menu line that lists every word does not. */
|
|
90
|
+
function lastExplicitVerdict(text) {
|
|
91
|
+
let found = null;
|
|
92
|
+
for (const line of text.split(/\r?\n/)) {
|
|
93
|
+
const trimmed = line.trim().replace(/^[-*•]\s*/, '');
|
|
94
|
+
const match = trimmed.match(/^(?:Verdict|DECISION):\s*(\w+)/i);
|
|
95
|
+
const token = match?.[1];
|
|
96
|
+
if (!token || trimmed.includes('|'))
|
|
97
|
+
continue;
|
|
98
|
+
const verdict = verdictFromToken(token);
|
|
99
|
+
if (verdict)
|
|
100
|
+
found = verdict;
|
|
101
|
+
}
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
75
104
|
export function parseConsultVerdictFromText(text) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return 'FAIL';
|
|
85
|
-
}
|
|
86
|
-
if (decision === 'abstain' ||
|
|
87
|
-
decision === 'needs_revision' ||
|
|
88
|
-
decision === 'conditional' ||
|
|
89
|
-
decision === 'revise') {
|
|
90
|
-
return 'CONDITIONAL';
|
|
91
|
-
}
|
|
105
|
+
const explicit = lastExplicitVerdict(text);
|
|
106
|
+
if (explicit)
|
|
107
|
+
return explicit;
|
|
108
|
+
const decisionToken = text.match(/\bDECISION:\s*(\w+)/i)?.[1];
|
|
109
|
+
if (decisionToken) {
|
|
110
|
+
const mapped = verdictFromToken(decisionToken);
|
|
111
|
+
if (mapped)
|
|
112
|
+
return mapped;
|
|
92
113
|
}
|
|
114
|
+
const normalized = text.toUpperCase();
|
|
93
115
|
if (/\bCONDITIONAL(\s+PASS)?\b/.test(normalized))
|
|
94
116
|
return 'CONDITIONAL';
|
|
95
117
|
if (/\b(?:PASS|SHIP|APPROVE)\b/.test(normalized))
|
|
@@ -120,6 +120,11 @@ export declare class EnhancedMultiAgentOrchestrator {
|
|
|
120
120
|
* Check for pending spawns that can now execute
|
|
121
121
|
*/
|
|
122
122
|
private checkPendingSpawns;
|
|
123
|
+
/**
|
|
124
|
+
* Lookup one monitored agent without copying every spawn map.
|
|
125
|
+
* Precedence matches getMonitoringInterface: failed, then completed, then active.
|
|
126
|
+
*/
|
|
127
|
+
getMonitoredAgent(agentId: string): SpawnedAgent | undefined;
|
|
123
128
|
/**
|
|
124
129
|
* Get clickable agent monitoring interface
|
|
125
130
|
*/
|
|
@@ -302,6 +302,15 @@ export class EnhancedMultiAgentOrchestrator {
|
|
|
302
302
|
this.spawnAgent(spawn);
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Lookup one monitored agent without copying every spawn map.
|
|
307
|
+
* Precedence matches getMonitoringInterface: failed, then completed, then active.
|
|
308
|
+
*/
|
|
309
|
+
getMonitoredAgent(agentId) {
|
|
310
|
+
return (this.state.failedAgents.get(agentId) ??
|
|
311
|
+
this.state.completedAgents.get(agentId) ??
|
|
312
|
+
this.state.activeAgents.get(agentId));
|
|
313
|
+
}
|
|
305
314
|
/**
|
|
306
315
|
* Get clickable agent monitoring interface
|
|
307
316
|
*/
|
|
@@ -210,8 +210,7 @@ export class MultiAgentOrchestrationCoordinator {
|
|
|
210
210
|
// Wait for completion
|
|
211
211
|
return new Promise((resolve) => {
|
|
212
212
|
const checkCompletion = () => {
|
|
213
|
-
const
|
|
214
|
-
const agent = monitoringData[spawnedAgent.id];
|
|
213
|
+
const agent = this.enhancedOrchestrator.getMonitoredAgent(spawnedAgent.id);
|
|
215
214
|
if (agent?.status === "completed") {
|
|
216
215
|
resolve({
|
|
217
216
|
success: true,
|
|
@@ -470,8 +470,7 @@ export class XrayOrchestrator {
|
|
|
470
470
|
// Wait for agent completion with monitoring
|
|
471
471
|
return new Promise((resolve, reject) => {
|
|
472
472
|
const checkCompletion = () => {
|
|
473
|
-
const
|
|
474
|
-
const agent = monitoringData[spawnedAgent.id];
|
|
473
|
+
const agent = enhancedMultiAgentOrchestrator.getMonitoredAgent(spawnedAgent.id);
|
|
475
474
|
if (!agent) {
|
|
476
475
|
reject(new Error(`Agent ${spawnedAgent.id} not found in monitoring data`));
|
|
477
476
|
return;
|
|
@@ -176,18 +176,7 @@ export class AgentsMdValidationProcessor {
|
|
|
176
176
|
const now = new Date();
|
|
177
177
|
return Math.ceil((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
|
|
178
178
|
}
|
|
179
|
-
getPackageVersion() {
|
|
180
|
-
try {
|
|
181
|
-
const pkgPath = path.join(this.projectRoot, "package.json");
|
|
182
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
183
|
-
return pkg.version || "0.0.0";
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return "0.0.0";
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
179
|
getSectionTemplate(section) {
|
|
190
|
-
const version = this.getPackageVersion();
|
|
191
180
|
const now = new Date().toISOString().split("T")[0];
|
|
192
181
|
const templates = {
|
|
193
182
|
"## Available Agents": [
|
|
@@ -11,7 +11,13 @@ import { frameworkLogger } from "../../core/framework-logger.js";
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
// Processor
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
|
-
const
|
|
14
|
+
const CONSOLE_METHOD_PATTERNS = [
|
|
15
|
+
{ method: "log", pattern: /\bconsole\.log\s*\(/ },
|
|
16
|
+
{ method: "warn", pattern: /\bconsole\.warn\s*\(/ },
|
|
17
|
+
{ method: "error", pattern: /\bconsole\.error\s*\(/ },
|
|
18
|
+
{ method: "info", pattern: /\bconsole\.info\s*\(/ },
|
|
19
|
+
{ method: "debug", pattern: /\bconsole\.debug\s*\(/ },
|
|
20
|
+
];
|
|
15
21
|
const TEST_FILE_PATTERN = /\.(test|spec)\.ts$/;
|
|
16
22
|
export class ConsoleLogGuardProcessor {
|
|
17
23
|
/**
|
|
@@ -34,8 +40,7 @@ export class ConsoleLogGuardProcessor {
|
|
|
34
40
|
const line = lines[i];
|
|
35
41
|
if (!line)
|
|
36
42
|
continue;
|
|
37
|
-
for (const method of
|
|
38
|
-
const pattern = new RegExp(`\\bconsole\\.${method}\\s*\\(`, "g");
|
|
43
|
+
for (const { method, pattern } of CONSOLE_METHOD_PATTERNS) {
|
|
39
44
|
if (pattern.test(line)) {
|
|
40
45
|
violations.push({
|
|
41
46
|
line: lineNum,
|
|
@@ -58,75 +63,59 @@ export class ConsoleLogGuardProcessor {
|
|
|
58
63
|
* Preserves line structure so line numbers remain valid.
|
|
59
64
|
*/
|
|
60
65
|
stripComments(content) {
|
|
61
|
-
|
|
66
|
+
const parts = [];
|
|
62
67
|
let i = 0;
|
|
68
|
+
let chunkStart = 0;
|
|
63
69
|
const len = content.length;
|
|
70
|
+
const flush = (end) => {
|
|
71
|
+
if (end > chunkStart)
|
|
72
|
+
parts.push(content.slice(chunkStart, end));
|
|
73
|
+
chunkStart = end;
|
|
74
|
+
};
|
|
64
75
|
while (i < len) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Keep the newline character to preserve line numbers
|
|
72
|
-
if (i < len && content[i] === "\n") {
|
|
73
|
-
result += "\n";
|
|
76
|
+
const c = content[i];
|
|
77
|
+
const next = i + 1 < len ? content[i + 1] : "";
|
|
78
|
+
// Single-line comment: // ... newline. Newline stays in the next chunk.
|
|
79
|
+
if (c === "/" && next === "/") {
|
|
80
|
+
flush(i);
|
|
81
|
+
while (i < len && content[i] !== "\n")
|
|
74
82
|
i++;
|
|
75
|
-
|
|
83
|
+
chunkStart = i;
|
|
76
84
|
}
|
|
77
|
-
// Multi-line comment: /* ... */
|
|
78
|
-
else if (
|
|
79
|
-
i
|
|
85
|
+
// Multi-line comment: /* ... */ — keep newlines so line numbers hold.
|
|
86
|
+
else if (c === "/" && next === "*") {
|
|
87
|
+
flush(i);
|
|
88
|
+
i += 2;
|
|
80
89
|
while (i < len) {
|
|
81
90
|
if (content[i] === "*" && i + 1 < len && content[i + 1] === "/") {
|
|
82
|
-
i += 2;
|
|
91
|
+
i += 2;
|
|
83
92
|
break;
|
|
84
93
|
}
|
|
85
|
-
if (content[i] === "\n")
|
|
86
|
-
|
|
87
|
-
}
|
|
94
|
+
if (content[i] === "\n")
|
|
95
|
+
parts.push("\n");
|
|
88
96
|
i++;
|
|
89
97
|
}
|
|
98
|
+
chunkStart = i;
|
|
90
99
|
}
|
|
91
|
-
// String literal —
|
|
92
|
-
else if (
|
|
93
|
-
const quote =
|
|
94
|
-
result += content[i];
|
|
100
|
+
// String literal — walk past it so comment markers inside stay code.
|
|
101
|
+
else if (c === '"' || c === "'" || c === "`") {
|
|
102
|
+
const quote = c;
|
|
95
103
|
i++;
|
|
96
104
|
while (i < len && content[i] !== quote) {
|
|
97
|
-
if (content[i] === "\\" && i + 1 < len)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
result += content[i];
|
|
105
|
+
if (content[i] === "\\" && i + 1 < len)
|
|
106
|
+
i += 2;
|
|
107
|
+
else
|
|
101
108
|
i++;
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
if (content[i] === "\n") {
|
|
105
|
-
result += "\n";
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
result += content[i];
|
|
109
|
-
}
|
|
110
|
-
i++;
|
|
111
|
-
}
|
|
112
109
|
}
|
|
113
|
-
if (i < len)
|
|
114
|
-
result += content[i]; // closing quote
|
|
110
|
+
if (i < len)
|
|
115
111
|
i++;
|
|
116
|
-
}
|
|
117
112
|
}
|
|
118
|
-
// Regular character
|
|
119
113
|
else {
|
|
120
|
-
if (content[i] === "\n") {
|
|
121
|
-
result += "\n";
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
result += content[i];
|
|
125
|
-
}
|
|
126
114
|
i++;
|
|
127
115
|
}
|
|
128
116
|
}
|
|
129
|
-
|
|
117
|
+
flush(len);
|
|
118
|
+
return parts.join("");
|
|
130
119
|
}
|
|
131
120
|
}
|
|
132
121
|
// ---------------------------------------------------------------------------
|
|
@@ -40,10 +40,5 @@ export declare class PerformanceBudgetProcessor {
|
|
|
40
40
|
* Does NOT require a full AST – keeps the processor zero-dependency.
|
|
41
41
|
*/
|
|
42
42
|
private extractFunctions;
|
|
43
|
-
/**
|
|
44
|
-
* Measure the maximum nesting depth by tracking control-flow keywords
|
|
45
|
-
* (if / for / while / switch / try) inside the function body.
|
|
46
|
-
*/
|
|
47
|
-
private measureMaxNesting;
|
|
48
43
|
}
|
|
49
44
|
export declare function runPerformanceBudgetCheck(context: PreValidateContext): Promise<ProcessorExecutionResult>;
|
|
@@ -43,8 +43,9 @@ export class PerformanceBudgetProcessor {
|
|
|
43
43
|
}
|
|
44
44
|
// --- function-level checks ---
|
|
45
45
|
const funcViolations = this.checkFunctionComplexity(content);
|
|
46
|
-
for (const
|
|
47
|
-
|
|
46
|
+
for (const violation of funcViolations) {
|
|
47
|
+
violation.filePath = filePath;
|
|
48
|
+
violations.push(violation);
|
|
48
49
|
}
|
|
49
50
|
return violations;
|
|
50
51
|
}
|
|
@@ -67,7 +68,7 @@ export class PerformanceBudgetProcessor {
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
// nesting depth
|
|
70
|
-
const depth =
|
|
71
|
+
const depth = fn.nestingDepth;
|
|
71
72
|
if (depth > this.config.maxNestingDepth) {
|
|
72
73
|
violations.push({
|
|
73
74
|
type: "nestingTooDeep",
|
|
@@ -123,64 +124,54 @@ export class PerformanceBudgetProcessor {
|
|
|
123
124
|
const paramCount = params
|
|
124
125
|
? params.split(",").filter((p) => p.trim().length > 0).length
|
|
125
126
|
: 0;
|
|
126
|
-
//
|
|
127
|
+
// Walk the body once: brace span, line count, and nesting share the same lines.
|
|
128
|
+
// Blank lines are skipped, matching the previous join-then-split of kept lines.
|
|
127
129
|
let depth = 0;
|
|
128
130
|
let started = false;
|
|
129
|
-
|
|
131
|
+
let bodyLineCount = 0;
|
|
132
|
+
let maxDepth = 0;
|
|
133
|
+
let currentDepth = 0;
|
|
130
134
|
for (let j = i; j < lines.length; j++) {
|
|
131
135
|
const l = lines[j];
|
|
132
136
|
if (!l)
|
|
133
137
|
continue;
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
bodyLineCount++;
|
|
139
|
+
for (let k = 0; k < l.length; k++) {
|
|
140
|
+
const ch = l.charCodeAt(k);
|
|
141
|
+
if (ch === 123) {
|
|
136
142
|
depth++;
|
|
137
143
|
started = true;
|
|
138
144
|
}
|
|
139
|
-
else if (ch ===
|
|
145
|
+
else if (ch === 125) {
|
|
140
146
|
depth--;
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
|
-
|
|
149
|
+
const trimmed = l.trim();
|
|
150
|
+
const openingMatches = trimmed.match(/\b(if|for|while|switch|try|catch|else)\b/g);
|
|
151
|
+
if (openingMatches) {
|
|
152
|
+
// 'else' and 'catch' partially close+reopen, so net +0 for those
|
|
153
|
+
const netOpen = openingMatches.length -
|
|
154
|
+
(trimmed.includes("else") ? 1 : 0) -
|
|
155
|
+
(trimmed.includes("catch") ? 1 : 0);
|
|
156
|
+
currentDepth += Math.max(0, netOpen);
|
|
157
|
+
if (currentDepth > maxDepth)
|
|
158
|
+
maxDepth = currentDepth;
|
|
159
|
+
}
|
|
160
|
+
const closeBraces = (trimmed.match(/}/g) || []).length;
|
|
161
|
+
currentDepth = Math.max(0, currentDepth - closeBraces);
|
|
144
162
|
if (started && depth <= 0)
|
|
145
163
|
break;
|
|
146
164
|
}
|
|
147
|
-
const body = bodyLines.join("\n");
|
|
148
165
|
results.push({
|
|
149
166
|
name,
|
|
150
167
|
startLine: i + 1,
|
|
151
|
-
|
|
152
|
-
bodyLineCount: bodyLines.length,
|
|
168
|
+
bodyLineCount,
|
|
153
169
|
paramCount,
|
|
170
|
+
nestingDepth: maxDepth,
|
|
154
171
|
});
|
|
155
172
|
}
|
|
156
173
|
return results;
|
|
157
174
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Measure the maximum nesting depth by tracking control-flow keywords
|
|
160
|
-
* (if / for / while / switch / try) inside the function body.
|
|
161
|
-
*/
|
|
162
|
-
measureMaxNesting(body) {
|
|
163
|
-
let maxDepth = 0;
|
|
164
|
-
let currentDepth = 0;
|
|
165
|
-
const lines = body.split("\n");
|
|
166
|
-
for (const line of lines) {
|
|
167
|
-
const trimmed = line.trim();
|
|
168
|
-
// Count opening structures that increase nesting
|
|
169
|
-
const openingMatches = trimmed.match(/\b(if|for|while|switch|try|catch|else)\b/g);
|
|
170
|
-
if (openingMatches) {
|
|
171
|
-
// 'else' and 'catch' partially close+reopen, so net +0 for those
|
|
172
|
-
const netOpen = openingMatches.length -
|
|
173
|
-
(trimmed.includes("else") ? 1 : 0) -
|
|
174
|
-
(trimmed.includes("catch") ? 1 : 0);
|
|
175
|
-
currentDepth += Math.max(0, netOpen);
|
|
176
|
-
maxDepth = Math.max(maxDepth, currentDepth);
|
|
177
|
-
}
|
|
178
|
-
// Simple heuristic: decrease depth when line closes braces
|
|
179
|
-
const closeBraces = (trimmed.match(/}/g) || []).length;
|
|
180
|
-
currentDepth = Math.max(0, currentDepth - closeBraces);
|
|
181
|
-
}
|
|
182
|
-
return maxDepth;
|
|
183
|
-
}
|
|
184
175
|
}
|
|
185
176
|
// ---------------------------------------------------------------------------
|
|
186
177
|
// Standalone runner for processor-manager integration
|
|
@@ -17,6 +17,11 @@ export declare class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
17
17
|
private extractDecisions;
|
|
18
18
|
private synthesizeInferences;
|
|
19
19
|
private getCommitsBetween;
|
|
20
|
+
/**
|
|
21
|
+
* One `git log --name-only` for the range. Same paths as `git diff --name-only HASH~1 HASH`
|
|
22
|
+
* for non-merge commits. Null means the caller should fall back per commit.
|
|
23
|
+
*/
|
|
24
|
+
private fileNamesByCommit;
|
|
20
25
|
private getRecentCommits;
|
|
21
26
|
private summarizeCommits;
|
|
22
27
|
private getLastTag;
|
|
@@ -156,8 +156,13 @@ export class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
156
156
|
if (diff.uniqueDirs.length > 0) {
|
|
157
157
|
lines.push("## Areas Touched");
|
|
158
158
|
lines.push("");
|
|
159
|
+
const touched = diff.filesAdded.concat(diff.filesModified);
|
|
159
160
|
for (const dir of diff.uniqueDirs.slice(0, 15)) {
|
|
160
|
-
|
|
161
|
+
let count = 0;
|
|
162
|
+
for (const fileName of touched) {
|
|
163
|
+
if (fileName.startsWith(dir))
|
|
164
|
+
count++;
|
|
165
|
+
}
|
|
161
166
|
lines.push(`- \`${dir}\` (${count} files)`);
|
|
162
167
|
}
|
|
163
168
|
lines.push("");
|
|
@@ -370,12 +375,16 @@ export class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
370
375
|
}
|
|
371
376
|
return inferences;
|
|
372
377
|
}
|
|
373
|
-
getCommitsBetween(from, to) {
|
|
378
|
+
getCommitsBetween(from, to, limit) {
|
|
374
379
|
try {
|
|
375
|
-
|
|
380
|
+
// Newest-first, so -n matches slice(0, limit). %x1e starts every commit, including
|
|
381
|
+
// one with no shortstat — a blank-line split would glue that header to the next.
|
|
382
|
+
const limitArg = typeof limit === "number" && limit > 0 ? `-n ${Math.floor(limit)} ` : "";
|
|
383
|
+
const log = this.git(`git log ${limitArg}${from}..${to} --format="%x1e%H||%s||%an||%aI" --shortstat --no-merges`);
|
|
376
384
|
if (!log)
|
|
377
385
|
return [];
|
|
378
|
-
const
|
|
386
|
+
const namesByHash = this.fileNamesByCommit(from, to, limit);
|
|
387
|
+
const commitBlocks = log.split("\x1e").filter((b) => b.includes("||"));
|
|
379
388
|
return commitBlocks.map((block) => {
|
|
380
389
|
const lines = block.trim().split("\n");
|
|
381
390
|
const headerLine = lines.find((l) => l.includes("||")) || "";
|
|
@@ -384,15 +393,21 @@ export class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
384
393
|
const filesChanged = parseInt(statsLine.match(/(\d+) files? changed/)?.[1] || "0", 10);
|
|
385
394
|
const insertions = parseInt(statsLine.match(/(\d+) insertion/)?.[1] || "0", 10);
|
|
386
395
|
const deletions = parseInt(statsLine.match(/(\d+) deletion/)?.[1] || "0", 10);
|
|
396
|
+
const fullHash = (hash || "").trim();
|
|
387
397
|
let fileNames = [];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
398
|
+
if (namesByHash?.has(fullHash)) {
|
|
399
|
+
fileNames = namesByHash.get(fullHash) ?? [];
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
try {
|
|
403
|
+
const shortHash = fullHash.slice(0, 7);
|
|
404
|
+
const namesOutput = this.git(`git diff --name-only ${shortHash}~1 ${shortHash} 2>/dev/null`);
|
|
405
|
+
fileNames = namesOutput ? namesOutput.split("\n").filter(Boolean) : [];
|
|
406
|
+
}
|
|
407
|
+
catch { /* empty */ }
|
|
392
408
|
}
|
|
393
|
-
catch { /* empty */ }
|
|
394
409
|
return {
|
|
395
|
-
hash:
|
|
410
|
+
hash: fullHash.slice(0, 7),
|
|
396
411
|
message: message || "",
|
|
397
412
|
author: author || "",
|
|
398
413
|
date: date || "",
|
|
@@ -407,13 +422,44 @@ export class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
407
422
|
return [];
|
|
408
423
|
}
|
|
409
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* One `git log --name-only` for the range. Same paths as `git diff --name-only HASH~1 HASH`
|
|
427
|
+
* for non-merge commits. Null means the caller should fall back per commit.
|
|
428
|
+
*/
|
|
429
|
+
fileNamesByCommit(from, to, limit) {
|
|
430
|
+
const limitArg = typeof limit === "number" && limit > 0 ? `-n ${Math.floor(limit)} ` : "";
|
|
431
|
+
const out = this.git(`git log ${limitArg}${from}..${to} --no-merges --name-only --pretty=format:%x1e%H`);
|
|
432
|
+
if (!out)
|
|
433
|
+
return null;
|
|
434
|
+
const names = new Map();
|
|
435
|
+
for (const chunk of out.split("\x1e")) {
|
|
436
|
+
if (chunk.trim().length === 0)
|
|
437
|
+
continue;
|
|
438
|
+
let hash = "";
|
|
439
|
+
const files = [];
|
|
440
|
+
for (const line of chunk.split("\n")) {
|
|
441
|
+
if (!hash) {
|
|
442
|
+
if (/^[0-9a-f]{40}$/.test(line))
|
|
443
|
+
hash = line;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (line.length > 0)
|
|
447
|
+
files.push(line);
|
|
448
|
+
}
|
|
449
|
+
if (hash)
|
|
450
|
+
names.set(hash, files);
|
|
451
|
+
}
|
|
452
|
+
return names;
|
|
453
|
+
}
|
|
410
454
|
getRecentCommits(count) {
|
|
411
455
|
try {
|
|
456
|
+
if (!(count > 0))
|
|
457
|
+
return [];
|
|
412
458
|
const hash = this.git("git rev-list --max-parents=0 HEAD");
|
|
413
459
|
if (!hash)
|
|
414
460
|
return [];
|
|
415
461
|
const firstCommit = hash.split("\n")[0].trim();
|
|
416
|
-
return this.getCommitsBetween(firstCommit, "HEAD"
|
|
462
|
+
return this.getCommitsBetween(firstCommit, "HEAD", count);
|
|
417
463
|
}
|
|
418
464
|
catch {
|
|
419
465
|
return [];
|
|
@@ -453,11 +499,18 @@ export class StorytellingTriggerProcessor extends PostProcessor {
|
|
|
453
499
|
filesModified.push(file);
|
|
454
500
|
}
|
|
455
501
|
}
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
502
|
+
const dirSet = new Set();
|
|
503
|
+
const addDir = (fileName) => {
|
|
504
|
+
const slash = fileName.lastIndexOf("/");
|
|
505
|
+
dirSet.add(slash >= 0 ? fileName.slice(0, slash) : ".");
|
|
506
|
+
};
|
|
507
|
+
for (const fileName of filesAdded)
|
|
508
|
+
addDir(fileName);
|
|
509
|
+
for (const fileName of filesModified)
|
|
510
|
+
addDir(fileName);
|
|
511
|
+
for (const fileName of filesDeleted)
|
|
512
|
+
addDir(fileName);
|
|
513
|
+
const uniqueDirs = [...dirSet].sort();
|
|
461
514
|
return {
|
|
462
515
|
totalCommits: commits.length,
|
|
463
516
|
totalFilesChanged: allFiles.size,
|
|
@@ -28,55 +28,58 @@ function findRecentlyModifiedTsFile(dir, maxAgeSeconds = 60) {
|
|
|
28
28
|
return cached.file;
|
|
29
29
|
}
|
|
30
30
|
try {
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// One stat per source file. Dirent tells files from directories; mtime is kept
|
|
32
|
+
// on that stat so the walk does not stat the same path again.
|
|
33
|
+
// Holder avoids TS narrowing the binding to null across the closure.
|
|
34
|
+
const mostRecent = { path: null, mtime: 0 };
|
|
35
|
+
const shouldWalk = (name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist";
|
|
36
|
+
const isTsSource = (name) => name.endsWith(".ts") && !name.endsWith(".test.ts") && !name.endsWith(".spec.ts");
|
|
37
|
+
const consider = (fullPath, mtimeMs) => {
|
|
38
|
+
const relPath = path.relative(dir, fullPath);
|
|
39
|
+
if (mostRecent.path === null || mtimeMs > mostRecent.mtime) {
|
|
40
|
+
mostRecent.path = relPath;
|
|
41
|
+
mostRecent.mtime = mtimeMs;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
33
44
|
function walkSync(dirPath) {
|
|
45
|
+
let items;
|
|
34
46
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
items = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const name = item.name;
|
|
54
|
+
const fullPath = path.join(dirPath, name);
|
|
55
|
+
try {
|
|
56
|
+
// stat follows links, so a symlink to a directory is still walked.
|
|
57
|
+
if (item.isSymbolicLink()) {
|
|
39
58
|
const stat = fs.statSync(fullPath);
|
|
40
59
|
if (stat.isDirectory()) {
|
|
41
|
-
|
|
42
|
-
if (!item.startsWith(".") &&
|
|
43
|
-
item !== "node_modules" &&
|
|
44
|
-
item !== "dist") {
|
|
60
|
+
if (shouldWalk(name))
|
|
45
61
|
walkSync(fullPath);
|
|
46
|
-
}
|
|
47
62
|
}
|
|
48
|
-
else if (
|
|
49
|
-
|
|
50
|
-
!item.endsWith(".spec.ts")) {
|
|
51
|
-
files.push(fullPath);
|
|
63
|
+
else if (isTsSource(name)) {
|
|
64
|
+
consider(fullPath, stat.mtimeMs);
|
|
52
65
|
}
|
|
66
|
+
continue;
|
|
53
67
|
}
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
if (item.isDirectory()) {
|
|
69
|
+
if (shouldWalk(name))
|
|
70
|
+
walkSync(fullPath);
|
|
71
|
+
}
|
|
72
|
+
else if (isTsSource(name)) {
|
|
73
|
+
consider(fullPath, fs.statSync(fullPath).mtimeMs);
|
|
56
74
|
}
|
|
57
75
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Skip directories we can't read
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
walkSync(dir);
|
|
64
|
-
// Find most recently modified
|
|
65
|
-
let mostRecent = null;
|
|
66
|
-
for (const filePath of files) {
|
|
67
|
-
try {
|
|
68
|
-
const stats = fs.statSync(filePath);
|
|
69
|
-
// Get relative path
|
|
70
|
-
const relPath = path.relative(dir, filePath);
|
|
71
|
-
if (!mostRecent || stats.mtimeMs > mostRecent.mtime) {
|
|
72
|
-
mostRecent = { path: relPath, mtime: stats.mtimeMs };
|
|
76
|
+
catch {
|
|
77
|
+
// Skip files we can't stat
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
|
-
catch {
|
|
76
|
-
// Skip files we can't stat
|
|
77
|
-
}
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
walkSync(dir);
|
|
82
|
+
const result = mostRecent.path;
|
|
80
83
|
// Cache the result
|
|
81
84
|
fileScanCache.set(cacheKey, {
|
|
82
85
|
file: result,
|
|
@@ -8,6 +8,9 @@ export declare class FrameworkReportingSystem {
|
|
|
8
8
|
getRealtimeStatus(): Promise<RealtimeStatus>;
|
|
9
9
|
createCustomReport(template: CustomReportTemplate): string;
|
|
10
10
|
private generateReportId;
|
|
11
|
+
private activityLogFile;
|
|
12
|
+
private currentLogStamp;
|
|
13
|
+
private memoryLogStamp;
|
|
11
14
|
private getCachedReport;
|
|
12
15
|
private isCacheValid;
|
|
13
16
|
private cacheReport;
|