archgraph-argo 0.10.43 → 0.10.44
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/LICENSE +201 -201
- package/argo/package.json +8 -8
- package/argo/schema/ImplementationToCodingHandoff.schema.json +251 -251
- package/argo/schema/ImplementationToIntentTraceProposal.schema.json +180 -180
- package/argo/schema/IntentToImplementationHandoff.schema.json +74 -74
- package/argo/schema/SystemArchitecture.schema.json +378 -378
- package/argo/schema/archimate3.2.md +7153 -7153
- package/argo/scripts/archimate32-rules.js +12301 -12301
- package/argo/scripts/generateArchitectureDiffPlantuml.js +466 -466
- package/argo/scripts/graph-rag/ARCHITECTURE.md +192 -192
- package/argo/scripts/graph-rag/canonicalProjectionAuthority.js +45 -45
- package/argo/scripts/graph-rag/embeddingQualificationGate.js +59 -59
- package/argo/scripts/graph-rag/externalProductionConfig.js +74 -74
- package/argo/scripts/graph-rag/liveEmbeddingProviderClient.js +49 -49
- package/argo/scripts/graph-rag/neo4jNativeRetrieval.js +37 -37
- package/argo/scripts/graph-rag/semantic-persistence/ARCHITECTURE.md +51 -51
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticCheckpointStore.js +99 -99
- package/argo/scripts/graph-rag/semantic-persistence/productionSemanticProjectionStore.js +171 -171
- package/argo/scripts/graph-rag/semanticOperatorError.js +38 -38
- package/argo/scripts/graph-rag/semanticOperatorJourney.js +459 -459
- package/argo/scripts/graph-rag/semanticReadinessAttestationStore.js +398 -398
- package/argo/scripts/graph-rag/systemMetadataCommandAdapter.js +269 -269
- package/argo/scripts/graph-semantics.js +220 -220
- package/argo/scripts/repositoryArgoEnvironment.js +101 -101
- package/argo/scripts/runArchitectureTests.js +583 -583
- package/argo/scripts/semanticOperatorJourneyCli.js +91 -91
- package/argo/scripts/syncSystemArchitectureToNeo4j.js +66 -66
- package/argo/scripts/test-executors/_template.js +58 -58
- package/argo/scripts/test-executors/default.js +199 -199
- package/argo/scripts/validateStageHandoff.js +458 -458
- package/argo/scripts/validateSystemArchitecture.js +253 -253
- package/argo/scripts/validateTraceProposal.js +181 -181
- package/bin/argo-deploy.js +12 -12
- package/install-argo.ps1 +27 -8
- package/package.json +1 -1
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Test Executor — file-extension-based execution.
|
|
3
|
-
*
|
|
4
|
-
* This is the built-in executor that handles the original acceptanceCriteria format:
|
|
5
|
-
* a workspace-relative script path, optionally with a pytest node-id selector.
|
|
6
|
-
*
|
|
7
|
-
* Custom executors can be added to this directory; they are auto-discovered by
|
|
8
|
-
* runArchitectureTests.js. Each executor module must export:
|
|
9
|
-
* name: string
|
|
10
|
-
* canHandle(acceptanceCriteria, workspaceRoot): boolean
|
|
11
|
-
* execute(acceptanceCriteria, workspaceRoot): Promise<{exitCode, stdout, stderr}>
|
|
12
|
-
* getCommandPreview(acceptanceCriteria, workspaceRoot): string | null (optional)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const { execFile } = require('child_process');
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { promisify } = require('util');
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
getWorkspaceRoot,
|
|
22
|
-
} = require('../argo-paths.js');
|
|
23
|
-
|
|
24
|
-
const execFileAsync = promisify(execFile);
|
|
25
|
-
|
|
26
|
-
const PYTHON_EXECUTABLE = resolvePythonExecutable();
|
|
27
|
-
const TEST_TIMEOUT_MS = readPositiveInteger(process.env.ARGO_TEST_TIMEOUT_MS, 120000);
|
|
28
|
-
|
|
29
|
-
const SUPPORTED_EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.py', '.ps1', '.cmd', '.bat']);
|
|
30
|
-
|
|
31
|
-
const DISALLOWED_PATTERNS = [
|
|
32
|
-
/[\r\n]/,
|
|
33
|
-
/[|&;<>]/,
|
|
34
|
-
/^['"].*['"]$/,
|
|
35
|
-
/^(?:npm|pnpm|yarn|npx|node|python|py|powershell|pwsh|cmd|bash|sh)\b/i,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// --- Exported interface ---
|
|
39
|
-
|
|
40
|
-
const name = 'default';
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The default executor handles acceptanceCriteria that are workspace-relative
|
|
44
|
-
* script file paths (optionally with ::pytest_node_id selectors).
|
|
45
|
-
*/
|
|
46
|
-
function canHandle(acceptanceCriteria) {
|
|
47
|
-
if (!acceptanceCriteria) return false;
|
|
48
|
-
|
|
49
|
-
for (const pattern of DISALLOWED_PATTERNS) {
|
|
50
|
-
if (pattern.test(acceptanceCriteria)) return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const parsed = parseCriteria(acceptanceCriteria);
|
|
54
|
-
const ext = path.extname(parsed.scriptRelativePath).toLowerCase();
|
|
55
|
-
if (!SUPPORTED_EXTENSIONS.has(ext)) return false;
|
|
56
|
-
|
|
57
|
-
if (parsed.selector && ext !== '.py') return false;
|
|
58
|
-
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getCommandPreview(acceptanceCriteria) {
|
|
63
|
-
const parsed = parseCriteria(acceptanceCriteria);
|
|
64
|
-
if (parsed.selector) {
|
|
65
|
-
return formatCommand('python', ['-m', 'pytest', buildPytestNodeId(parsed)]);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const ext = path.extname(parsed.scriptRelativePath).toLowerCase();
|
|
69
|
-
switch (ext) {
|
|
70
|
-
case '.js': case '.cjs': case '.mjs':
|
|
71
|
-
return formatCommand(process.execPath, [parsed.displayPath || parsed.scriptRelativePath]);
|
|
72
|
-
case '.py':
|
|
73
|
-
return formatCommand('python', [parsed.scriptRelativePath]);
|
|
74
|
-
case '.ps1':
|
|
75
|
-
return formatCommand('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', parsed.scriptRelativePath]);
|
|
76
|
-
case '.cmd': case '.bat':
|
|
77
|
-
return formatCommand(parsed.scriptRelativePath, []);
|
|
78
|
-
default:
|
|
79
|
-
return formatCommand(parsed.scriptRelativePath, []);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function execute(acceptanceCriteria, workspaceRoot) {
|
|
84
|
-
const parsed = parseCriteria(acceptanceCriteria);
|
|
85
|
-
const scriptPath = path.join(workspaceRoot, ...parsed.scriptRelativePath.split('/'));
|
|
86
|
-
|
|
87
|
-
if (!fs.existsSync(scriptPath)) {
|
|
88
|
-
return {
|
|
89
|
-
exitCode: null,
|
|
90
|
-
stdout: '',
|
|
91
|
-
stderr: `test script not found: ${acceptanceCriteria}`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (parsed.selector) {
|
|
96
|
-
return runPythonPytestNodeId(parsed, workspaceRoot);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const ext = path.extname(scriptPath).toLowerCase();
|
|
100
|
-
switch (ext) {
|
|
101
|
-
case '.js': case '.cjs': case '.mjs':
|
|
102
|
-
return runCommand(process.execPath, [scriptPath], workspaceRoot, parsed.fragment
|
|
103
|
-
? { ARGO_TESTCASE_ANCHOR: parsed.fragment }
|
|
104
|
-
: undefined);
|
|
105
|
-
case '.py':
|
|
106
|
-
return runCommand(PYTHON_EXECUTABLE, [scriptPath], workspaceRoot);
|
|
107
|
-
case '.ps1':
|
|
108
|
-
return runCommand('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath], workspaceRoot);
|
|
109
|
-
case '.cmd': case '.bat':
|
|
110
|
-
return runCommand(scriptPath, [], workspaceRoot);
|
|
111
|
-
default:
|
|
112
|
-
return runCommand(scriptPath, [], workspaceRoot);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// --- Internal helpers ---
|
|
117
|
-
|
|
118
|
-
function parseCriteria(value) {
|
|
119
|
-
const [pathAndFragment, ...selectorParts] = value.split('::');
|
|
120
|
-
const hashIndex = pathAndFragment.indexOf('#');
|
|
121
|
-
const scriptRelativePath = hashIndex >= 0 ? pathAndFragment.slice(0, hashIndex) : pathAndFragment;
|
|
122
|
-
const fragment = hashIndex >= 0 ? pathAndFragment.slice(hashIndex + 1).trim() : undefined;
|
|
123
|
-
return {
|
|
124
|
-
scriptRelativePath: normalizePath(scriptRelativePath),
|
|
125
|
-
displayPath: normalizePath(pathAndFragment),
|
|
126
|
-
fragment,
|
|
127
|
-
selector: selectorParts.length > 0 ? selectorParts.join('::').trim() : undefined,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildPytestNodeId(criteria) {
|
|
132
|
-
return criteria.selector
|
|
133
|
-
? `${criteria.scriptRelativePath}::${criteria.selector}`
|
|
134
|
-
: criteria.scriptRelativePath;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function runPythonPytestNodeId(criteria, cwd) {
|
|
138
|
-
return runCommand(PYTHON_EXECUTABLE, ['-m', 'pytest', buildPytestNodeId(criteria)], cwd);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function runCommand(command, args, cwd, extraEnv = undefined) {
|
|
142
|
-
try {
|
|
143
|
-
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
144
|
-
cwd,
|
|
145
|
-
windowsHide: true,
|
|
146
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
147
|
-
timeout: TEST_TIMEOUT_MS,
|
|
148
|
-
env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
|
|
149
|
-
});
|
|
150
|
-
return { exitCode: 0, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
151
|
-
} catch (error) {
|
|
152
|
-
const timedOut = error && (error.killed || error.signal === 'SIGTERM' || error.code === 'ETIMEDOUT');
|
|
153
|
-
return {
|
|
154
|
-
exitCode: typeof error.code === 'number' ? error.code : 1,
|
|
155
|
-
stdout: String(error.stdout || '').trim(),
|
|
156
|
-
stderr: timedOut
|
|
157
|
-
? `Command timed out after ${TEST_TIMEOUT_MS}ms: ${formatCommand(command, args)}`
|
|
158
|
-
: String(error.stderr || error.message || error).trim(),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function resolvePythonExecutable() {
|
|
164
|
-
const workspaceRoot = getWorkspaceRoot();
|
|
165
|
-
|
|
166
|
-
const candidates = process.platform === 'win32'
|
|
167
|
-
? [
|
|
168
|
-
path.join(workspaceRoot, '.venv', 'Scripts', 'python.exe'),
|
|
169
|
-
path.join(workspaceRoot, 'venv', 'Scripts', 'python.exe'),
|
|
170
|
-
]
|
|
171
|
-
: [
|
|
172
|
-
path.join(workspaceRoot, '.venv', 'bin', 'python'),
|
|
173
|
-
path.join(workspaceRoot, 'venv', 'bin', 'python'),
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
for (const candidate of candidates) {
|
|
177
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
178
|
-
}
|
|
179
|
-
return 'python';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function readPositiveInteger(value, fallback) {
|
|
183
|
-
const parsed = Number.parseInt(String(value || ''), 10);
|
|
184
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function normalizePath(value) {
|
|
188
|
-
return String(value).replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function formatCommand(command, args) {
|
|
192
|
-
return [quote(command), ...args.map(quote)].join(' ');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function quote(value) {
|
|
196
|
-
return /\s/.test(value) ? `"${value}"` : value;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
module.exports = { name, canHandle, execute, getCommandPreview };
|
|
1
|
+
/**
|
|
2
|
+
* Default Test Executor — file-extension-based execution.
|
|
3
|
+
*
|
|
4
|
+
* This is the built-in executor that handles the original acceptanceCriteria format:
|
|
5
|
+
* a workspace-relative script path, optionally with a pytest node-id selector.
|
|
6
|
+
*
|
|
7
|
+
* Custom executors can be added to this directory; they are auto-discovered by
|
|
8
|
+
* runArchitectureTests.js. Each executor module must export:
|
|
9
|
+
* name: string
|
|
10
|
+
* canHandle(acceptanceCriteria, workspaceRoot): boolean
|
|
11
|
+
* execute(acceptanceCriteria, workspaceRoot): Promise<{exitCode, stdout, stderr}>
|
|
12
|
+
* getCommandPreview(acceptanceCriteria, workspaceRoot): string | null (optional)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { execFile } = require('child_process');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { promisify } = require('util');
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
getWorkspaceRoot,
|
|
22
|
+
} = require('../argo-paths.js');
|
|
23
|
+
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
|
|
26
|
+
const PYTHON_EXECUTABLE = resolvePythonExecutable();
|
|
27
|
+
const TEST_TIMEOUT_MS = readPositiveInteger(process.env.ARGO_TEST_TIMEOUT_MS, 120000);
|
|
28
|
+
|
|
29
|
+
const SUPPORTED_EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.py', '.ps1', '.cmd', '.bat']);
|
|
30
|
+
|
|
31
|
+
const DISALLOWED_PATTERNS = [
|
|
32
|
+
/[\r\n]/,
|
|
33
|
+
/[|&;<>]/,
|
|
34
|
+
/^['"].*['"]$/,
|
|
35
|
+
/^(?:npm|pnpm|yarn|npx|node|python|py|powershell|pwsh|cmd|bash|sh)\b/i,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// --- Exported interface ---
|
|
39
|
+
|
|
40
|
+
const name = 'default';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The default executor handles acceptanceCriteria that are workspace-relative
|
|
44
|
+
* script file paths (optionally with ::pytest_node_id selectors).
|
|
45
|
+
*/
|
|
46
|
+
function canHandle(acceptanceCriteria) {
|
|
47
|
+
if (!acceptanceCriteria) return false;
|
|
48
|
+
|
|
49
|
+
for (const pattern of DISALLOWED_PATTERNS) {
|
|
50
|
+
if (pattern.test(acceptanceCriteria)) return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const parsed = parseCriteria(acceptanceCriteria);
|
|
54
|
+
const ext = path.extname(parsed.scriptRelativePath).toLowerCase();
|
|
55
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) return false;
|
|
56
|
+
|
|
57
|
+
if (parsed.selector && ext !== '.py') return false;
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getCommandPreview(acceptanceCriteria) {
|
|
63
|
+
const parsed = parseCriteria(acceptanceCriteria);
|
|
64
|
+
if (parsed.selector) {
|
|
65
|
+
return formatCommand('python', ['-m', 'pytest', buildPytestNodeId(parsed)]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ext = path.extname(parsed.scriptRelativePath).toLowerCase();
|
|
69
|
+
switch (ext) {
|
|
70
|
+
case '.js': case '.cjs': case '.mjs':
|
|
71
|
+
return formatCommand(process.execPath, [parsed.displayPath || parsed.scriptRelativePath]);
|
|
72
|
+
case '.py':
|
|
73
|
+
return formatCommand('python', [parsed.scriptRelativePath]);
|
|
74
|
+
case '.ps1':
|
|
75
|
+
return formatCommand('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', parsed.scriptRelativePath]);
|
|
76
|
+
case '.cmd': case '.bat':
|
|
77
|
+
return formatCommand(parsed.scriptRelativePath, []);
|
|
78
|
+
default:
|
|
79
|
+
return formatCommand(parsed.scriptRelativePath, []);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function execute(acceptanceCriteria, workspaceRoot) {
|
|
84
|
+
const parsed = parseCriteria(acceptanceCriteria);
|
|
85
|
+
const scriptPath = path.join(workspaceRoot, ...parsed.scriptRelativePath.split('/'));
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(scriptPath)) {
|
|
88
|
+
return {
|
|
89
|
+
exitCode: null,
|
|
90
|
+
stdout: '',
|
|
91
|
+
stderr: `test script not found: ${acceptanceCriteria}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (parsed.selector) {
|
|
96
|
+
return runPythonPytestNodeId(parsed, workspaceRoot);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const ext = path.extname(scriptPath).toLowerCase();
|
|
100
|
+
switch (ext) {
|
|
101
|
+
case '.js': case '.cjs': case '.mjs':
|
|
102
|
+
return runCommand(process.execPath, [scriptPath], workspaceRoot, parsed.fragment
|
|
103
|
+
? { ARGO_TESTCASE_ANCHOR: parsed.fragment }
|
|
104
|
+
: undefined);
|
|
105
|
+
case '.py':
|
|
106
|
+
return runCommand(PYTHON_EXECUTABLE, [scriptPath], workspaceRoot);
|
|
107
|
+
case '.ps1':
|
|
108
|
+
return runCommand('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath], workspaceRoot);
|
|
109
|
+
case '.cmd': case '.bat':
|
|
110
|
+
return runCommand(scriptPath, [], workspaceRoot);
|
|
111
|
+
default:
|
|
112
|
+
return runCommand(scriptPath, [], workspaceRoot);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Internal helpers ---
|
|
117
|
+
|
|
118
|
+
function parseCriteria(value) {
|
|
119
|
+
const [pathAndFragment, ...selectorParts] = value.split('::');
|
|
120
|
+
const hashIndex = pathAndFragment.indexOf('#');
|
|
121
|
+
const scriptRelativePath = hashIndex >= 0 ? pathAndFragment.slice(0, hashIndex) : pathAndFragment;
|
|
122
|
+
const fragment = hashIndex >= 0 ? pathAndFragment.slice(hashIndex + 1).trim() : undefined;
|
|
123
|
+
return {
|
|
124
|
+
scriptRelativePath: normalizePath(scriptRelativePath),
|
|
125
|
+
displayPath: normalizePath(pathAndFragment),
|
|
126
|
+
fragment,
|
|
127
|
+
selector: selectorParts.length > 0 ? selectorParts.join('::').trim() : undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildPytestNodeId(criteria) {
|
|
132
|
+
return criteria.selector
|
|
133
|
+
? `${criteria.scriptRelativePath}::${criteria.selector}`
|
|
134
|
+
: criteria.scriptRelativePath;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function runPythonPytestNodeId(criteria, cwd) {
|
|
138
|
+
return runCommand(PYTHON_EXECUTABLE, ['-m', 'pytest', buildPytestNodeId(criteria)], cwd);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function runCommand(command, args, cwd, extraEnv = undefined) {
|
|
142
|
+
try {
|
|
143
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
144
|
+
cwd,
|
|
145
|
+
windowsHide: true,
|
|
146
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
147
|
+
timeout: TEST_TIMEOUT_MS,
|
|
148
|
+
env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
|
|
149
|
+
});
|
|
150
|
+
return { exitCode: 0, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const timedOut = error && (error.killed || error.signal === 'SIGTERM' || error.code === 'ETIMEDOUT');
|
|
153
|
+
return {
|
|
154
|
+
exitCode: typeof error.code === 'number' ? error.code : 1,
|
|
155
|
+
stdout: String(error.stdout || '').trim(),
|
|
156
|
+
stderr: timedOut
|
|
157
|
+
? `Command timed out after ${TEST_TIMEOUT_MS}ms: ${formatCommand(command, args)}`
|
|
158
|
+
: String(error.stderr || error.message || error).trim(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function resolvePythonExecutable() {
|
|
164
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
165
|
+
|
|
166
|
+
const candidates = process.platform === 'win32'
|
|
167
|
+
? [
|
|
168
|
+
path.join(workspaceRoot, '.venv', 'Scripts', 'python.exe'),
|
|
169
|
+
path.join(workspaceRoot, 'venv', 'Scripts', 'python.exe'),
|
|
170
|
+
]
|
|
171
|
+
: [
|
|
172
|
+
path.join(workspaceRoot, '.venv', 'bin', 'python'),
|
|
173
|
+
path.join(workspaceRoot, 'venv', 'bin', 'python'),
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
178
|
+
}
|
|
179
|
+
return 'python';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function readPositiveInteger(value, fallback) {
|
|
183
|
+
const parsed = Number.parseInt(String(value || ''), 10);
|
|
184
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizePath(value) {
|
|
188
|
+
return String(value).replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function formatCommand(command, args) {
|
|
192
|
+
return [quote(command), ...args.map(quote)].join(' ');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function quote(value) {
|
|
196
|
+
return /\s/.test(value) ? `"${value}"` : value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { name, canHandle, execute, getCommandPreview };
|