@syrin/cli 1.3.2 → 1.4.0
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/README.md +36 -0
- package/dist/cli/commands/config.d.ts +47 -0
- package/dist/cli/commands/config.js +360 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +67 -15
- package/dist/cli/commands/doctor.js +49 -13
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +89 -18
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.js +162 -0
- package/dist/cli/index.js +211 -12
- package/dist/cli/prompts/init-prompt.d.ts +18 -0
- package/dist/cli/prompts/init-prompt.js +159 -99
- package/dist/cli/utils/command-error-handler.js +2 -5
- package/dist/config/env-checker.d.ts +12 -2
- package/dist/config/env-checker.js +88 -38
- package/dist/config/env-templates.d.ts +15 -0
- package/dist/config/env-templates.js +49 -0
- package/dist/config/generator.js +17 -0
- package/dist/config/global-loader.d.ts +50 -0
- package/dist/config/global-loader.js +244 -0
- package/dist/config/loader.d.ts +28 -0
- package/dist/config/loader.js +95 -9
- package/dist/config/merger.d.ts +37 -0
- package/dist/config/merger.js +68 -0
- package/dist/config/schema.d.ts +26 -1
- package/dist/config/schema.js +73 -8
- package/dist/config/types.d.ts +19 -0
- package/dist/config/types.js +26 -1
- package/dist/constants/messages.d.ts +7 -0
- package/dist/constants/messages.js +8 -0
- package/dist/constants/paths.d.ts +6 -0
- package/dist/constants/paths.js +10 -0
- package/dist/events/emitter.js +7 -7
- package/dist/index.js +0 -0
- package/dist/presentation/config-ui.d.ts +34 -0
- package/dist/presentation/config-ui.js +139 -0
- package/dist/presentation/doctor-ui.d.ts +11 -0
- package/dist/presentation/doctor-ui.js +52 -1
- package/dist/presentation/init-ui.d.ts +9 -0
- package/dist/presentation/init-ui.js +33 -0
- package/dist/runtime/analysis/analyser.js +2 -2
- package/dist/runtime/analysis/rules/warnings/w104-generic-description.d.ts +1 -1
- package/dist/runtime/analysis/rules/warnings/w104-generic-description.js +1 -1
- package/dist/runtime/dev/event-mapper.js +19 -3
- package/dist/runtime/dev/session.d.ts +4 -0
- package/dist/runtime/dev/session.js +52 -3
- package/dist/runtime/llm/ollama.js +4 -4
- package/dist/runtime/mcp/client/manager.js +3 -3
- package/dist/runtime/sandbox/executor.js +5 -5
- package/dist/runtime/test/orchestrator.js +4 -4
- package/dist/utils/editor.d.ts +37 -0
- package/dist/utils/editor.js +137 -0
- package/dist/utils/logger.d.ts +24 -6
- package/dist/utils/logger.js +51 -8
- package/package.json +1 -1
- package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.js +0 -30
- package/dist/runtime/analysis/rules/errors/e002-underspecified-input.d.ts +0 -24
- package/dist/runtime/analysis/rules/errors/e002-underspecified-input.js +0 -52
- package/dist/runtime/analysis/rules/errors/e003-type-mismatch.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e003-type-mismatch.js +0 -73
- package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.js +0 -47
- package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.d.ts +0 -25
- package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.js +0 -73
- package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.js +0 -57
- package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.js +0 -56
- package/dist/runtime/analysis/rules/errors/e008-circular-dependency.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e008-circular-dependency.js +0 -84
- package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.js +0 -89
- package/dist/runtime/analysis/rules/errors/e010-non-serializable.d.ts +0 -25
- package/dist/runtime/analysis/rules/errors/e010-non-serializable.js +0 -46
- package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.d.ts +0 -24
- package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.js +0 -33
- package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.js +0 -40
- package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.d.ts +0 -37
- package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.js +0 -34
- package/dist/runtime/analysis/rules/errors/e013-output-explosion.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e013-output-explosion.js +0 -36
- package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.d.ts +0 -42
- package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.js +0 -46
- package/dist/runtime/analysis/rules/errors/e014-output-explosion.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e014-output-explosion.js +0 -36
- package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.d.ts +0 -42
- package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.js +0 -46
- package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.d.ts +0 -44
- package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.js +0 -66
- package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.d.ts +0 -43
- package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.js +0 -42
- package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.d.ts +0 -44
- package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.js +0 -66
- package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.d.ts +0 -57
- package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.js +0 -80
- package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.d.ts +0 -43
- package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.js +0 -42
- package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.d.ts +0 -57
- package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.js +0 -80
- package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.d.ts +0 -38
- package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.js +0 -37
- package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.d.ts +0 -38
- package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.js +0 -37
- package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.d.ts +0 -65
- package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.js +0 -109
- package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.d.ts +0 -65
- package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.js +0 -109
- package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.js +0 -39
- package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.d.ts +0 -24
- package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.js +0 -40
- package/dist/runtime/analysis/rules/warnings/w003-missing-examples.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w003-missing-examples.js +0 -84
- package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.js +0 -96
- package/dist/runtime/analysis/rules/warnings/w005-generic-description.d.ts +0 -53
- package/dist/runtime/analysis/rules/warnings/w005-generic-description.js +0 -108
- package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.js +0 -44
- package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.js +0 -37
- package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.js +0 -97
- package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.js +0 -88
- package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.js +0 -81
- package/dist/runtime/analysis/rules/warnings/w021-weak-schema.d.ts +0 -40
- package/dist/runtime/analysis/rules/warnings/w021-weak-schema.js +0 -32
- package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.d.ts +0 -39
- package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.js +0 -36
- package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.d.ts +0 -38
- package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.js +0 -36
- package/dist/runtime/test/dependency-tracker.d.ts +0 -66
- package/dist/runtime/test/dependency-tracker.js +0 -80
- package/dist/runtime/test/formatters.d.ts +0 -18
- package/dist/runtime/test/formatters.js +0 -172
- package/dist/runtime/test/input-generator.d.ts +0 -33
- package/dist/runtime/test/input-generator.js +0 -498
- package/dist/runtime/test/mcp-root-detector.d.ts +0 -31
- package/dist/runtime/test/mcp-root-detector.js +0 -105
- package/dist/runtime/test/retry-tester.d.ts +0 -44
- package/dist/runtime/test/retry-tester.js +0 -103
- package/dist/runtime/test/synthetic-input-generator.d.ts +0 -11
- package/dist/runtime/test/synthetic-input-generator.js +0 -154
- package/dist/runtime/test/test-runner.d.ts +0 -28
- package/dist/runtime/test/test-runner.js +0 -55
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor utility for opening files in system editor.
|
|
3
|
+
* Supports nano (Unix) and notepad (Windows).
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Get the appropriate editor command for the current platform.
|
|
10
|
+
* Checks EDITOR and VISUAL environment variables first, then falls back to platform defaults.
|
|
11
|
+
* @returns Editor command
|
|
12
|
+
*/
|
|
13
|
+
export function getEditor() {
|
|
14
|
+
// Check environment variables first (user preference)
|
|
15
|
+
if (process.env.EDITOR && process.env.EDITOR.trim() !== '') {
|
|
16
|
+
return process.env.EDITOR.trim();
|
|
17
|
+
}
|
|
18
|
+
if (process.env.VISUAL && process.env.VISUAL.trim() !== '') {
|
|
19
|
+
return process.env.VISUAL.trim();
|
|
20
|
+
}
|
|
21
|
+
// Fall back to platform defaults
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
return 'notepad';
|
|
24
|
+
}
|
|
25
|
+
return 'nano';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get editor arguments for opening a file.
|
|
29
|
+
* @param editor - Editor command
|
|
30
|
+
* @param filePath - Path to file to open
|
|
31
|
+
* @returns Array of arguments for the editor
|
|
32
|
+
*/
|
|
33
|
+
function getEditorArgs(editor, filePath) {
|
|
34
|
+
// For notepad and nano, just pass the file path
|
|
35
|
+
return [filePath];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check file permissions and return security status.
|
|
39
|
+
* @param filePath - Path to file to check
|
|
40
|
+
* @returns Permission check result
|
|
41
|
+
*/
|
|
42
|
+
export function checkFilePermissions(filePath) {
|
|
43
|
+
if (!fs.existsSync(filePath)) {
|
|
44
|
+
return {
|
|
45
|
+
isSecure: true, // Non-existent files are considered secure
|
|
46
|
+
currentMode: 'N/A',
|
|
47
|
+
recommendedMode: '600',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const stats = fs.statSync(filePath);
|
|
52
|
+
// Normalize octal string to 3 digits for consistent comparison
|
|
53
|
+
const mode = stats.mode.toString(8).slice(-3).padStart(3, '0');
|
|
54
|
+
const isSecure = mode === '600' || mode === '400'; // Read/write owner only, or read-only owner
|
|
55
|
+
return {
|
|
56
|
+
isSecure,
|
|
57
|
+
currentMode: mode,
|
|
58
|
+
recommendedMode: '600',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// If we can't check permissions, assume insecure
|
|
63
|
+
return {
|
|
64
|
+
isSecure: false,
|
|
65
|
+
currentMode: 'unknown',
|
|
66
|
+
recommendedMode: '600',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Ensure a file exists, creating it with a template if needed.
|
|
72
|
+
* @param filePath - Path to file
|
|
73
|
+
* @param template - Template content to use if file doesn't exist
|
|
74
|
+
* @param secure - Whether to set secure permissions (default: true for .env files)
|
|
75
|
+
*/
|
|
76
|
+
export function ensureFileExists(filePath, template, secure = true) {
|
|
77
|
+
if (fs.existsSync(filePath)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Create directory if needed
|
|
81
|
+
const dir = path.dirname(filePath);
|
|
82
|
+
if (!fs.existsSync(dir)) {
|
|
83
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
84
|
+
}
|
|
85
|
+
// Create file with template
|
|
86
|
+
fs.writeFileSync(filePath, template, 'utf-8');
|
|
87
|
+
// Set secure permissions if requested
|
|
88
|
+
if (secure) {
|
|
89
|
+
try {
|
|
90
|
+
fs.chmodSync(filePath, 0o600);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore permission errors (may not work on all systems)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Open a file in the system editor.
|
|
99
|
+
* @param filePath - Path to file to open
|
|
100
|
+
* @param createIfMissing - Whether to create file if it doesn't exist (default: false)
|
|
101
|
+
* @param template - Template content if creating file (optional)
|
|
102
|
+
* @returns Promise that resolves when editor closes
|
|
103
|
+
* @throws {Error} If editor fails to open
|
|
104
|
+
*/
|
|
105
|
+
export async function openInEditor(filePath, createIfMissing = false, template) {
|
|
106
|
+
// Ensure file exists if requested
|
|
107
|
+
if (createIfMissing && !fs.existsSync(filePath) && template) {
|
|
108
|
+
ensureFileExists(filePath, template, filePath.endsWith('.env'));
|
|
109
|
+
}
|
|
110
|
+
// Check if file exists
|
|
111
|
+
if (!fs.existsSync(filePath)) {
|
|
112
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
113
|
+
}
|
|
114
|
+
const editor = getEditor();
|
|
115
|
+
const args = getEditorArgs(editor, filePath);
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const proc = spawn(editor, args, {
|
|
118
|
+
stdio: 'inherit',
|
|
119
|
+
shell: true,
|
|
120
|
+
});
|
|
121
|
+
proc.on('exit', code => {
|
|
122
|
+
if (code === 0 || code === null) {
|
|
123
|
+
// Exit code 0 or null (normal exit) - user saved and closed
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Non-zero exit code - might be user cancellation or error
|
|
128
|
+
// We'll treat it as success (user may have cancelled, which is fine)
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
proc.on('error', error => {
|
|
133
|
+
reject(new Error(`Failed to open editor "${editor}": ${error.message}. Make sure the editor is installed and available in your PATH.`));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=editor.js.map
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -54,6 +54,14 @@ export declare class Log {
|
|
|
54
54
|
* Set the log level.
|
|
55
55
|
*/
|
|
56
56
|
setLevel(level: LogLevel): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get the current log level.
|
|
59
|
+
*/
|
|
60
|
+
getLevel(): LogLevel;
|
|
61
|
+
/**
|
|
62
|
+
* Check if the logger is in quiet mode (only errors shown).
|
|
63
|
+
*/
|
|
64
|
+
isQuiet(): boolean;
|
|
57
65
|
/**
|
|
58
66
|
* Enable or disable structured logging.
|
|
59
67
|
*/
|
|
@@ -67,7 +75,8 @@ export declare class Log {
|
|
|
67
75
|
*/
|
|
68
76
|
private structuredLog;
|
|
69
77
|
/**
|
|
70
|
-
* Print styled text to console.
|
|
78
|
+
* Print styled text to console at INFO level.
|
|
79
|
+
* Respects log level - won't print if level is higher than INFO.
|
|
71
80
|
*/
|
|
72
81
|
private print;
|
|
73
82
|
/**
|
|
@@ -92,6 +101,7 @@ export declare class Log {
|
|
|
92
101
|
* Log/print error message (red) with error icon.
|
|
93
102
|
* For structured logging: logs at ERROR level (can include Error object)
|
|
94
103
|
* For styled output: prints with red color and error icon
|
|
104
|
+
* Note: Errors always show regardless of log level (for quiet mode).
|
|
95
105
|
*/
|
|
96
106
|
error(message: string, errorOrIcon?: Error | IconType, contextOrIcon?: Record<string, unknown> | IconType): void;
|
|
97
107
|
/**
|
|
@@ -124,6 +134,7 @@ export declare class Log {
|
|
|
124
134
|
debug(text: string, icon?: IconType): void;
|
|
125
135
|
/**
|
|
126
136
|
* Print a line with label and value.
|
|
137
|
+
* Respects log level.
|
|
127
138
|
*/
|
|
128
139
|
labelValue(labelText: string, valueText: string): void;
|
|
129
140
|
/**
|
|
@@ -154,10 +165,12 @@ export declare class Log {
|
|
|
154
165
|
warnSymbol(text: string): void;
|
|
155
166
|
/**
|
|
156
167
|
* Print plain text (no styling).
|
|
168
|
+
* Respects log level.
|
|
157
169
|
*/
|
|
158
170
|
plain(text: string): void;
|
|
159
171
|
/**
|
|
160
172
|
* Print an empty line.
|
|
173
|
+
* Respects log level.
|
|
161
174
|
*/
|
|
162
175
|
blank(): void;
|
|
163
176
|
/**
|
|
@@ -167,15 +180,20 @@ export declare class Log {
|
|
|
167
180
|
styleText(text: string, ...styles: ColorKey[]): string;
|
|
168
181
|
}
|
|
169
182
|
/**
|
|
170
|
-
* Default logger instance for styled output
|
|
171
|
-
* Use this for
|
|
183
|
+
* Default logger instance for styled output.
|
|
184
|
+
* Use this for all logging throughout the application.
|
|
172
185
|
*/
|
|
173
186
|
export declare const log: Log;
|
|
174
187
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
188
|
+
* Set quiet mode (errors only).
|
|
189
|
+
* @param enabled - Whether to enable quiet mode
|
|
190
|
+
*/
|
|
191
|
+
export declare function setQuietMode(enabled: boolean): void;
|
|
192
|
+
/**
|
|
193
|
+
* Set verbose mode (debug level).
|
|
194
|
+
* @param enabled - Whether to enable verbose mode
|
|
177
195
|
*/
|
|
178
|
-
export declare
|
|
196
|
+
export declare function setVerboseMode(enabled: boolean): void;
|
|
179
197
|
/**
|
|
180
198
|
* Create a logger with a specific context.
|
|
181
199
|
*/
|
package/dist/utils/logger.js
CHANGED
|
@@ -56,6 +56,18 @@ export class Log {
|
|
|
56
56
|
setLevel(level) {
|
|
57
57
|
this.level = level;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the current log level.
|
|
61
|
+
*/
|
|
62
|
+
getLevel() {
|
|
63
|
+
return this.level;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if the logger is in quiet mode (only errors shown).
|
|
67
|
+
*/
|
|
68
|
+
isQuiet() {
|
|
69
|
+
return this.level >= LogLevel.ERROR;
|
|
70
|
+
}
|
|
59
71
|
/**
|
|
60
72
|
* Enable or disable structured logging.
|
|
61
73
|
*/
|
|
@@ -117,9 +129,13 @@ export class Log {
|
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
/**
|
|
120
|
-
* Print styled text to console.
|
|
132
|
+
* Print styled text to console at INFO level.
|
|
133
|
+
* Respects log level - won't print if level is higher than INFO.
|
|
121
134
|
*/
|
|
122
135
|
print(text, ...styles) {
|
|
136
|
+
if (LogLevel.INFO < this.level) {
|
|
137
|
+
return; // Skip if log level is higher than INFO (e.g., quiet mode)
|
|
138
|
+
}
|
|
123
139
|
if (this.useStructuredLogging) {
|
|
124
140
|
this.structuredLog(LogLevel.INFO, text);
|
|
125
141
|
}
|
|
@@ -167,6 +183,7 @@ export class Log {
|
|
|
167
183
|
* Log/print error message (red) with error icon.
|
|
168
184
|
* For structured logging: logs at ERROR level (can include Error object)
|
|
169
185
|
* For styled output: prints with red color and error icon
|
|
186
|
+
* Note: Errors always show regardless of log level (for quiet mode).
|
|
170
187
|
*/
|
|
171
188
|
error(message, errorOrIcon, contextOrIcon) {
|
|
172
189
|
if (this.useStructuredLogging) {
|
|
@@ -179,9 +196,10 @@ export class Log {
|
|
|
179
196
|
}
|
|
180
197
|
else {
|
|
181
198
|
// Styled output: errorOrIcon is optional Icon
|
|
199
|
+
// Errors always print regardless of log level (critical for quiet mode)
|
|
182
200
|
const icon = errorOrIcon;
|
|
183
201
|
const iconText = icon ? `${icon} ` : `${Icon.ERROR} `;
|
|
184
|
-
this.
|
|
202
|
+
console.log(this.style(`${iconText}${message}`, 'red'));
|
|
185
203
|
}
|
|
186
204
|
}
|
|
187
205
|
warning(text, contextOrIcon) {
|
|
@@ -238,8 +256,12 @@ export class Log {
|
|
|
238
256
|
}
|
|
239
257
|
/**
|
|
240
258
|
* Print a line with label and value.
|
|
259
|
+
* Respects log level.
|
|
241
260
|
*/
|
|
242
261
|
labelValue(labelText, valueText) {
|
|
262
|
+
if (LogLevel.INFO < this.level) {
|
|
263
|
+
return; // Skip if log level is higher than INFO
|
|
264
|
+
}
|
|
243
265
|
const output = `${this.style(labelText, 'dim')} ${this.style(valueText, 'cyan')}`;
|
|
244
266
|
if (this.useStructuredLogging) {
|
|
245
267
|
this.structuredLog(LogLevel.INFO, `${labelText} ${valueText}`);
|
|
@@ -288,8 +310,12 @@ export class Log {
|
|
|
288
310
|
}
|
|
289
311
|
/**
|
|
290
312
|
* Print plain text (no styling).
|
|
313
|
+
* Respects log level.
|
|
291
314
|
*/
|
|
292
315
|
plain(text) {
|
|
316
|
+
if (LogLevel.INFO < this.level) {
|
|
317
|
+
return; // Skip if log level is higher than INFO
|
|
318
|
+
}
|
|
293
319
|
if (this.useStructuredLogging) {
|
|
294
320
|
this.structuredLog(LogLevel.INFO, text);
|
|
295
321
|
}
|
|
@@ -299,8 +325,12 @@ export class Log {
|
|
|
299
325
|
}
|
|
300
326
|
/**
|
|
301
327
|
* Print an empty line.
|
|
328
|
+
* Respects log level.
|
|
302
329
|
*/
|
|
303
330
|
blank() {
|
|
331
|
+
if (LogLevel.INFO < this.level) {
|
|
332
|
+
return; // Skip if log level is higher than INFO
|
|
333
|
+
}
|
|
304
334
|
console.log('');
|
|
305
335
|
}
|
|
306
336
|
/**
|
|
@@ -312,19 +342,32 @@ export class Log {
|
|
|
312
342
|
}
|
|
313
343
|
}
|
|
314
344
|
/**
|
|
315
|
-
* Default logger instance for styled output
|
|
316
|
-
* Use this for
|
|
345
|
+
* Default logger instance for styled output.
|
|
346
|
+
* Use this for all logging throughout the application.
|
|
317
347
|
*/
|
|
318
348
|
export const log = new Log(LogLevel.INFO, {}, false);
|
|
319
349
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
350
|
+
* Set quiet mode (errors only).
|
|
351
|
+
* @param enabled - Whether to enable quiet mode
|
|
352
|
+
*/
|
|
353
|
+
export function setQuietMode(enabled) {
|
|
354
|
+
if (enabled) {
|
|
355
|
+
log.setLevel(LogLevel.ERROR);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set verbose mode (debug level).
|
|
360
|
+
* @param enabled - Whether to enable verbose mode
|
|
322
361
|
*/
|
|
323
|
-
export
|
|
362
|
+
export function setVerboseMode(enabled) {
|
|
363
|
+
if (enabled) {
|
|
364
|
+
log.setLevel(LogLevel.DEBUG);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
324
367
|
/**
|
|
325
368
|
* Create a logger with a specific context.
|
|
326
369
|
*/
|
|
327
370
|
export function createLogger(context) {
|
|
328
|
-
return
|
|
371
|
+
return log.child(context);
|
|
329
372
|
}
|
|
330
373
|
//# sourceMappingURL=logger.js.map
|
package/package.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E001: Missing Output Schema
|
|
3
|
-
*
|
|
4
|
-
* Condition: Tool does not declare an output schema
|
|
5
|
-
*
|
|
6
|
-
* Why this is fatal:
|
|
7
|
-
* - Downstream tools cannot reason about outputs
|
|
8
|
-
* - LLM will invent structure
|
|
9
|
-
* - Reproducibility is impossible
|
|
10
|
-
*/
|
|
11
|
-
import { BaseRule } from '../base.js';
|
|
12
|
-
import type { AnalysisContext, Diagnostic } from '../../types.js';
|
|
13
|
-
declare class E001MissingOutputSchemaRule extends BaseRule {
|
|
14
|
-
readonly id: "E001";
|
|
15
|
-
readonly severity: "error";
|
|
16
|
-
readonly ruleName = "Missing Output Schema";
|
|
17
|
-
readonly description = "Tool does not declare an output schema. Downstream tools cannot safely consume its output.";
|
|
18
|
-
check(ctx: AnalysisContext): Diagnostic[];
|
|
19
|
-
}
|
|
20
|
-
export declare const E001MissingOutputSchema: E001MissingOutputSchemaRule;
|
|
21
|
-
export {};
|
|
22
|
-
//# sourceMappingURL=e001-missing-output-schema.d.ts.map
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E001: Missing Output Schema
|
|
3
|
-
*
|
|
4
|
-
* Condition: Tool does not declare an output schema
|
|
5
|
-
*
|
|
6
|
-
* Why this is fatal:
|
|
7
|
-
* - Downstream tools cannot reason about outputs
|
|
8
|
-
* - LLM will invent structure
|
|
9
|
-
* - Reproducibility is impossible
|
|
10
|
-
*/
|
|
11
|
-
import { BaseRule } from '../base.js';
|
|
12
|
-
import { ERROR_CODES } from '../error-codes.js';
|
|
13
|
-
class E001MissingOutputSchemaRule extends BaseRule {
|
|
14
|
-
id = ERROR_CODES.E001;
|
|
15
|
-
severity = 'error';
|
|
16
|
-
ruleName = 'Missing Output Schema';
|
|
17
|
-
description = 'Tool does not declare an output schema. Downstream tools cannot safely consume its output.';
|
|
18
|
-
check(ctx) {
|
|
19
|
-
const diagnostics = [];
|
|
20
|
-
for (const tool of ctx.tools) {
|
|
21
|
-
// Check if tool has no output fields
|
|
22
|
-
if (!tool.outputs || tool.outputs.length === 0) {
|
|
23
|
-
diagnostics.push(this.createDiagnostic(`Tool "${tool.name}" has no output schema. Downstream tools cannot safely consume its output.`, tool.name, undefined, 'Add an output schema to the tool definition to specify the structure of the output.'));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return diagnostics;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export const E001MissingOutputSchema = new E001MissingOutputSchemaRule();
|
|
30
|
-
//# sourceMappingURL=e001-missing-output-schema.js.map
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E002: Underspecified Required Input
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Input parameter is required AND
|
|
6
|
-
* - Type is broad (string, any, object) AND
|
|
7
|
-
* - No description, constraints, enum, regex, or example
|
|
8
|
-
*
|
|
9
|
-
* Why:
|
|
10
|
-
* - LLM will hallucinate values
|
|
11
|
-
* - Tool invocation becomes nondeterministic
|
|
12
|
-
*/
|
|
13
|
-
import { BaseRule } from '../base.js';
|
|
14
|
-
import type { AnalysisContext, Diagnostic } from '../../types.js';
|
|
15
|
-
declare class E002UnderspecifiedRequiredInputRule extends BaseRule {
|
|
16
|
-
readonly id: "E002";
|
|
17
|
-
readonly severity: "error";
|
|
18
|
-
readonly ruleName = "Underspecified Required Input";
|
|
19
|
-
readonly description = "Required parameter is underspecified. LLM may pass invalid or ambiguous values.";
|
|
20
|
-
check(ctx: AnalysisContext): Diagnostic[];
|
|
21
|
-
}
|
|
22
|
-
export declare const E002UnderspecifiedRequiredInput: E002UnderspecifiedRequiredInputRule;
|
|
23
|
-
export {};
|
|
24
|
-
//# sourceMappingURL=e002-underspecified-input.d.ts.map
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E002: Underspecified Required Input
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Input parameter is required AND
|
|
6
|
-
* - Type is broad (string, any, object) AND
|
|
7
|
-
* - No description, constraints, enum, regex, or example
|
|
8
|
-
*
|
|
9
|
-
* Why:
|
|
10
|
-
* - LLM will hallucinate values
|
|
11
|
-
* - Tool invocation becomes nondeterministic
|
|
12
|
-
*/
|
|
13
|
-
import { BaseRule } from '../base.js';
|
|
14
|
-
import { ERROR_CODES } from '../error-codes.js';
|
|
15
|
-
class E002UnderspecifiedRequiredInputRule extends BaseRule {
|
|
16
|
-
id = ERROR_CODES.E002;
|
|
17
|
-
severity = 'error';
|
|
18
|
-
ruleName = 'Underspecified Required Input';
|
|
19
|
-
description = 'Required parameter is underspecified. LLM may pass invalid or ambiguous values.';
|
|
20
|
-
check(ctx) {
|
|
21
|
-
const diagnostics = [];
|
|
22
|
-
// Broad types that need constraints
|
|
23
|
-
const broadTypes = new Set(['string', 'any', 'object']);
|
|
24
|
-
for (const tool of ctx.tools) {
|
|
25
|
-
for (const input of tool.inputs) {
|
|
26
|
-
// Check if type is broad
|
|
27
|
-
if (!broadTypes.has(input.type)) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
// Check if it has any constraints
|
|
31
|
-
const hasDescription = Boolean(input.description && input.description.trim());
|
|
32
|
-
const hasEnum = Boolean(input.enum && input.enum.length > 0);
|
|
33
|
-
const hasPattern = Boolean(input.pattern);
|
|
34
|
-
const hasExample = input.example !== undefined;
|
|
35
|
-
// If it's broad and has no constraints, it's underspecified
|
|
36
|
-
if (!hasDescription && !hasEnum && !hasPattern && !hasExample) {
|
|
37
|
-
if (input.required) {
|
|
38
|
-
// Required parameter without constraints - error
|
|
39
|
-
diagnostics.push(this.createDiagnostic(`Required parameter "${input.name}" in tool "${tool.name}" is underspecified. LLM may pass invalid or ambiguous values.`, tool.name, input.name, `Add constraints to "${input.name}": provide a description, enum values, regex pattern, or example.`));
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
// Optional parameter without constraints - flag as warning since it's less critical
|
|
43
|
-
diagnostics.push(this.createDiagnostic(`Optional parameter "${input.name}" in tool "${tool.name}" is underspecified. LLM may pass invalid or ambiguous values.`, tool.name, input.name, `Add constraints to "${input.name}": provide a description, enum values, regex pattern, or example.`, undefined, 'warning'));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return diagnostics;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export const E002UnderspecifiedRequiredInput = new E002UnderspecifiedRequiredInputRule();
|
|
52
|
-
//# sourceMappingURL=e002-underspecified-input.js.map
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E003: Unsafe Tool Chaining (Type Mismatch)
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Tool A output flows into Tool B input AND
|
|
6
|
-
* - Output type incompatible with input type
|
|
7
|
-
*
|
|
8
|
-
* Why:
|
|
9
|
-
* - Tool chains silently break
|
|
10
|
-
* - Bugs appear "random"
|
|
11
|
-
*/
|
|
12
|
-
import { BaseRule } from '../base.js';
|
|
13
|
-
import type { AnalysisContext, Diagnostic } from '../../types.js';
|
|
14
|
-
declare class E003TypeMismatchRule extends BaseRule {
|
|
15
|
-
readonly id: "E003";
|
|
16
|
-
readonly severity: "error";
|
|
17
|
-
readonly ruleName = "Unsafe Tool Chaining (Type Mismatch)";
|
|
18
|
-
readonly description = "Output type incompatible with downstream input type. Tool chains will break.";
|
|
19
|
-
check(ctx: AnalysisContext): Diagnostic[];
|
|
20
|
-
}
|
|
21
|
-
export declare const E003TypeMismatch: E003TypeMismatchRule;
|
|
22
|
-
export {};
|
|
23
|
-
//# sourceMappingURL=e003-type-mismatch.d.ts.map
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E003: Unsafe Tool Chaining (Type Mismatch)
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Tool A output flows into Tool B input AND
|
|
6
|
-
* - Output type incompatible with input type
|
|
7
|
-
*
|
|
8
|
-
* Why:
|
|
9
|
-
* - Tool chains silently break
|
|
10
|
-
* - Bugs appear "random"
|
|
11
|
-
*/
|
|
12
|
-
import { BaseRule } from '../base.js';
|
|
13
|
-
import { ERROR_CODES } from '../error-codes.js';
|
|
14
|
-
/**
|
|
15
|
-
* Check if two types are incompatible.
|
|
16
|
-
*/
|
|
17
|
-
function areTypesIncompatible(outputType, inputType) {
|
|
18
|
-
// Exact match is always compatible
|
|
19
|
-
if (outputType === inputType) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
// String can accept almost anything (it's broad)
|
|
23
|
-
if (inputType === 'string') {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
// Incompatible type pairs
|
|
27
|
-
const incompatiblePairs = [
|
|
28
|
-
['string', 'number'],
|
|
29
|
-
['string', 'boolean'],
|
|
30
|
-
['string', 'integer'],
|
|
31
|
-
['number', 'boolean'],
|
|
32
|
-
['boolean', 'number'],
|
|
33
|
-
['array', 'object'],
|
|
34
|
-
['object', 'array'],
|
|
35
|
-
];
|
|
36
|
-
for (const [from, to] of incompatiblePairs) {
|
|
37
|
-
if (outputType === from && inputType === to) {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
class E003TypeMismatchRule extends BaseRule {
|
|
44
|
-
id = ERROR_CODES.E003;
|
|
45
|
-
severity = 'error';
|
|
46
|
-
ruleName = 'Unsafe Tool Chaining (Type Mismatch)';
|
|
47
|
-
description = 'Output type incompatible with downstream input type. Tool chains will break.';
|
|
48
|
-
check(ctx) {
|
|
49
|
-
const diagnostics = [];
|
|
50
|
-
// Only check high-confidence dependencies (>= 0.8)
|
|
51
|
-
const highConfidenceDeps = ctx.dependencies.filter(d => d.confidence >= 0.8);
|
|
52
|
-
for (const dep of highConfidenceDeps) {
|
|
53
|
-
// Find the output and input fields
|
|
54
|
-
const fromTool = ctx.indexes.toolIndex.get(dep.fromTool.toLowerCase());
|
|
55
|
-
const toTool = ctx.indexes.toolIndex.get(dep.toTool.toLowerCase());
|
|
56
|
-
if (!fromTool || !toTool) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const fromField = fromTool.outputs.find(f => f.name === dep.fromField);
|
|
60
|
-
const toField = toTool.inputs.find(f => f.name === dep.toField);
|
|
61
|
-
if (!fromField || !toField) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
// Check type compatibility
|
|
65
|
-
if (areTypesIncompatible(fromField.type, toField.type)) {
|
|
66
|
-
diagnostics.push(this.createDiagnostic(`Tool "${dep.toTool}" parameter "${dep.toField}" depends on output from "${dep.fromTool}", but types are incompatible (${fromField.type} → ${toField.type}).`, dep.toTool, dep.toField, `Ensure "${dep.fromTool}" outputs ${toField.type}, or modify "${dep.toTool}" to accept ${fromField.type}.`));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return diagnostics;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
export const E003TypeMismatch = new E003TypeMismatchRule();
|
|
73
|
-
//# sourceMappingURL=e003-type-mismatch.js.map
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E004: Unsafe Tool Chaining (Free Text Propagation)
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Output is unconstrained string
|
|
6
|
-
* - Used as input to another tool
|
|
7
|
-
*
|
|
8
|
-
* Why:
|
|
9
|
-
* - LLM passes sentences instead of data
|
|
10
|
-
* - Most common real-world failure
|
|
11
|
-
*/
|
|
12
|
-
import { BaseRule } from '../base.js';
|
|
13
|
-
import type { AnalysisContext, Diagnostic } from '../../types.js';
|
|
14
|
-
declare class E004FreeTextPropagationRule extends BaseRule {
|
|
15
|
-
readonly id: "E004";
|
|
16
|
-
readonly severity: "error";
|
|
17
|
-
readonly ruleName = "Unsafe Tool Chaining (Free Text Propagation)";
|
|
18
|
-
readonly description = "Free-text output is used by another tool. This is unsafe without constraints.";
|
|
19
|
-
check(ctx: AnalysisContext): Diagnostic[];
|
|
20
|
-
}
|
|
21
|
-
export declare const E004FreeTextPropagation: E004FreeTextPropagationRule;
|
|
22
|
-
export {};
|
|
23
|
-
//# sourceMappingURL=e004-free-text-propagation.d.ts.map
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E004: Unsafe Tool Chaining (Free Text Propagation)
|
|
3
|
-
*
|
|
4
|
-
* Condition:
|
|
5
|
-
* - Output is unconstrained string
|
|
6
|
-
* - Used as input to another tool
|
|
7
|
-
*
|
|
8
|
-
* Why:
|
|
9
|
-
* - LLM passes sentences instead of data
|
|
10
|
-
* - Most common real-world failure
|
|
11
|
-
*/
|
|
12
|
-
import { BaseRule } from '../base.js';
|
|
13
|
-
import { ERROR_CODES } from '../error-codes.js';
|
|
14
|
-
class E004FreeTextPropagationRule extends BaseRule {
|
|
15
|
-
id = ERROR_CODES.E004;
|
|
16
|
-
severity = 'error';
|
|
17
|
-
ruleName = 'Unsafe Tool Chaining (Free Text Propagation)';
|
|
18
|
-
description = 'Free-text output is used by another tool. This is unsafe without constraints.';
|
|
19
|
-
check(ctx) {
|
|
20
|
-
const diagnostics = [];
|
|
21
|
-
// Only check high-confidence dependencies (>= 0.8)
|
|
22
|
-
const highConfidenceDeps = ctx.dependencies.filter(d => d.confidence >= 0.8);
|
|
23
|
-
for (const dep of highConfidenceDeps) {
|
|
24
|
-
// Find the output field
|
|
25
|
-
const fromTool = ctx.indexes.toolIndex.get(dep.fromTool.toLowerCase());
|
|
26
|
-
if (!fromTool) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
const fromField = fromTool.outputs.find(f => f.name === dep.fromField);
|
|
30
|
-
if (!fromField) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
// Check if output is unconstrained string
|
|
34
|
-
const isString = fromField.type === 'string';
|
|
35
|
-
const hasEnum = Boolean(fromField.enum && fromField.enum.length > 0);
|
|
36
|
-
const hasPattern = Boolean(fromField.pattern);
|
|
37
|
-
const hasDescription = Boolean(fromField.description && fromField.description.trim());
|
|
38
|
-
// If it's a string with no constraints, it's free text
|
|
39
|
-
if (isString && !hasEnum && !hasPattern && !hasDescription) {
|
|
40
|
-
diagnostics.push(this.createDiagnostic(`Free-text output from "${dep.fromTool}" (field: "${dep.fromField}") is used by "${dep.toTool}". This is unsafe without constraints.`, dep.toTool, dep.toField, `Constrain the output of "${dep.fromTool}" by adding enum values, regex pattern, or clear description of expected format.`));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return diagnostics;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export const E004FreeTextPropagation = new E004FreeTextPropagationRule();
|
|
47
|
-
//# sourceMappingURL=e004-free-text-propagation.js.map
|