Skip to content

Commit 81d908b

Browse files
committed
chore: sync and update various features and documentation
1 parent e8c0a31 commit 81d908b

16 files changed

Lines changed: 815 additions & 568 deletions

File tree

nitpick-mcp/nitpick_mcp.py

Lines changed: 147 additions & 493 deletions
Large diffs are not rendered by default.

nitpick-mcp/test_mcp.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
import json
4+
import subprocess
5+
import time
6+
7+
mcp_script = "./nitpick_mcp.py"
8+
9+
p = subprocess.Popen(
10+
["python3", mcp_script],
11+
stdin=subprocess.PIPE,
12+
stdout=subprocess.PIPE,
13+
stderr=subprocess.PIPE,
14+
text=True
15+
)
16+
17+
def send_request(method, params, req_id):
18+
req = {
19+
"jsonrpc": "2.0",
20+
"id": req_id,
21+
"method": method,
22+
"params": params
23+
}
24+
p.stdin.write(json.dumps(req) + "\n")
25+
p.stdin.flush()
26+
line = p.stdout.readline()
27+
if not line:
28+
return None
29+
return json.loads(line)
30+
31+
try:
32+
print("Testing initialize...")
33+
res = send_request("initialize", {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name":"test","version":"1"}}, 1)
34+
assert res is not None, "Failed to get response"
35+
assert "result" in res, f"Expected result in {res}"
36+
37+
# After initialize, the server sends an initialized notification?
38+
# Actually client sends initialized notification.
39+
notif = {
40+
"jsonrpc": "2.0",
41+
"method": "notifications/initialized",
42+
"params": {}
43+
}
44+
p.stdin.write(json.dumps(notif) + "\n")
45+
p.stdin.flush()
46+
47+
print("Testing tools/list...")
48+
res = send_request("tools/list", {}, 2)
49+
assert res is not None, "Failed to get response"
50+
assert "result" in res, f"Expected result in {res}"
51+
tools = res["result"]["tools"]
52+
tool_names = [t["name"] for t in tools]
53+
assert "nitpick_compile" in tool_names
54+
assert "nitpick_check" in tool_names
55+
assert "nitpick_docs" in tool_names
56+
57+
print("All MCP tests passed!")
58+
finally:
59+
p.terminate()

nitpick-safety/README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,4 @@ src/parser.npk:91: [RAW] raw() strips Result<T> — caller must handle fa
5252
| `1` | One or more findings present |
5353
| `2` | Usage error or no files found |
5454

55-
## v1 Limitations
5655

57-
- `//` comment stripping is naive and does not account for `//` inside string literals
58-
- Brace counting for `failsafe` block triviality can be thrown off by unbalanced
59-
`{` / `}` inside string literals within the block
60-
- Content placed on the same line as the `failsafe` opening brace (after the `{`)
61-
is not checked for triviality in multi-line blocks
62-
- String literals are not excluded from pattern matching; a string containing
63-
`raw(` or `wild` would be flagged
64-
65-
These are all acceptable for a v1 review-aid tool. False positives require a
66-
quick human glance; false negatives for the edge cases above are rare in practice.

nitpick-safety/mock_file.npk

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
func main() -> NIL:
2+
// This is a comment with wild and raw(
3+
let str1 = "This string has wild and raw( and { and }"
4+
let str2 = `This is a
5+
multi-line string with wild and
6+
raw( and {
7+
and // comments inside strings
8+
}`
9+
10+
// Test failsafe string triviality
11+
failsafe {
12+
pass("not trivial because string {");
13+
}
14+
15+
failsafe {
16+
pass(NIL)
17+
}
18+
19+
let a = wildx alloc(10) // should trigger
20+
raw(a) // should trigger
21+
end

nitpick-safety/nitpick-safety

0 Bytes
Binary file not shown.

nitpick-safety/nitpick_safety.c

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,38 @@ static void strip_nl(char *s)
181181
if (n > 0 && s[n - 1] == '\r') s[--n] = '\0';
182182
}
183183

184-
/* Strip // line comment in-place. Naive: does not handle // inside strings. */
185-
static void strip_line_comment(char *s)
184+
/*
185+
* mask_strings_and_comments: Masks string contents (replaces with space) to prevent
186+
* false positive pattern/brace matches, and correctly strips // comments.
187+
* Tracks quote state across lines to support multi-line strings.
188+
*/
189+
static void mask_strings_and_comments(char *s, int *in_quote)
186190
{
187-
char *p = strstr(s, "//");
188-
if (p) *p = '\0';
191+
int escape = 0;
192+
for (char *p = s; *p; p++) {
193+
if (!*in_quote) {
194+
if (*p == '/' && *(p+1) == '/') {
195+
*p = '\0';
196+
break;
197+
}
198+
if (*p == '"') *in_quote = 1;
199+
else if (*p == '`') *in_quote = 2;
200+
} else {
201+
if (escape) {
202+
*p = ' ';
203+
escape = 0;
204+
} else if (*p == '\\') {
205+
*p = ' ';
206+
escape = 1;
207+
} else if ((*in_quote == 1 && *p == '"') || (*in_quote == 2 && *p == '`')) {
208+
*in_quote = 0;
209+
} else {
210+
if (*p != '\n' && *p != '\r') {
211+
*p = ' ';
212+
}
213+
}
214+
}
215+
}
189216
}
190217

191218
/*
@@ -228,13 +255,15 @@ static void scan_file(const char *path)
228255
int fs_open_line = 0; /* line where the opening '{' was found */
229256
int fs_trivial = 1; /* cleared once we see non-trivial content */
230257

258+
int in_quote = 0; /* string masking state: 0 = none, 1 = ", 2 = ` */
259+
231260
while (fgets(raw, sizeof(raw), f)) {
232261
lineno++;
233262
strip_nl(raw);
234263

235264
/* working copy: strip // comment so patterns don't fire in comments */
236265
memcpy(line, raw, strlen(raw) + 1);
237-
strip_line_comment(line);
266+
mask_strings_and_comments(line, &in_quote);
238267

239268
/* first non-whitespace character */
240269
const char *trimmed = line;

nitpick-safety/output.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3 findings across 1 file
3+
mock_file.npk:15: [FAILSAFE] empty or trivial failsafe block — error is silently swallowed
4+
mock_file.npk:19: [WILD] wildx allocation — no GC, no bounds checking; manual lifetime required
5+
mock_file.npk:20: [RAW] raw() strips Result<T> — caller must handle failure explicitly

nitpick-safety/test_safety.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "Building nitpick-safety..."
5+
make
6+
7+
echo "Creating mock_file.npk..."
8+
cat << 'EOF' > mock_file.npk
9+
func main() -> NIL:
10+
// This is a comment with wild and raw(
11+
let str1 = "This string has wild and raw( and { and }"
12+
let str2 = `This is a
13+
multi-line string with wild and
14+
raw( and {
15+
and // comments inside strings
16+
}`
17+
18+
// Test failsafe string triviality
19+
failsafe {
20+
pass("not trivial because string {");
21+
}
22+
23+
failsafe {
24+
pass(NIL)
25+
}
26+
27+
let a = wildx alloc(10) // should trigger
28+
raw(a) // should trigger
29+
end
30+
EOF
31+
32+
echo "Running nitpick-safety..."
33+
./nitpick-safety mock_file.npk > output.txt 2>&1 || true
34+
35+
cat output.txt
36+
37+
if grep -q "mock_file.npk:19: \[WILD\]" output.txt && grep -q "mock_file.npk:20: \[RAW\]" output.txt && grep -q "mock_file.npk:15: \[FAILSAFE\]" output.txt; then
38+
echo "Expected hits found!"
39+
else
40+
echo "Missing expected hits!"
41+
exit 1
42+
fi
43+
44+
if grep -q "str1" output.txt || grep -q "str2" output.txt || grep -q "multi-line" output.txt || grep -q "comment with wild" output.txt || grep -q "not trivial" output.txt; then
45+
echo "False positives found inside strings/comments!"
46+
exit 1
47+
else
48+
echo "No false positives in strings/comments."
49+
fi
50+
51+
echo "All tests passed!"

nitpick-test/leaky.npk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
extern func:malloc = void*(uint64:size);
2+
3+
func:failsafe = int32(tbb32:err) {
4+
exit 1i32;
5+
};
6+
7+
func:main = int32() {
8+
malloc(1024u64);
9+
return 0i32;
10+
};

0 commit comments

Comments
 (0)