holycodex 0.2.1
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.md +82 -0
- package/README.md +107 -0
- package/THIRD-PARTY-NOTICES.md +7 -0
- package/bin/holycodex.js +2 -0
- package/marketplace.json +12 -0
- package/package.json +59 -0
- package/plugin/.codex-plugin/plugin.json +29 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/agents/explorer.toml +6 -0
- package/plugin/agents/librarian.toml +6 -0
- package/plugin/agents/worker.toml +6 -0
- package/plugin/hooks/hooks.json +93 -0
- package/plugin/runtime/bootstrap.js +23 -0
- package/plugin/runtime/cli.js +233 -0
- package/plugin/runtime/git-bash.js +359 -0
- package/plugin/runtime/lsp.js +3674 -0
- package/plugin/runtime/rules.js +146 -0
- package/plugin/runtime/src-tRIOClWZ.js +209 -0
- package/plugin/skills/ast-grep/LICENSE +21 -0
- package/plugin/skills/ast-grep/SKILL.md +20 -0
- package/plugin/skills/ast-grep/references/cli.md +3 -0
- package/plugin/skills/ast-grep/references/install.md +3 -0
- package/plugin/skills/ast-grep/references/patterns.md +3 -0
- package/plugin/skills/ast-grep/references/pitfalls.md +3 -0
- package/plugin/skills/ast-grep/references/recipes.md +9 -0
- package/plugin/skills/ast-grep/references/sgconfig.md +3 -0
- package/plugin/skills/ast-grep/references/yaml-rules.md +3 -0
- package/plugin/skills/caveman/LICENSE +21 -0
- package/plugin/skills/caveman/SKILL.md +84 -0
- package/plugin/skills/comment-checker/SKILL.md +16 -0
- package/plugin/skills/compress/SKILL.md +26 -0
- package/plugin/skills/debugging/SKILL.md +23 -0
- package/plugin/skills/debugging/references/runtimes/README.md +9 -0
- package/plugin/skills/debugging/references/tools/README.md +8 -0
- package/plugin/skills/define-goal/LICENSE.txt +201 -0
- package/plugin/skills/define-goal/SKILL.md +36 -0
- package/plugin/skills/frontend/ATTRIBUTION.md +218 -0
- package/plugin/skills/frontend/LICENSE-Apache-2.0.txt +201 -0
- package/plugin/skills/frontend/SKILL.md +42 -0
- package/plugin/skills/frontend/references/perfection/README.md +5 -0
- package/plugin/skills/frontend/references/perfection/react-perf-tooling.md +3 -0
- package/plugin/skills/frontend/references/ui-ux-db/README.md +5 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/charts.csv +26 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/colors.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/icons.csv +106 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/landing.csv +35 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/products.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/react-performance.csv +45 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/astro.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/flutter.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/html-tailwind.csv +56 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/jetpack-compose.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nextjs.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxt-ui.csv +51 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxtjs.csv +59 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react-native.csv +52 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/shadcn.csv +61 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/svelte.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/swiftui.csv +51 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/vue.csv +50 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/styles.csv +85 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/typography.csv +74 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/ui-reasoning.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/ux-guidelines.csv +100 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/web-interface.csv +31 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/core.py +262 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +1148 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +114 -0
- package/plugin/skills/handoff/SKILL.md +10 -0
- package/plugin/skills/lsp/SKILL.md +35 -0
- package/plugin/skills/lsp-setup/SKILL.md +18 -0
- package/plugin/skills/lsp-setup/scripts/detect-lsp.ts +233 -0
- package/plugin/skills/lsp-setup/scripts/lsp-server-table.ts +188 -0
- package/plugin/skills/lsp-setup/scripts/tsconfig.json +17 -0
- package/plugin/skills/lsp-setup/scripts/verify-lsp.ts +166 -0
- package/plugin/skills/plan/SKILL.md +14 -0
- package/plugin/skills/plan-review/SKILL.md +12 -0
- package/plugin/skills/programming/SKILL.md +35 -0
- package/plugin/skills/programming/references/code-smells.md +3 -0
- package/plugin/skills/programming/references/go/README.md +3 -0
- package/plugin/skills/programming/references/logging.md +3 -0
- package/plugin/skills/programming/references/python/README.md +3 -0
- package/plugin/skills/programming/references/rust/README.md +3 -0
- package/plugin/skills/programming/references/rust-ub/README.md +3 -0
- package/plugin/skills/programming/references/typescript/README.md +3 -0
- package/plugin/skills/refactor/SKILL.md +21 -0
- package/plugin/skills/remove-ai-slops/SKILL.md +20 -0
- package/plugin/skills/rules/SKILL.md +16 -0
- package/plugin/skills/security-research/SKILL.md +38 -0
- package/plugin/skills/tdd/SKILL.md +24 -0
- package/plugin/skills/tdd/mocking.md +60 -0
- package/plugin/skills/tdd/tests.md +77 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
//#region src/rules-hook.ts
|
|
8
|
+
var DEFAULT_RULE_LIMIT = 8e3;
|
|
9
|
+
var DEFAULT_RESULT_LIMIT = 24e3;
|
|
10
|
+
var SOURCES = [
|
|
11
|
+
".holycodex/rules",
|
|
12
|
+
".codex/rules",
|
|
13
|
+
".github/instructions"
|
|
14
|
+
];
|
|
15
|
+
async function loadRules(cwd, targetPath) {
|
|
16
|
+
if (process.env.HOLYCODEX_RULES_DISABLED === "1") return [];
|
|
17
|
+
const candidates = [join(cwd, "CONTEXT.md"), join(cwd, ".github", "copilot-instructions.md")];
|
|
18
|
+
for (const source of SOURCES) candidates.push(...await markdownFiles(join(cwd, source)));
|
|
19
|
+
const rules = [];
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
let total = 0;
|
|
22
|
+
for (const path of candidates) {
|
|
23
|
+
const text = await readable(path);
|
|
24
|
+
if (text === void 0) continue;
|
|
25
|
+
const parsed = parseRule(text);
|
|
26
|
+
const staticSource = path.endsWith("CONTEXT.md") || path.endsWith("copilot-instructions.md");
|
|
27
|
+
if (!(targetPath === void 0 ? staticSource || parsed.alwaysApply : parsed.globs.some((glob) => globMatches(glob, relative(cwd, targetPath)))) || parsed.body.length === 0) continue;
|
|
28
|
+
const body = parsed.body.slice(0, numberFromEnv("HOLYCODEX_RULES_MAX_RULE_CHARS", DEFAULT_RULE_LIMIT));
|
|
29
|
+
const hash = createHash("sha256").update(body).digest("hex");
|
|
30
|
+
if (seen.has(hash)) continue;
|
|
31
|
+
if (total + body.length > numberFromEnv("HOLYCODEX_RULES_MAX_RESULT_CHARS", DEFAULT_RESULT_LIMIT)) break;
|
|
32
|
+
seen.add(hash);
|
|
33
|
+
total += body.length;
|
|
34
|
+
rules.push({
|
|
35
|
+
path,
|
|
36
|
+
body
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return rules;
|
|
40
|
+
}
|
|
41
|
+
async function runRulesHook(input) {
|
|
42
|
+
if (typeof input.cwd !== "string" || typeof input.session_id !== "string") return "";
|
|
43
|
+
const event = input.hook_event_name;
|
|
44
|
+
const cache = cachePath(input.session_id);
|
|
45
|
+
if (event === "PostCompact") {
|
|
46
|
+
await rm(cache, { force: true });
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
const target = event === "PostToolUse" ? editPath(input.tool_input, input.cwd) : void 0;
|
|
50
|
+
if (event === "PostToolUse" && target === void 0) return "";
|
|
51
|
+
if (event !== "SessionStart" && event !== "UserPromptSubmit" && event !== "PostToolUse") return "";
|
|
52
|
+
const emitted = await filterCached(cache, await loadRules(input.cwd, target), typeof input.transcript_path === "string" ? await readable(input.transcript_path) ?? "" : "");
|
|
53
|
+
if (emitted.length === 0) return "";
|
|
54
|
+
const context = emitted.map((rule) => `Rule ${relative(input.cwd, rule.path)}:\n${rule.body}`).join("\n\n");
|
|
55
|
+
return `${JSON.stringify({ hookSpecificOutput: {
|
|
56
|
+
hookEventName: event,
|
|
57
|
+
additionalContext: context
|
|
58
|
+
} })}\n`;
|
|
59
|
+
}
|
|
60
|
+
function parseRule(text) {
|
|
61
|
+
if (!text.startsWith("---\n")) return {
|
|
62
|
+
alwaysApply: false,
|
|
63
|
+
globs: [],
|
|
64
|
+
body: text.trim()
|
|
65
|
+
};
|
|
66
|
+
const end = text.indexOf("\n---\n", 4);
|
|
67
|
+
if (end < 0) return {
|
|
68
|
+
alwaysApply: false,
|
|
69
|
+
globs: [],
|
|
70
|
+
body: text.trim()
|
|
71
|
+
};
|
|
72
|
+
const header = text.slice(4, end);
|
|
73
|
+
const globs = [...(/^globs:\s*(.+)$/m.exec(header)?.[1] ?? "").matchAll(/["']([^"']+)["']/g)].map((match) => match[1]).filter((value) => value !== void 0);
|
|
74
|
+
return {
|
|
75
|
+
alwaysApply: /^alwaysApply:\s*true\s*$/m.test(header),
|
|
76
|
+
globs,
|
|
77
|
+
body: text.slice(end + 5).trim()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function globMatches(glob, path) {
|
|
81
|
+
const normalized = path.replaceAll("\\", "/");
|
|
82
|
+
const pattern = glob.replaceAll("\\", "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**", "\0").replaceAll("*", "[^/]*").replaceAll("\0", ".*");
|
|
83
|
+
return new RegExp(`^${pattern}$`).test(normalized);
|
|
84
|
+
}
|
|
85
|
+
async function markdownFiles(root) {
|
|
86
|
+
try {
|
|
87
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
88
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
89
|
+
const path = join(root, entry.name);
|
|
90
|
+
return entry.isDirectory() ? markdownFiles(path) : entry.isFile() && entry.name.endsWith(".md") ? [path] : [];
|
|
91
|
+
}))).flat();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function readable(path) {
|
|
98
|
+
try {
|
|
99
|
+
return await readFile(path, "utf8");
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function editPath(value, cwd) {
|
|
106
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
107
|
+
const input = value;
|
|
108
|
+
for (const key of [
|
|
109
|
+
"filePath",
|
|
110
|
+
"file_path",
|
|
111
|
+
"path",
|
|
112
|
+
"targetPath",
|
|
113
|
+
"target_path"
|
|
114
|
+
]) if (typeof input[key] === "string") return isAbsolute(input[key]) ? input[key] : resolve(cwd, input[key]);
|
|
115
|
+
const patch = typeof input.patch === "string" ? input.patch : typeof input.input === "string" ? input.input : void 0;
|
|
116
|
+
const path = patch === void 0 ? void 0 : /^\*\*\* (?:Add|Update) File: (.+)$/m.exec(patch)?.[1]?.trim();
|
|
117
|
+
return path === void 0 ? void 0 : resolve(cwd, path);
|
|
118
|
+
}
|
|
119
|
+
async function filterCached(path, rules, transcript) {
|
|
120
|
+
const previous = new Set(JSON.parse(await readable(path) ?? "[]"));
|
|
121
|
+
const emitted = rules.filter((rule) => !previous.has(hashRule(rule)) && !transcript.includes(rule.body));
|
|
122
|
+
for (const rule of emitted) previous.add(hashRule(rule));
|
|
123
|
+
await mkdir(dirname(path), { recursive: true });
|
|
124
|
+
await writeFile(path, JSON.stringify([...previous]), "utf8");
|
|
125
|
+
return emitted;
|
|
126
|
+
}
|
|
127
|
+
function hashRule(rule) {
|
|
128
|
+
return createHash("sha256").update(rule.body).digest("hex");
|
|
129
|
+
}
|
|
130
|
+
function cachePath(session) {
|
|
131
|
+
return join(process.env.PLUGIN_DATA ?? join(tmpdir(), "holycodex-plugin-data"), "rules", `${session.replaceAll(/[^A-Za-z0-9._-]/g, "_")}.json`);
|
|
132
|
+
}
|
|
133
|
+
function numberFromEnv(name, fallback) {
|
|
134
|
+
const value = Number(process.env[name]);
|
|
135
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/rules-cli.ts
|
|
139
|
+
var raw = "";
|
|
140
|
+
stdin.setEncoding("utf8");
|
|
141
|
+
for await (const chunk of stdin) raw += chunk;
|
|
142
|
+
if (raw.trim()) {
|
|
143
|
+
const input = JSON.parse(raw);
|
|
144
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) stdout.write(await runRulesHook(input));
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
//#region packages/mcp-stdio-core/src/record.ts
|
|
2
|
+
function isPlainRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region packages/mcp-stdio-core/src/responses.ts
|
|
7
|
+
function successResponse(id, result) {
|
|
8
|
+
return {
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
id,
|
|
11
|
+
result
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function errorResponse(id, code, message, data) {
|
|
15
|
+
return {
|
|
16
|
+
jsonrpc: "2.0",
|
|
17
|
+
id,
|
|
18
|
+
error: data === void 0 ? {
|
|
19
|
+
code,
|
|
20
|
+
message
|
|
21
|
+
} : {
|
|
22
|
+
code,
|
|
23
|
+
message,
|
|
24
|
+
data
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function jsonRpcId(value) {
|
|
29
|
+
return typeof value === "string" || typeof value === "number" || value === null ? value : null;
|
|
30
|
+
}
|
|
31
|
+
function messageFromError(error) {
|
|
32
|
+
return error instanceof Error ? error.message : String(error);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region packages/mcp-stdio-core/src/transport.ts
|
|
36
|
+
var HEADER_SEPARATOR = Buffer.from("\r\n\r\n");
|
|
37
|
+
async function* readStdioJsonRpcMessages(input) {
|
|
38
|
+
let buffer = Buffer.alloc(0);
|
|
39
|
+
for await (const chunk of input) {
|
|
40
|
+
buffer = Buffer.concat([buffer, bufferFromChunk(chunk)]);
|
|
41
|
+
while (true) {
|
|
42
|
+
const result = readNextMessage(buffer);
|
|
43
|
+
if (result.kind === "incomplete") break;
|
|
44
|
+
buffer = result.remaining;
|
|
45
|
+
if (result.message) yield result.message;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const trailing = buffer.toString("utf8").trim();
|
|
49
|
+
if (trailing.length > 0) yield parseJsonPayload(trailing, "line");
|
|
50
|
+
}
|
|
51
|
+
function writeStdioJsonRpcResponse(output, response, responseMode) {
|
|
52
|
+
const body = JSON.stringify(response);
|
|
53
|
+
if (responseMode === "framed") {
|
|
54
|
+
output.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
output.write(`${body}\n`);
|
|
58
|
+
}
|
|
59
|
+
function readNextMessage(buffer) {
|
|
60
|
+
if (buffer.length === 0) return { kind: "incomplete" };
|
|
61
|
+
return startsWithContentLength(buffer) ? readFramedMessage(buffer) : readLineMessage(buffer);
|
|
62
|
+
}
|
|
63
|
+
function readLineMessage(buffer) {
|
|
64
|
+
const newlineIndex = buffer.indexOf(10);
|
|
65
|
+
if (newlineIndex === -1) return { kind: "incomplete" };
|
|
66
|
+
const line = buffer.subarray(0, newlineIndex).toString("utf8").replace(/\r$/, "");
|
|
67
|
+
if (line.trim().length === 0) return {
|
|
68
|
+
kind: "complete",
|
|
69
|
+
remaining: buffer.subarray(newlineIndex + 1)
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
kind: "complete",
|
|
73
|
+
message: parseJsonPayload(line, "line"),
|
|
74
|
+
remaining: buffer.subarray(newlineIndex + 1)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function readFramedMessage(buffer) {
|
|
78
|
+
const separatorIndex = buffer.indexOf(HEADER_SEPARATOR);
|
|
79
|
+
if (separatorIndex === -1) return { kind: "incomplete" };
|
|
80
|
+
const contentLength = parseContentLength(buffer.subarray(0, separatorIndex).toString("ascii"));
|
|
81
|
+
const bodyStart = separatorIndex + HEADER_SEPARATOR.length;
|
|
82
|
+
if (contentLength === void 0) return {
|
|
83
|
+
kind: "complete",
|
|
84
|
+
message: {
|
|
85
|
+
kind: "parse_error",
|
|
86
|
+
message: "Missing or invalid Content-Length header",
|
|
87
|
+
responseMode: "framed"
|
|
88
|
+
},
|
|
89
|
+
remaining: buffer.subarray(bodyStart)
|
|
90
|
+
};
|
|
91
|
+
const bodyEnd = bodyStart + contentLength;
|
|
92
|
+
if (buffer.length < bodyEnd) return { kind: "incomplete" };
|
|
93
|
+
return {
|
|
94
|
+
kind: "complete",
|
|
95
|
+
message: parseJsonPayload(buffer.subarray(bodyStart, bodyEnd).toString("utf8"), "framed"),
|
|
96
|
+
remaining: buffer.subarray(bodyEnd)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function startsWithContentLength(buffer) {
|
|
100
|
+
return buffer.subarray(0, 15).toString("ascii").toLowerCase() === "content-length:";
|
|
101
|
+
}
|
|
102
|
+
function parseContentLength(headers) {
|
|
103
|
+
for (const line of headers.split("\r\n")) {
|
|
104
|
+
const match = /^content-length:\s*(\d+)$/i.exec(line);
|
|
105
|
+
if (match === null) continue;
|
|
106
|
+
const value = match[1];
|
|
107
|
+
if (value === void 0) return void 0;
|
|
108
|
+
return Number(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function parseJsonPayload(payload, responseMode) {
|
|
112
|
+
try {
|
|
113
|
+
return {
|
|
114
|
+
kind: "request",
|
|
115
|
+
payload: JSON.parse(payload),
|
|
116
|
+
responseMode
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
kind: "parse_error",
|
|
121
|
+
message: error instanceof Error ? error.message : String(error),
|
|
122
|
+
responseMode
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function bufferFromChunk(chunk) {
|
|
127
|
+
if (Buffer.isBuffer(chunk)) return chunk;
|
|
128
|
+
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
129
|
+
throw new TypeError(`Unsupported stdio chunk type: ${typeof chunk}`);
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region packages/mcp-stdio-core/src/server.ts
|
|
133
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
134
|
+
var noopLog = () => {};
|
|
135
|
+
async function runJsonRpcStdioServer(config) {
|
|
136
|
+
const log = config.log ?? noopLog;
|
|
137
|
+
const idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
138
|
+
const idleTimer = createIdleTimer(idleTimeoutMs, log, config.onIdleTimeout);
|
|
139
|
+
log("stdio_started", {
|
|
140
|
+
cwd: process.cwd(),
|
|
141
|
+
idle_timeout_ms: idleTimeoutMs
|
|
142
|
+
});
|
|
143
|
+
idleTimer.arm();
|
|
144
|
+
try {
|
|
145
|
+
for await (const message of readStdioJsonRpcMessages(config.input)) {
|
|
146
|
+
if (idleTimer.closed()) break;
|
|
147
|
+
idleTimer.arm();
|
|
148
|
+
if (message.kind === "parse_error") {
|
|
149
|
+
handleParseError(message, config, log);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
await handleRequest(message, config, log);
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
idleTimer.clear();
|
|
156
|
+
log("stdio_stopped");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function handleParseError(message, config, log) {
|
|
160
|
+
log("parse_error", { message: message.message });
|
|
161
|
+
const response = config.parseErrorResponse?.(message.message) ?? errorResponse(null, -32700, "Parse error", message.message);
|
|
162
|
+
if (response !== void 0) writeStdioJsonRpcResponse(config.output, response, message.responseMode);
|
|
163
|
+
}
|
|
164
|
+
async function handleRequest(message, config, log) {
|
|
165
|
+
const parsed = message.payload;
|
|
166
|
+
const id = isPlainRecord(parsed) ? jsonRpcId(parsed["id"]) : null;
|
|
167
|
+
const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
|
|
168
|
+
log("request", {
|
|
169
|
+
id: id === null ? null : String(id),
|
|
170
|
+
method
|
|
171
|
+
});
|
|
172
|
+
try {
|
|
173
|
+
const response = await config.handler(parsed, config.handlerOptions);
|
|
174
|
+
if (response === void 0) return;
|
|
175
|
+
writeStdioJsonRpcResponse(config.output, response, message.responseMode);
|
|
176
|
+
log("response", {
|
|
177
|
+
id: String(response.id),
|
|
178
|
+
method,
|
|
179
|
+
is_error: response.error !== void 0
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (config.onHandlerError === void 0) throw error;
|
|
183
|
+
config.onHandlerError(error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
|
|
187
|
+
let timer = null;
|
|
188
|
+
let isClosed = false;
|
|
189
|
+
return {
|
|
190
|
+
arm: () => {
|
|
191
|
+
if (timer !== null) clearTimeout(timer);
|
|
192
|
+
if (idleTimeoutMs <= 0) return;
|
|
193
|
+
timer = setTimeout(() => {
|
|
194
|
+
isClosed = true;
|
|
195
|
+
log("idle_timeout", { idle_timeout_ms: idleTimeoutMs });
|
|
196
|
+
onIdleTimeout?.();
|
|
197
|
+
}, idleTimeoutMs);
|
|
198
|
+
timer.unref();
|
|
199
|
+
},
|
|
200
|
+
clear: () => {
|
|
201
|
+
if (timer === null) return;
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
timer = null;
|
|
204
|
+
},
|
|
205
|
+
closed: () => isClosed
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { successResponse as a, messageFromError as i, errorResponse as n, isPlainRecord as o, jsonRpcId as r, runJsonRpcStdioServer as t };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yeongyu Kim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ast-grep
|
|
3
|
+
description: AST-aware search and deterministic rewrite. Use when a code pattern depends on syntax shape or needs a safe repeatable codemod.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ast-grep
|
|
7
|
+
|
|
8
|
+
Use `sg` when target is syntax shape: function, call, class, import, missing `await`, empty catch, unsafe assertion, or codemod. Use `rg` for plain text.
|
|
9
|
+
|
|
10
|
+
## Flow
|
|
11
|
+
|
|
12
|
+
1. Name language and exact syntax shape.
|
|
13
|
+
2. Start search-only. Use smallest pattern with metavariables.
|
|
14
|
+
3. Inspect every match class. Add `inside`, `has`, `not`, or relational rule only when needed.
|
|
15
|
+
4. Test rewrite on narrow path. Review diff.
|
|
16
|
+
5. Apply deterministic rewrite. Run formatter, diagnostics, targeted tests.
|
|
17
|
+
|
|
18
|
+
Use `sg run -p '<pattern>' -l <language> <path>` for simple search. Use YAML rule for constraints, relational matching, or reusable codemod. Never run write mode before match review. Never use regex replacement for syntax-bearing code.
|
|
19
|
+
|
|
20
|
+
Load only needed reference: `patterns.md`, `yaml-rules.md`, `recipes.md`, `pitfalls.md`, `sgconfig.md`, `cli.md`, or `install.md`.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Patterns
|
|
2
|
+
|
|
3
|
+
Use code-shaped pattern in target language. `$A` captures one node; `$$$ARGS` captures many; `$_` ignores one. Start concrete, then replace only variable parts. Quote pattern in shell. If syntax fragment cannot parse alone, use `context` plus `selector` in YAML. Verify matches before rewrite.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Pitfalls
|
|
2
|
+
|
|
3
|
+
Text that looks alike may have different AST. Pattern may overmatch nested nodes, comments may not be nodes, and shell may expand `$`. Quote patterns. Specify language. Avoid `$$$` unless arity truly irrelevant. Never write on first query. After rewrite inspect diff and rerun search for leftovers.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
- Call: `pattern: logger.debug($A)`.
|
|
4
|
+
- Any arguments: `pattern: console.log($$$ARGS)`.
|
|
5
|
+
- Empty catch: match `catch ($E) {}` with language-correct syntax.
|
|
6
|
+
- Missing await: match call plus `not`/`inside` constraint excluding await expression.
|
|
7
|
+
- Import migration: match full import/require form; preserve captured module/name in fix.
|
|
8
|
+
|
|
9
|
+
For each recipe: search narrow path, inspect variants, add constraint, dry review, write, format, test.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# YAML rules
|
|
2
|
+
|
|
3
|
+
Minimum rule: `id`, `language`, `rule`. Compose with `all`, `any`, `not`; relate with `inside`, `has`, `precedes`, `follows`; constrain captures with `constraints`. Add `fix` only after search result is exact. Keep one semantic purpose per rule. Test positive and negative fixtures with `sg test`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julius Brussee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: caveman
|
|
3
|
+
description: >
|
|
4
|
+
Ultra-compressed communication mode. Cuts output tokens 65% (measured) by speaking like caveman
|
|
5
|
+
while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra,
|
|
6
|
+
wenyan-lite, wenyan-full, wenyan-ultra.
|
|
7
|
+
Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens",
|
|
8
|
+
"be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Respond terse like smart caveman. All technical substance stay. Only fluff die.
|
|
12
|
+
|
|
13
|
+
## Persistence
|
|
14
|
+
|
|
15
|
+
ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift. Still active if unsure. Off only: "stop caveman" / "normal mode".
|
|
16
|
+
|
|
17
|
+
Default: **full**. Switch: `/caveman lite|full|ultra`.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji, no dumping long raw error logs unless asked — quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) — tokenizer split them same as full word: zero token saved, reader still decode. Full word cheaper AND clearer. No causal arrows (→) either — own token, save nothing. Technical terms exact. Code blocks unchanged. Errors quoted exact.
|
|
22
|
+
|
|
23
|
+
Preserve user's dominant language. User write Portuguese → reply Portuguese caveman. User write Spanish → reply Spanish caveman. Compress the style, not the language. No forced English openings or status phrases. ALWAYS keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim — unless user explicitly ask for translation.
|
|
24
|
+
|
|
25
|
+
No self-reference. Never name or announce the style. No "caveman mode on", "me caveman think", no third-person caveman tags. Output caveman-only — never normal answer plus "Caveman:" recap. Exception: user explicitly ask what the mode is.
|
|
26
|
+
|
|
27
|
+
Pattern: `[thing] [action] [reason]. [next step].`
|
|
28
|
+
|
|
29
|
+
Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
|
|
30
|
+
Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
|
|
31
|
+
|
|
32
|
+
## Intensity
|
|
33
|
+
|
|
34
|
+
| Level | What change |
|
|
35
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
|
|
37
|
+
| **full** | Drop articles, fragments OK, short synonyms. Classic caveman. No tool-call narration, no decorative tables/emoji, no long raw error-log dumps unless asked. Standard acronyms OK; no invented abbreviations |
|
|
38
|
+
| **ultra** | Strip conjunctions when cause-then-effect stay unambiguous. One word when one word enough. State each fact once. NO prose abbreviations (cfg/impl/req/res/fn/auth), NO arrows (X → Y) — measured zero token saving under tokenizer, cost decode clarity. Code symbols, function names, API names, error strings: never touch |
|
|
39
|
+
| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register |
|
|
40
|
+
| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) |
|
|
41
|
+
| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse |
|
|
42
|
+
|
|
43
|
+
Example — "Why React component re-render?"
|
|
44
|
+
|
|
45
|
+
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
|
|
46
|
+
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
|
|
47
|
+
- ultra: "Inline obj prop, new ref, re-render. `useMemo`."
|
|
48
|
+
- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"
|
|
49
|
+
- wenyan-full: "每繪新生對象參照,故重繪;以 useMemo 包之則免。"
|
|
50
|
+
- wenyan-ultra: "新參照則重繪。useMemo 包之。"
|
|
51
|
+
|
|
52
|
+
Example — "Explain database connection pooling."
|
|
53
|
+
|
|
54
|
+
- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
|
|
55
|
+
- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
|
|
56
|
+
- ultra: "Pool reuse open DB connections. No per-request handshake."
|
|
57
|
+
- wenyan-full: "池蓄已開之連,不逐請而新開,省握手之費。"
|
|
58
|
+
- wenyan-ultra: "池蓄連,免逐請新開,省握手。"
|
|
59
|
+
|
|
60
|
+
## Auto-Clarity
|
|
61
|
+
|
|
62
|
+
Drop caveman when:
|
|
63
|
+
|
|
64
|
+
- Security warnings
|
|
65
|
+
- Irreversible action confirmations
|
|
66
|
+
- Multi-step sequences where fragment order or omitted conjunctions risk misread
|
|
67
|
+
- Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` — order unclear without articles/conjunctions)
|
|
68
|
+
- User asks to clarify or repeats question
|
|
69
|
+
|
|
70
|
+
Resume caveman after clear part done.
|
|
71
|
+
|
|
72
|
+
Example — destructive op:
|
|
73
|
+
|
|
74
|
+
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
|
|
75
|
+
>
|
|
76
|
+
> ```sql
|
|
77
|
+
> DROP TABLE users;
|
|
78
|
+
> ```
|
|
79
|
+
>
|
|
80
|
+
> Caveman resume. Verify backup exist first.
|
|
81
|
+
|
|
82
|
+
## Boundaries
|
|
83
|
+
|
|
84
|
+
Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: comment-checker
|
|
3
|
+
description: Use when Codex needs to understand or respond to automatic comment-checker feedback emitted after an edit-like PostToolUse hook.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Codex Comment Checker
|
|
7
|
+
|
|
8
|
+
The plugin registers a `PostToolUse` hook for successful `apply_patch`, `write`, `edit`, `multi_edit`, and `multiedit` calls.
|
|
9
|
+
|
|
10
|
+
When comment-checker reports a warning after a patch, Codex receives blocking feedback and should fix or explain the flagged comment before moving on.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
- No MCP tool is exposed.
|
|
15
|
+
- Non-edit tools are ignored by this plugin.
|
|
16
|
+
- Missing checker binaries emit no hook output so normal Codex work can continue.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compress
|
|
3
|
+
description: Rewrite any text, skill, prompt, documentation, instructions, prose, notes, or structured content to use fewer words and tokens while preserving meaning, facts, constraints, safety, tone, and required structure. Use when asked to compress, condense, tighten, shorten, de-duplicate, optimize token usage, or rewrite using caveman principles.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compress
|
|
7
|
+
|
|
8
|
+
Rewrite with caveman principles: all substance stays; filler dies. Do not automatically adopt caveman voice unless user requests it. Default output remains clear, grammatical, and natural.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
1. Identify purpose, audience, format, required facts, exact strings, constraints, decision gates, safety warnings, examples, links, and tone.
|
|
13
|
+
2. Remove repetition, throat-clearing, filler, redundant headings, obvious explanation, decorative adjectives, and verbose transitions.
|
|
14
|
+
3. Merge related rules. Prefer direct verbs, short familiar words, compact lists, and one statement per fact.
|
|
15
|
+
4. Preserve technical names, code, commands, paths, APIs, error strings, numbers, citations, legal terms, and user-defined terminology exactly unless correction is requested.
|
|
16
|
+
5. Preserve ordering when sequence matters. Keep full grammar where compression could create ambiguity, especially safety, irreversible actions, legal/medical guidance, or multi-step operations.
|
|
17
|
+
6. Check compressed output against source: no lost requirement, changed meaning, weakened prohibition, invented claim, broken reference, or altered scope.
|
|
18
|
+
|
|
19
|
+
## Content Rules
|
|
20
|
+
|
|
21
|
+
- **Skills/prompts:** preserve YAML frontmatter, trigger coverage, imperative instructions, tool/resource references, permissions, stop conditions, and validation. Remove duplicated “when to use” prose from body when frontmatter already carries it.
|
|
22
|
+
- **Code/config:** never compress syntax or identifiers unless explicitly asked to refactor. Compress surrounding explanation only.
|
|
23
|
+
- **Prose/docs:** preserve thesis, evidence, nuance, attribution, and intended tone. Remove repeated framing and examples that add no distinct value.
|
|
24
|
+
- **Lists/tables:** merge duplicates; keep mappings and comparisons when format improves scan speed.
|
|
25
|
+
|
|
26
|
+
If user sets length/ratio/style, follow it. Otherwise aim for largest safe reduction, not shortest possible output. Report before/after size only when useful or requested.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugging
|
|
3
|
+
description: Reproduce, isolate, prove, and minimally fix runtime bugs. Use for crashes, wrong behavior, hangs, races, leaks, or unexplained performance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debugging
|
|
7
|
+
|
|
8
|
+
No guess-fix loop.
|
|
9
|
+
|
|
10
|
+
1. Reproduce exact symptom with smallest command.
|
|
11
|
+
2. Minimize input, environment, and path.
|
|
12
|
+
3. Write at least three plausible causes from distinct layers.
|
|
13
|
+
4. Rank by evidence and cheap falsifier.
|
|
14
|
+
5. Instrument narrow boundary. Capture values, ordering, ownership, timing, process state.
|
|
15
|
+
6. Disprove causes. After two failed rounds, change angle; do not add reviewer or oracle agent.
|
|
16
|
+
7. Confirm root cause with evidence that predicts symptom.
|
|
17
|
+
8. Add failing public-behavior regression test.
|
|
18
|
+
9. Make minimum root fix.
|
|
19
|
+
10. Run targeted test, then proportional suite. Remove temporary instrumentation.
|
|
20
|
+
|
|
21
|
+
No sleeps for async proof. Use event, signal, fake clock, trace, debugger, profiler, sanitizer, or deterministic fixture. No implementation before root cause unless user explicitly asks for mitigation.
|
|
22
|
+
|
|
23
|
+
Load only relevant runtime/tool reference under `references/`. Report reproduction, hypotheses rejected, root cause, proof, fix, checks, residual uncertainty.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Runtime probes
|
|
2
|
+
|
|
3
|
+
- Node/Bun: reproduce with exact runtime/version; enable source maps and warnings; inspect event ordering, handles, promises, memory snapshots, CPU profile. Distinguish module resolution, transpilation, runtime, and external process.
|
|
4
|
+
- Python: use traceback with causes, `faulthandler`, debugger, allocation/profile tools. Check interpreter/env, import path, sync blocking in async loop, resource ownership.
|
|
5
|
+
- Rust/native: debug symbols; backtrace, debugger, sanitizer, Miri, profiler as symptom requires. Check UB, ownership, ABI, signal/thread ordering.
|
|
6
|
+
- Go: race detector, pprof, trace, goroutine dump. Check cancellation, channel ownership, leaked goroutines, copied synchronization.
|
|
7
|
+
- Bundled JS binary: reproduce source and bundle separately; verify target/runtime/externalization and source map. Inspect generated boundary, not minified noise first.
|
|
8
|
+
|
|
9
|
+
Capture smallest evidence that separates hypotheses. Remove probe after proof.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Tool choice
|
|
2
|
+
|
|
3
|
+
- Playwright: deterministic browser flow, console/network/DOM/screenshot evidence.
|
|
4
|
+
- GDB/pwndbg: native crash, registers, stack, memory, heap; local authorized target only.
|
|
5
|
+
- Ghidra: static binary control/data flow when source absent; pair claims with runtime proof when possible.
|
|
6
|
+
- pwntools: local protocol/process PoC with bounded input and timeout; never destructive or third-party.
|
|
7
|
+
|
|
8
|
+
Use least invasive tool that falsifies a hypothesis. Record exact command/version and preserve a safe reproducer.
|