pi-readseek 0.1.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/LICENSE +22 -0
- package/README.md +41 -0
- package/index.ts +142 -0
- package/package.json +73 -0
- package/prompts/edit.md +113 -0
- package/prompts/find.md +19 -0
- package/prompts/grep.md +26 -0
- package/prompts/ls.md +11 -0
- package/prompts/read.md +33 -0
- package/prompts/sg.md +25 -0
- package/prompts/write.md +46 -0
- package/src/binary-detect.ts +22 -0
- package/src/binary-resolution.ts +77 -0
- package/src/coerce-obvious-int.ts +39 -0
- package/src/context-application.ts +70 -0
- package/src/context-hygiene.ts +503 -0
- package/src/diff-data.ts +303 -0
- package/src/doom-loop-suggestions.ts +42 -0
- package/src/doom-loop.ts +216 -0
- package/src/edit-classify.ts +190 -0
- package/src/edit-diff.ts +354 -0
- package/src/edit-output.ts +107 -0
- package/src/edit-render-helpers.ts +141 -0
- package/src/edit-syntax-validate.ts +120 -0
- package/src/edit.ts +725 -0
- package/src/find-parsers.ts +89 -0
- package/src/find-stat.ts +36 -0
- package/src/find.ts +613 -0
- package/src/grep-budget.ts +79 -0
- package/src/grep-output.ts +197 -0
- package/src/grep-render-helpers.ts +77 -0
- package/src/grep-symbol-scope.ts +197 -0
- package/src/grep.ts +792 -0
- package/src/hashline.ts +747 -0
- package/src/ls.ts +293 -0
- package/src/map-cache.ts +152 -0
- package/src/path-utils.ts +24 -0
- package/src/pending-diff-preview.ts +269 -0
- package/src/persistent-map-cache.ts +251 -0
- package/src/read-local-bundle.ts +87 -0
- package/src/read-output.ts +212 -0
- package/src/read-render-helpers.ts +104 -0
- package/src/read.ts +748 -0
- package/src/readseek/constants.ts +21 -0
- package/src/readseek/enums.ts +38 -0
- package/src/readseek/formatter.ts +431 -0
- package/src/readseek/language-detect.ts +29 -0
- package/src/readseek/mapper.ts +69 -0
- package/src/readseek/parser-errors.ts +22 -0
- package/src/readseek/parser-loader.ts +83 -0
- package/src/readseek/symbol-error-format.ts +18 -0
- package/src/readseek/symbol-lookup.ts +294 -0
- package/src/readseek/types.ts +79 -0
- package/src/readseek-client.ts +343 -0
- package/src/readseek-error-codes.ts +54 -0
- package/src/readseek-settings.ts +287 -0
- package/src/readseek-value.ts +144 -0
- package/src/replace-symbol.ts +74 -0
- package/src/runtime.ts +3 -0
- package/src/sg-output.ts +88 -0
- package/src/sg.ts +308 -0
- package/src/syntax-validate-mode.ts +25 -0
- package/src/tool-prompt-metadata.ts +76 -0
- package/src/tui-diff-component.ts +86 -0
- package/src/tui-diff-renderer.ts +92 -0
- package/src/tui-render-utils.ts +129 -0
- package/src/write.ts +532 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const VARIANT_KEYS = ["set_line", "replace_lines", "insert_after", "replace"] as const;
|
|
2
|
+
|
|
3
|
+
export type EditTypeCounts = {
|
|
4
|
+
set_line: number;
|
|
5
|
+
replace_lines: number;
|
|
6
|
+
insert_after: number;
|
|
7
|
+
replace: number;
|
|
8
|
+
total: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function countEditTypes(edits: unknown[] | undefined): EditTypeCounts {
|
|
12
|
+
const counts: EditTypeCounts = {
|
|
13
|
+
set_line: 0,
|
|
14
|
+
replace_lines: 0,
|
|
15
|
+
insert_after: 0,
|
|
16
|
+
replace: 0,
|
|
17
|
+
total: 0,
|
|
18
|
+
};
|
|
19
|
+
if (!edits) return counts;
|
|
20
|
+
for (const edit of edits) {
|
|
21
|
+
counts.total++;
|
|
22
|
+
if (edit && typeof edit === "object") {
|
|
23
|
+
for (const key of VARIANT_KEYS) {
|
|
24
|
+
if (key in edit) {
|
|
25
|
+
counts[key]++;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return counts;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DiffStats {
|
|
35
|
+
added: number;
|
|
36
|
+
removed: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseDiffStats(diff: string | undefined): DiffStats {
|
|
40
|
+
if (!diff) return { added: 0, removed: 0 };
|
|
41
|
+
let added = 0;
|
|
42
|
+
let removed = 0;
|
|
43
|
+
for (const line of diff.split("\n")) {
|
|
44
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
45
|
+
if (line.startsWith("+")) added++;
|
|
46
|
+
else if (line.startsWith("-")) removed++;
|
|
47
|
+
}
|
|
48
|
+
return { added, removed };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EditCallTextResult {
|
|
52
|
+
path: string | null;
|
|
53
|
+
suffix: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatEditCallText(
|
|
57
|
+
args: Record<string, unknown> | undefined,
|
|
58
|
+
argsComplete: boolean,
|
|
59
|
+
): EditCallTextResult {
|
|
60
|
+
const rawPath = typeof args?.path === "string" ? args.path : null;
|
|
61
|
+
|
|
62
|
+
if (!argsComplete) {
|
|
63
|
+
return { path: rawPath, suffix: undefined };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const edits = args?.edits;
|
|
67
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
68
|
+
return { path: rawPath, suffix: undefined };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const counts = countEditTypes(edits);
|
|
72
|
+
const parts: string[] = [];
|
|
73
|
+
for (const key of VARIANT_KEYS) {
|
|
74
|
+
if (counts[key] > 0) {
|
|
75
|
+
parts.push(`${counts[key]} ${key}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const word = counts.total === 1 ? "edit" : "edits";
|
|
79
|
+
const suffix = `${counts.total} ${word} (${parts.join(", ")})`;
|
|
80
|
+
return { path: rawPath, suffix };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface EditResultTextInput {
|
|
84
|
+
isError: boolean;
|
|
85
|
+
diff: string;
|
|
86
|
+
warnings: string[];
|
|
87
|
+
noopEdits: unknown[];
|
|
88
|
+
errorText: string;
|
|
89
|
+
semanticClassification?: "no-op" | "whitespace-only" | "semantic" | "mixed";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface EditResultTextOutput {
|
|
93
|
+
diffStats: string | undefined;
|
|
94
|
+
noOp: boolean;
|
|
95
|
+
warningsBadge: string | undefined;
|
|
96
|
+
errorText: string | undefined;
|
|
97
|
+
semanticBadge: string | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function formatEditResultText(input: EditResultTextInput): EditResultTextOutput {
|
|
101
|
+
const { isError, diff, warnings, noopEdits, errorText } = input;
|
|
102
|
+
|
|
103
|
+
// No-op detection: error + noopEdits present OR error text contains "No changes made"
|
|
104
|
+
const isNoOp = isError && (
|
|
105
|
+
(Array.isArray(noopEdits) && noopEdits.length > 0) ||
|
|
106
|
+
errorText.includes("No changes made")
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Diff stats
|
|
110
|
+
const stats = parseDiffStats(diff);
|
|
111
|
+
const hasDiffStats = stats.added > 0 || stats.removed > 0;
|
|
112
|
+
const diffStats = hasDiffStats ? `+${stats.added} / -${stats.removed}` : undefined;
|
|
113
|
+
|
|
114
|
+
// Warnings badge
|
|
115
|
+
let warningsBadge: string | undefined;
|
|
116
|
+
if (warnings.length === 1) {
|
|
117
|
+
warningsBadge = "\u26a0 1 warning";
|
|
118
|
+
} else if (warnings.length > 1) {
|
|
119
|
+
warningsBadge = `\u26a0 ${warnings.length} warnings`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Error text (only for errors)
|
|
123
|
+
const showErrorText = isError ? errorText : undefined;
|
|
124
|
+
|
|
125
|
+
// Semantic classification badge (success only)
|
|
126
|
+
let semanticBadge: string | undefined;
|
|
127
|
+
if (!isError && !isNoOp && input.semanticClassification) {
|
|
128
|
+
switch (input.semanticClassification) {
|
|
129
|
+
case "whitespace-only": semanticBadge = "ws-only"; break;
|
|
130
|
+
case "semantic": semanticBadge = "\u2713 semantic"; break;
|
|
131
|
+
case "mixed": semanticBadge = "mixed"; break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
diffStats,
|
|
136
|
+
noOp: isNoOp,
|
|
137
|
+
warningsBadge,
|
|
138
|
+
errorText: showErrorText,
|
|
139
|
+
semanticBadge,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Node as SyntaxNode, Parser as WasmParser, Tree } from "web-tree-sitter";
|
|
2
|
+
import { detectLanguage } from "./readseek/language-detect.js";
|
|
3
|
+
import { getWasmParser } from "./readseek/parser-loader.js";
|
|
4
|
+
import { reportParserError } from "./readseek/parser-errors.js";
|
|
5
|
+
|
|
6
|
+
export interface ValidateInput {
|
|
7
|
+
filePath: string;
|
|
8
|
+
before: string | undefined;
|
|
9
|
+
after: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ValidateResult {
|
|
13
|
+
errorLines: string[];
|
|
14
|
+
newErrorCount: number;
|
|
15
|
+
newMissingCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface NodeStats {
|
|
19
|
+
errors: Array<{ startLine: number; endLine: number }>;
|
|
20
|
+
missing: Array<{ startLine: number; endLine: number }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function countNodes(parser: WasmParser, source: string): NodeStats {
|
|
24
|
+
const tree: Tree | null = parser.parse(source);
|
|
25
|
+
const errors: Array<{ startLine: number; endLine: number }> = [];
|
|
26
|
+
const missing: Array<{ startLine: number; endLine: number }> = [];
|
|
27
|
+
if (!tree) return { errors, missing };
|
|
28
|
+
try {
|
|
29
|
+
const stack: SyntaxNode[] = [tree.rootNode];
|
|
30
|
+
while (stack.length > 0) {
|
|
31
|
+
const node = stack.pop()!;
|
|
32
|
+
if (node.type === "ERROR") {
|
|
33
|
+
errors.push({
|
|
34
|
+
startLine: node.startPosition.row + 1,
|
|
35
|
+
endLine: node.endPosition.row + 1,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (node.isMissing) {
|
|
39
|
+
missing.push({
|
|
40
|
+
startLine: node.startPosition.row + 1,
|
|
41
|
+
endLine: node.endPosition.row + 1,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
45
|
+
const c = node.namedChild(i);
|
|
46
|
+
if (c) stack.push(c);
|
|
47
|
+
}
|
|
48
|
+
// Also descend into anonymous children to find MISSING tokens.
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const c = node.child(i);
|
|
51
|
+
if (c && !c.isNamed) stack.push(c);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { errors, missing };
|
|
55
|
+
} finally {
|
|
56
|
+
tree.delete();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function dedupeSortLines(
|
|
61
|
+
ranges: Array<{ startLine: number; endLine: number }>,
|
|
62
|
+
): string[] {
|
|
63
|
+
const seen = new Set<string>();
|
|
64
|
+
const out: Array<{ key: string; start: number }> = [];
|
|
65
|
+
for (const r of ranges) {
|
|
66
|
+
const key = r.startLine === r.endLine
|
|
67
|
+
? String(r.startLine)
|
|
68
|
+
: `${r.startLine}-${r.endLine}`;
|
|
69
|
+
if (!seen.has(key)) {
|
|
70
|
+
seen.add(key);
|
|
71
|
+
out.push({ key, start: r.startLine });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
out.sort((a, b) => a.start - b.start);
|
|
75
|
+
return out.map((o) => o.key);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function validateSyntaxRegression(
|
|
79
|
+
input: ValidateInput,
|
|
80
|
+
): Promise<ValidateResult | null> {
|
|
81
|
+
const lang = detectLanguage(input.filePath);
|
|
82
|
+
if (!lang) return null;
|
|
83
|
+
if (lang.id !== "rust" && lang.id !== "cpp" && lang.id !== "c-header" && lang.id !== "java") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const parser = await getWasmParser(lang.id);
|
|
87
|
+
if (!parser) return null;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const beforeStats = input.before === undefined
|
|
91
|
+
? { errors: [], missing: [] }
|
|
92
|
+
: countNodes(parser, input.before);
|
|
93
|
+
const afterStats = countNodes(parser, input.after);
|
|
94
|
+
|
|
95
|
+
// ±1 tolerance on ERROR count, no tolerance on MISSING.
|
|
96
|
+
const newErrorCount = Math.max(
|
|
97
|
+
0,
|
|
98
|
+
afterStats.errors.length - beforeStats.errors.length - 1,
|
|
99
|
+
);
|
|
100
|
+
const newMissingCount = Math.max(
|
|
101
|
+
0,
|
|
102
|
+
afterStats.missing.length - beforeStats.missing.length,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (newErrorCount === 0 && newMissingCount === 0) return null;
|
|
106
|
+
|
|
107
|
+
const errorLines = dedupeSortLines([
|
|
108
|
+
...afterStats.errors,
|
|
109
|
+
...afterStats.missing,
|
|
110
|
+
]);
|
|
111
|
+
return { errorLines, newErrorCount, newMissingCount };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
reportParserError(`wasm:syntax-validate:${lang.id}:${err instanceof Error ? err.message : String(err)}`, err, {
|
|
114
|
+
context: `tree-sitter syntax validation failed for ${lang.id}`,
|
|
115
|
+
});
|
|
116
|
+
return null;
|
|
117
|
+
} finally {
|
|
118
|
+
parser.delete();
|
|
119
|
+
}
|
|
120
|
+
}
|