invar-tools 1.15.6__py3-none-any.whl → 1.17.0__py3-none-any.whl
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.
- invar/core/format_strategies.py +3 -1
- invar/mcp/handlers.py +10 -1
- invar/node_tools/.gitignore +17 -0
- invar/node_tools/MANIFEST +1 -0
- invar/node_tools/eslint-plugin/cli.js +163 -0
- invar/node_tools/fc-runner/bundle.js +77 -0
- invar/node_tools/fc-runner/cli.d.ts +12 -0
- invar/node_tools/fc-runner/cli.d.ts.map +1 -0
- invar/node_tools/fc-runner/cli.js +117 -45
- invar/node_tools/fc-runner/cli.js.map +1 -0
- invar/node_tools/fc-runner/index.d.ts +338 -0
- invar/node_tools/fc-runner/index.d.ts.map +1 -0
- invar/node_tools/fc-runner/index.js +260 -0
- invar/node_tools/fc-runner/index.js.map +1 -0
- invar/shell/commands/init.py +2 -1
- invar/shell/prove/guard_ts.py +110 -8
- invar/shell/templates.py +44 -6
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/METADATA +1 -1
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/RECORD +24 -14
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.15.6.dist-info → invar_tools-1.17.0.dist-info}/licenses/NOTICE +0 -0
invar/core/format_strategies.py
CHANGED
|
@@ -159,11 +159,13 @@ def text_with_pattern(
|
|
|
159
159
|
)
|
|
160
160
|
noise_lines = st.lists(noise_line, min_size=0, max_size=10)
|
|
161
161
|
|
|
162
|
+
# Note: Hypothesis will automatically explore different orderings of the lines,
|
|
163
|
+
# so explicit shuffling is unnecessary. The concatenation order is sufficient.
|
|
162
164
|
return st.builds(
|
|
163
165
|
lambda p, n: "\n".join(p + n),
|
|
164
166
|
pattern_lines,
|
|
165
167
|
noise_lines,
|
|
166
|
-
)
|
|
168
|
+
)
|
|
167
169
|
|
|
168
170
|
|
|
169
171
|
@pre(lambda pattern: len(pattern) > 0)
|
invar/mcp/handlers.py
CHANGED
|
@@ -24,6 +24,11 @@ def _validate_path(path: str) -> tuple[bool, str]:
|
|
|
24
24
|
|
|
25
25
|
Returns (is_valid, error_message).
|
|
26
26
|
Rejects paths that could be interpreted as shell commands or flags.
|
|
27
|
+
|
|
28
|
+
Note: This validation is for MCP (Model Context Protocol) handlers, which
|
|
29
|
+
are designed to provide AI agents with access to the project filesystem.
|
|
30
|
+
We validate format and reject shell injection patterns, but do not restrict
|
|
31
|
+
to working directory (unlike CLI tools) since MCP is a trusted local protocol.
|
|
27
32
|
"""
|
|
28
33
|
if not path:
|
|
29
34
|
return True, "" # Empty path defaults to "." in handlers
|
|
@@ -38,9 +43,13 @@ def _validate_path(path: str) -> tuple[bool, str]:
|
|
|
38
43
|
if char in path:
|
|
39
44
|
return False, f"Invalid path: contains forbidden character: {char!r}"
|
|
40
45
|
|
|
41
|
-
#
|
|
46
|
+
# Resolve path to canonical form, following symlinks
|
|
47
|
+
# This ensures path is valid and catches directory traversal attempts
|
|
42
48
|
try:
|
|
43
49
|
Path(path).resolve()
|
|
50
|
+
# Note: We don't restrict to cwd here because MCP handlers are designed
|
|
51
|
+
# to access the full project. If path restriction is needed, implement
|
|
52
|
+
# at the MCP server level, not per-handler.
|
|
44
53
|
except (OSError, ValueError) as e:
|
|
45
54
|
return False, f"Invalid path: {e}"
|
|
46
55
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Embedded Node.js tools - runtime dependencies
|
|
2
|
+
# These are installed by scripts/embed_node_tools.py
|
|
3
|
+
# Run that script before building or testing
|
|
4
|
+
|
|
5
|
+
*/node_modules/
|
|
6
|
+
*/package.json
|
|
7
|
+
|
|
8
|
+
# eslint-plugin is unbundled (entire dist/ directory)
|
|
9
|
+
# All other tools are bundled (single cli.js file)
|
|
10
|
+
eslint-plugin/
|
|
11
|
+
|
|
12
|
+
# Common generated/temp files
|
|
13
|
+
*.log
|
|
14
|
+
.DS_Store
|
|
15
|
+
.npm/
|
|
16
|
+
npm-debug.log*
|
|
17
|
+
node_modules/.cache/
|
invar/node_tools/MANIFEST
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for @invar/eslint-plugin
|
|
4
|
+
*
|
|
5
|
+
* Runs ESLint with @invar/* rules pre-configured.
|
|
6
|
+
* Outputs standard ESLint JSON format for integration with guard_ts.py.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node cli.js [path] [--config=recommended|strict]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* path Project directory to lint (default: current directory)
|
|
13
|
+
* --config Use 'recommended' or 'strict' preset (default: recommended)
|
|
14
|
+
* --help Show help message
|
|
15
|
+
*/
|
|
16
|
+
import { ESLint } from 'eslint';
|
|
17
|
+
import { resolve, dirname } from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { statSync, realpathSync } from 'fs';
|
|
20
|
+
import plugin from './index.js';
|
|
21
|
+
// Get directory containing this CLI script (for resolving node_modules)
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
function parseArgs(args) {
|
|
25
|
+
const projectPath = args.find(arg => !arg.startsWith('--')) || '.';
|
|
26
|
+
const configArg = args.find(arg => arg.startsWith('--config='));
|
|
27
|
+
const config = configArg?.split('=')[1] === 'strict' ? 'strict' : 'recommended';
|
|
28
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
29
|
+
return { projectPath, config, help };
|
|
30
|
+
}
|
|
31
|
+
function printHelp() {
|
|
32
|
+
console.log(`
|
|
33
|
+
@invar/eslint-plugin - ESLint with Invar-specific rules
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
node cli.js [path] [options]
|
|
37
|
+
|
|
38
|
+
Arguments:
|
|
39
|
+
path Project directory to lint (default: current directory)
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
--config=MODE Use 'recommended' or 'strict' preset (default: recommended)
|
|
43
|
+
--help, -h Show this help message
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
node cli.js # Lint current directory (recommended mode)
|
|
47
|
+
node cli.js ./src # Lint specific directory
|
|
48
|
+
node cli.js --config=strict # Use strict mode (all rules as errors)
|
|
49
|
+
|
|
50
|
+
Output:
|
|
51
|
+
JSON format compatible with ESLint's --format=json
|
|
52
|
+
Exit code 0 if no errors, 1 if errors found
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
async function main() {
|
|
56
|
+
const args = parseArgs(process.argv.slice(2));
|
|
57
|
+
if (args.help) {
|
|
58
|
+
printHelp();
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
const projectPath = resolve(args.projectPath);
|
|
62
|
+
// Validate resolved path is within current working directory
|
|
63
|
+
// This prevents path traversal attacks via "../../../etc/passwd" patterns
|
|
64
|
+
// and symlink-based bypasses (e.g., "./symlink_inside/../../../etc/passwd")
|
|
65
|
+
const cwd = process.cwd();
|
|
66
|
+
try {
|
|
67
|
+
// Use realpath to resolve symlinks and prevent bypass attacks
|
|
68
|
+
const realProjectPath = realpathSync(projectPath);
|
|
69
|
+
const realCwd = realpathSync(cwd);
|
|
70
|
+
if (!realProjectPath.startsWith(realCwd)) {
|
|
71
|
+
console.error(`Error: Project path must be within current directory`);
|
|
72
|
+
console.error(` Requested: ${args.projectPath}`);
|
|
73
|
+
console.error(` Resolved: ${realProjectPath}`);
|
|
74
|
+
console.error(` Working dir: ${realCwd}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// If realpath fails (path doesn't exist), fall back to string comparison
|
|
80
|
+
// This allows error messages to be more specific
|
|
81
|
+
if (!projectPath.startsWith(cwd)) {
|
|
82
|
+
console.error(`Error: Project path must be within current directory`);
|
|
83
|
+
console.error(` Requested: ${args.projectPath}`);
|
|
84
|
+
console.error(` Resolved: ${projectPath}`);
|
|
85
|
+
console.error(` Working dir: ${cwd}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
// Get the rules config for the selected mode
|
|
91
|
+
const selectedConfig = plugin.configs?.[args.config];
|
|
92
|
+
if (!selectedConfig || !selectedConfig.rules) {
|
|
93
|
+
console.error(`Config "${args.config}" not found or invalid`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Create ESLint instance with programmatic configuration
|
|
97
|
+
// Set cwd to CLI directory so ESLint can find parser in our node_modules
|
|
98
|
+
const eslint = new ESLint({
|
|
99
|
+
useEslintrc: false, // Don't load .eslintrc files
|
|
100
|
+
cwd: __dirname, // Set working directory to CLI location for module resolution
|
|
101
|
+
baseConfig: {
|
|
102
|
+
parser: '@typescript-eslint/parser',
|
|
103
|
+
parserOptions: {
|
|
104
|
+
ecmaVersion: 2022,
|
|
105
|
+
sourceType: 'module',
|
|
106
|
+
},
|
|
107
|
+
plugins: ['@invar'],
|
|
108
|
+
rules: selectedConfig.rules,
|
|
109
|
+
},
|
|
110
|
+
plugins: {
|
|
111
|
+
'@invar': plugin, // Register our plugin programmatically
|
|
112
|
+
},
|
|
113
|
+
}); // Type assertion for ESLint config complexity
|
|
114
|
+
// Lint the project - detect if path is a file or directory
|
|
115
|
+
// ESLint defaults to .js only, so we need glob patterns for .ts/.tsx
|
|
116
|
+
let filesToLint;
|
|
117
|
+
try {
|
|
118
|
+
const stats = statSync(projectPath);
|
|
119
|
+
// Note: Advisory check for optimization - TOCTOU race condition is acceptable
|
|
120
|
+
// because ESLint will handle file system changes gracefully during actual linting
|
|
121
|
+
if (stats.isFile()) {
|
|
122
|
+
// Single file - lint it directly
|
|
123
|
+
filesToLint = [projectPath];
|
|
124
|
+
}
|
|
125
|
+
else if (stats.isDirectory()) {
|
|
126
|
+
// Directory - use glob patterns for TypeScript files primarily
|
|
127
|
+
// Note: Focus on TypeScript files as this is a TypeScript Guard tool
|
|
128
|
+
filesToLint = [
|
|
129
|
+
`${projectPath}/**/*.ts`,
|
|
130
|
+
`${projectPath}/**/*.tsx`,
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.error(`Error: Path is neither a file nor a directory: ${projectPath}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
140
|
+
console.error(`Error: Cannot access path: ${errorMessage}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const results = await eslint.lintFiles(filesToLint);
|
|
144
|
+
// Output in standard ESLint JSON format (compatible with guard_ts.py)
|
|
145
|
+
const formatter = await eslint.loadFormatter('json');
|
|
146
|
+
const resultText = await Promise.resolve(formatter.format(results, {
|
|
147
|
+
cwd: projectPath,
|
|
148
|
+
rulesMeta: eslint.getRulesMetaForResults(results),
|
|
149
|
+
}));
|
|
150
|
+
console.log(resultText);
|
|
151
|
+
// Exit with error code if there are errors
|
|
152
|
+
const hasErrors = results.some(result => result.errorCount > 0);
|
|
153
|
+
process.exit(hasErrors ? 1 : 0);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// Sanitize error message to avoid leaking file paths or system information
|
|
157
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
158
|
+
console.error(`ESLint failed: ${errorMessage}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
main();
|
|
163
|
+
//# sourceMappingURL=cli.js.map
|