@arcadialdev/arcality 4.0.2 → 4.1.1

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 (36) hide show
  1. package/.agents/skills/db-validation-evidence/SKILL.md +43 -0
  2. package/.agents/skills/db-validation-evidence/agents/openai.yaml +4 -0
  3. package/.agents/skills/db-validation-evidence/references/mission-patterns.md +130 -0
  4. package/.agents/skills/evidence-synthesis-reporting/SKILL.md +31 -0
  5. package/.agents/skills/form-expert/SKILL.md +98 -0
  6. package/.agents/skills/investigation-protocol/SKILL.md +56 -0
  7. package/.agents/skills/mission-generation-reviewer/SKILL.md +25 -0
  8. package/.agents/skills/modal-master/SKILL.md +46 -0
  9. package/.agents/skills/native-control-expert/SKILL.md +74 -0
  10. package/.agents/skills/qa-context-governance/SKILL.md +23 -0
  11. package/.agents/skills/security-evidence-triage/SKILL.md +23 -0
  12. package/.agents/skills/test-data-lifecycle/SKILL.md +24 -0
  13. package/README.md +103 -163
  14. package/bin/arcality.mjs +25 -25
  15. package/package.json +75 -75
  16. package/scripts/edit-config.mjs +843 -0
  17. package/scripts/gen-and-run.mjs +2705 -2609
  18. package/scripts/generate.mjs +236 -236
  19. package/scripts/init.mjs +47 -47
  20. package/src/configManager.mjs +13 -13
  21. package/src/envSetup.ts +229 -205
  22. package/src/services/codebaseAnalyzer.mjs +59 -59
  23. package/src/services/databaseValidationService.mjs +598 -0
  24. package/src/services/executionEvidenceService.mjs +124 -0
  25. package/src/services/generatedMissionSchema.mjs +76 -76
  26. package/src/services/generatedMissionStore.mjs +117 -117
  27. package/src/services/generationContext.mjs +242 -242
  28. package/src/services/mcpStdioClient.mjs +204 -0
  29. package/src/services/missionGenerator.mjs +329 -329
  30. package/src/services/routeDiscovery.mjs +762 -762
  31. package/tests/_helpers/ArcalityReporter.js +1342 -1255
  32. package/tests/_helpers/agentic-runner.bundle.spec.js +1363 -245
  33. package/.agent/skills/form-expert.md +0 -102
  34. package/.agent/skills/investigation-protocol.md +0 -61
  35. package/.agent/skills/modal-master.md +0 -41
  36. package/.agent/skills/native-control-expert.md +0 -82
@@ -0,0 +1,204 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+
4
+ const MCP_PROTOCOL_VERSION = '2024-11-05';
5
+
6
+ function isPlainObject(value) {
7
+ return !!value && typeof value === 'object' && !Array.isArray(value);
8
+ }
9
+
10
+ function getNestedValue(source, dottedPath) {
11
+ return String(dottedPath || '')
12
+ .split('.')
13
+ .filter(Boolean)
14
+ .reduce((current, key) => (current && Object.prototype.hasOwnProperty.call(current, key) ? current[key] : undefined), source);
15
+ }
16
+
17
+ function replaceEnvTokens(value, envSource = process.env) {
18
+ return String(value).replace(/\$\{([A-Z0-9_]+)\}/gi, (_, envName) => {
19
+ const resolved = envSource[envName];
20
+ return resolved === undefined ? '' : String(resolved);
21
+ });
22
+ }
23
+
24
+ export function resolveTemplateValue(value, context = {}, envSource = process.env) {
25
+ if (typeof value === 'string') {
26
+ const envResolved = replaceEnvTokens(value, envSource);
27
+ const exactMatch = envResolved.match(/^\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}$/);
28
+ if (exactMatch) {
29
+ return getNestedValue(context, exactMatch[1]);
30
+ }
31
+
32
+ return envResolved.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_, tokenPath) => {
33
+ const resolved = getNestedValue(context, tokenPath);
34
+ if (resolved === undefined || resolved === null) return '';
35
+ if (typeof resolved === 'string' || typeof resolved === 'number' || typeof resolved === 'boolean') {
36
+ return String(resolved);
37
+ }
38
+ return JSON.stringify(resolved);
39
+ });
40
+ }
41
+
42
+ if (Array.isArray(value)) {
43
+ return value.map(entry => resolveTemplateValue(entry, context, envSource));
44
+ }
45
+
46
+ if (isPlainObject(value)) {
47
+ return Object.fromEntries(
48
+ Object.entries(value).map(([key, entryValue]) => [key, resolveTemplateValue(entryValue, context, envSource)])
49
+ );
50
+ }
51
+
52
+ return value;
53
+ }
54
+
55
+ export function extractRowsFromMcpToolResult(result, preferredRowsPath = '') {
56
+ const candidate = preferredRowsPath ? getNestedValue(result, preferredRowsPath) : undefined;
57
+ if (Array.isArray(candidate)) return candidate;
58
+ if (Array.isArray(result?.rows)) return result.rows;
59
+
60
+ const content = Array.isArray(result?.content) ? result.content : [];
61
+ for (const item of content) {
62
+ if (!item || item.type !== 'text' || typeof item.text !== 'string') continue;
63
+ const text = item.text.trim();
64
+ if (!text) continue;
65
+
66
+ try {
67
+ const parsed = JSON.parse(text);
68
+ if (Array.isArray(parsed)) return parsed;
69
+ if (Array.isArray(parsed?.rows)) return parsed.rows;
70
+ if (preferredRowsPath) {
71
+ const nested = getNestedValue(parsed, preferredRowsPath);
72
+ if (Array.isArray(nested)) return nested;
73
+ }
74
+ } catch { }
75
+ }
76
+
77
+ return [];
78
+ }
79
+
80
+ function createFrame(message) {
81
+ const body = Buffer.from(JSON.stringify(message), 'utf8');
82
+ const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'utf8');
83
+ return Buffer.concat([header, body]);
84
+ }
85
+
86
+ function createParser(onMessage) {
87
+ let buffer = Buffer.alloc(0);
88
+
89
+ return chunk => {
90
+ buffer = Buffer.concat([buffer, chunk]);
91
+
92
+ while (true) {
93
+ const separatorIndex = buffer.indexOf('\r\n\r\n');
94
+ if (separatorIndex === -1) return;
95
+
96
+ const headerText = buffer.slice(0, separatorIndex).toString('utf8');
97
+ const headers = headerText.split('\r\n');
98
+ const lengthHeader = headers.find(line => line.toLowerCase().startsWith('content-length:'));
99
+ if (!lengthHeader) {
100
+ throw new Error('Invalid MCP response: missing Content-Length header.');
101
+ }
102
+
103
+ const length = Number(lengthHeader.split(':')[1]?.trim() || 0);
104
+ const bodyStart = separatorIndex + 4;
105
+ const bodyEnd = bodyStart + length;
106
+ if (buffer.length < bodyEnd) return;
107
+
108
+ const body = buffer.slice(bodyStart, bodyEnd).toString('utf8');
109
+ buffer = buffer.slice(bodyEnd);
110
+ onMessage(JSON.parse(body));
111
+ }
112
+ };
113
+ }
114
+
115
+ export async function callMcpToolWithConfig(config, toolContext = {}) {
116
+ if (!config || !config.command) {
117
+ throw new Error('MCP configuration is missing the command field.');
118
+ }
119
+
120
+ const timeoutMs = Number(config.timeoutMs || toolContext.timeoutMs || 15000);
121
+ const command = resolveTemplateValue(config.command, toolContext);
122
+ const args = Array.isArray(config.args) ? resolveTemplateValue(config.args, toolContext) : [];
123
+ const envFromConfig = isPlainObject(config.env) ? resolveTemplateValue(config.env, toolContext) : {};
124
+ const cwd = config.cwd ? resolveTemplateValue(config.cwd, toolContext) : process.cwd();
125
+ const toolName = String(resolveTemplateValue(config.toolName || 'query', toolContext) || '').trim();
126
+ const toolArguments = isPlainObject(config.toolArguments)
127
+ ? resolveTemplateValue(config.toolArguments, toolContext)
128
+ : {};
129
+
130
+ const child = spawn(command, args, {
131
+ cwd: path.isAbsolute(cwd) ? cwd : path.join(process.cwd(), cwd),
132
+ env: { ...process.env, ...envFromConfig },
133
+ stdio: ['pipe', 'pipe', 'pipe'],
134
+ windowsHide: true
135
+ });
136
+
137
+ let requestId = 1;
138
+ const pending = new Map();
139
+ let startupError = '';
140
+
141
+ const parser = createParser(message => {
142
+ if (message.id && pending.has(message.id)) {
143
+ const entry = pending.get(message.id);
144
+ pending.delete(message.id);
145
+ if (message.error) {
146
+ entry.reject(new Error(message.error.message || 'Unknown MCP error.'));
147
+ } else {
148
+ entry.resolve(message.result);
149
+ }
150
+ }
151
+ });
152
+
153
+ child.stdout.on('data', chunk => parser(chunk));
154
+ child.stderr.on('data', chunk => {
155
+ startupError += chunk.toString('utf8');
156
+ });
157
+
158
+ child.on('error', error => {
159
+ for (const [, entry] of pending) entry.reject(error);
160
+ pending.clear();
161
+ });
162
+
163
+ const sendRequest = (method, params) => new Promise((resolve, reject) => {
164
+ const id = requestId++;
165
+ pending.set(id, { resolve, reject });
166
+ child.stdin.write(createFrame({ jsonrpc: '2.0', id, method, params }));
167
+ });
168
+
169
+ const sendNotification = (method, params) => {
170
+ child.stdin.write(createFrame({ jsonrpc: '2.0', method, params }));
171
+ };
172
+
173
+ const timeout = new Promise((_, reject) => {
174
+ setTimeout(() => reject(new Error(`MCP tool call timed out after ${timeoutMs}ms.`)), timeoutMs);
175
+ });
176
+
177
+ try {
178
+ const result = await Promise.race([
179
+ (async () => {
180
+ await sendRequest('initialize', {
181
+ protocolVersion: MCP_PROTOCOL_VERSION,
182
+ capabilities: {},
183
+ clientInfo: { name: 'arcality', version: '4.1.0' }
184
+ });
185
+ sendNotification('notifications/initialized', {});
186
+ return await sendRequest('tools/call', {
187
+ name: toolName,
188
+ arguments: toolArguments
189
+ });
190
+ })(),
191
+ timeout
192
+ ]);
193
+
194
+ return result;
195
+ } catch (error) {
196
+ const details = startupError.trim();
197
+ if (details) {
198
+ throw new Error(`${error.message} MCP stderr: ${details}`);
199
+ }
200
+ throw error;
201
+ } finally {
202
+ child.kill();
203
+ }
204
+ }