repowisestage 0.0.59 → 0.0.60
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/dist/bin/repowise.js +79 -4
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -3130,6 +3130,58 @@ function buildSubagentDirective(contextFolder) {
|
|
|
3130
3130
|
const folder = sanitizeForShellText(contextFolder);
|
|
3131
3131
|
return `IMPORTANT: Read ${folder}/project-overview.md before performing any work. This file maps every context file to its domain.`;
|
|
3132
3132
|
}
|
|
3133
|
+
var GATE_SCRIPT_REL = ".claude/repowise-gate.cjs";
|
|
3134
|
+
var GATE_PRETOOLUSE_MATCHER = "Read|Grep|Glob|mcp__RepoWise.*";
|
|
3135
|
+
var GATE_MAX_DENIES = 2;
|
|
3136
|
+
function buildGateHookCommand() {
|
|
3137
|
+
return `node "$CLAUDE_PROJECT_DIR/${GATE_SCRIPT_REL}" ${REPOWISE_MANAGED_COMMENT}`;
|
|
3138
|
+
}
|
|
3139
|
+
function buildGateScript(contextFolder) {
|
|
3140
|
+
const folder = contextFolder.replace(/[^A-Za-z0-9_./-]/g, "");
|
|
3141
|
+
return GATE_SCRIPT_TEMPLATE.replace(/__CONTEXT_FOLDER__/g, folder).replace(/__MAX_DENIES__/g, String(GATE_MAX_DENIES));
|
|
3142
|
+
}
|
|
3143
|
+
var GATE_SCRIPT_TEMPLATE = [
|
|
3144
|
+
"#!/usr/bin/env node",
|
|
3145
|
+
"// repowise-managed \u2014 RepoWise-first enforcement gate. Regenerated on sync; do not edit.",
|
|
3146
|
+
"var fs = require('fs'); var os = require('os'); var path = require('path');",
|
|
3147
|
+
"var CONTEXT_FOLDER = '__CONTEXT_FOLDER__'; var MAX_DENIES = __MAX_DENIES__;",
|
|
3148
|
+
'function readStdin(){ try { return fs.readFileSync(0, "utf8"); } catch (e) { return ""; } }',
|
|
3149
|
+
'var payload = {}; try { payload = JSON.parse(readStdin() || "{}"); } catch (e) {}',
|
|
3150
|
+
'var event = String(payload.hook_event_name || "");',
|
|
3151
|
+
'var sessionId = String(payload.session_id || "nosession").replace(/[^A-Za-z0-9_.-]/g, "_");',
|
|
3152
|
+
'var stateFile = path.join(os.tmpdir(), "repowise-gate", sessionId + ".json");',
|
|
3153
|
+
'function readState(){ try { return JSON.parse(fs.readFileSync(stateFile, "utf8")); } catch (e) { return { consulted:false, denies:0 }; } }',
|
|
3154
|
+
"function writeState(s){ try { fs.mkdirSync(path.dirname(stateFile), {recursive:true}); fs.writeFileSync(stateFile, JSON.stringify(s)); } catch (e) {} }",
|
|
3155
|
+
"// UserPromptSubmit: re-arm RepoWise-first for the new question (the echo hook carries the reminder).",
|
|
3156
|
+
'if (event === "UserPromptSubmit") { try { fs.rmSync(stateFile, {force:true}); } catch (e) {} process.exit(0); }',
|
|
3157
|
+
'if (event !== "PreToolUse") { process.exit(0); }',
|
|
3158
|
+
'var tool = String(payload.tool_name || "");',
|
|
3159
|
+
"var input = payload.tool_input || {};",
|
|
3160
|
+
'var filePath = String(input.file_path || input.path || input.notebook_path || "");',
|
|
3161
|
+
'var isContext = filePath.indexOf(CONTEXT_FOLDER + "/") !== -1;',
|
|
3162
|
+
'var tier = "other";',
|
|
3163
|
+
'if (tool.indexOf("mcp__RepoWise") === 0) { tier = "tier1-mcp"; }',
|
|
3164
|
+
'else if (tool === "Read" && isContext) { tier = "tier2-context"; }',
|
|
3165
|
+
'else if (tool === "Grep" || tool === "Glob" || (tool === "Read" && !isContext)) { tier = "tier3-native"; }',
|
|
3166
|
+
"var state = readState();",
|
|
3167
|
+
'if (tier === "tier1-mcp" || tier === "tier2-context") { state.consulted = true; writeState(state); process.exit(0); }',
|
|
3168
|
+
'if (tier !== "tier3-native") { process.exit(0); }',
|
|
3169
|
+
"function logMiss(){ try {",
|
|
3170
|
+
" var root = process.env.CLAUDE_PROJECT_DIR || payload.cwd || process.cwd();",
|
|
3171
|
+
" var blob = JSON.stringify(input);",
|
|
3172
|
+
" var ephemeral = /\\.git|commit|\\blog\\b|branch|HEAD|stash|reflog/i.test(blob);",
|
|
3173
|
+
' var rec = { ts: new Date().toISOString(), event: "repowise-miss", sessionId: sessionId, tool: tool, filePath: filePath, query: String(input.pattern || input.query || ""), ephemeral: ephemeral };',
|
|
3174
|
+
' var dir = path.join(root, ".repowise"); fs.mkdirSync(dir, {recursive:true});',
|
|
3175
|
+
' fs.appendFileSync(path.join(dir, "repowise-misses.jsonl"), JSON.stringify(rec) + "\\n");',
|
|
3176
|
+
"} catch (e) {} }",
|
|
3177
|
+
"if (state.consulted) { logMiss(); process.exit(0); }",
|
|
3178
|
+
"if ((state.denies || 0) >= MAX_DENIES) { process.exit(0); }",
|
|
3179
|
+
"state.denies = (state.denies || 0) + 1; writeState(state);",
|
|
3180
|
+
'var reason = "RepoWise-first: this codebase has RepoWise. Use Tier 1 (RepoWise MCP tools find_symbol/get_impact/lsp_*) or Tier 2 (" + CONTEXT_FOLDER + "/ docs) to answer this before native search. Retry this search and it will be allowed.";',
|
|
3181
|
+
'process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }));',
|
|
3182
|
+
"process.exit(0);",
|
|
3183
|
+
""
|
|
3184
|
+
].join("\n");
|
|
3133
3185
|
function assertShellSafe(payload) {
|
|
3134
3186
|
if (payload.includes("'")) {
|
|
3135
3187
|
throw new Error("hook payload must not contain single quotes");
|
|
@@ -3243,13 +3295,20 @@ function mergeClaudeHookSettings(existingContent, params) {
|
|
|
3243
3295
|
{
|
|
3244
3296
|
type: "command",
|
|
3245
3297
|
command: buildHookEchoCommand("UserPromptSubmit", buildRepoWiseFirstDirective(params.repoName, params.contextFolder, "claude"))
|
|
3246
|
-
}
|
|
3298
|
+
},
|
|
3299
|
+
// Re-arm the per-question gate (silent; the echo above carries the reminder).
|
|
3300
|
+
{ type: "command", command: buildGateHookCommand() }
|
|
3247
3301
|
]
|
|
3302
|
+
},
|
|
3303
|
+
// The enforcement gate: steer native search → RepoWise first, log misses.
|
|
3304
|
+
PreToolUse: {
|
|
3305
|
+
matcher: GATE_PRETOOLUSE_MATCHER,
|
|
3306
|
+
hooks: [{ type: "command", command: buildGateHookCommand() }]
|
|
3248
3307
|
}
|
|
3249
3308
|
});
|
|
3250
3309
|
}
|
|
3251
3310
|
function removeClaudeHookSettings(existingContent) {
|
|
3252
|
-
return removeHookEvents(existingContent, ["SubagentStart", "UserPromptSubmit"]);
|
|
3311
|
+
return removeHookEvents(existingContent, ["SubagentStart", "UserPromptSubmit", "PreToolUse"]);
|
|
3253
3312
|
}
|
|
3254
3313
|
function mergeGeminiHookSettings(existingContent, params) {
|
|
3255
3314
|
return mergeHookEvents(existingContent, {
|
|
@@ -3415,9 +3474,21 @@ async function writeClaudeHooksToRepo(repoRoot, contextFolder) {
|
|
|
3415
3474
|
await writeFile(targetPath, merged, "utf-8");
|
|
3416
3475
|
status2 = "written";
|
|
3417
3476
|
}
|
|
3477
|
+
const gateScript = buildGateScript(contextFolder);
|
|
3478
|
+
const gatePath = join2(repoRoot, GATE_SCRIPT_REL);
|
|
3479
|
+
let gateExisting = null;
|
|
3480
|
+
try {
|
|
3481
|
+
gateExisting = await readFile(gatePath, "utf-8");
|
|
3482
|
+
} catch {
|
|
3483
|
+
}
|
|
3484
|
+
if (gateExisting !== gateScript) {
|
|
3485
|
+
await mkdir(join2(repoRoot, ".claude"), { recursive: true });
|
|
3486
|
+
await writeFile(gatePath, gateScript, "utf-8");
|
|
3487
|
+
status2 = "written";
|
|
3488
|
+
}
|
|
3418
3489
|
if (status2 === "written") {
|
|
3419
3490
|
await applyGitignoreChanges(repoRoot, {
|
|
3420
|
-
add: [targetRel],
|
|
3491
|
+
add: [targetRel, GATE_SCRIPT_REL],
|
|
3421
3492
|
remove: [projectTracked ? CLAUDE_PROJECT_SETTINGS : CLAUDE_LOCAL_SETTINGS]
|
|
3422
3493
|
});
|
|
3423
3494
|
}
|
|
@@ -3439,8 +3510,12 @@ async function removeClaudeHooksFromRepo(repoRoot) {
|
|
|
3439
3510
|
await writeFile(path, next, "utf-8");
|
|
3440
3511
|
}
|
|
3441
3512
|
}
|
|
3513
|
+
try {
|
|
3514
|
+
await unlink(join2(repoRoot, GATE_SCRIPT_REL));
|
|
3515
|
+
} catch {
|
|
3516
|
+
}
|
|
3442
3517
|
await applyGitignoreChanges(repoRoot, {
|
|
3443
|
-
remove: [CLAUDE_PROJECT_SETTINGS, CLAUDE_LOCAL_SETTINGS]
|
|
3518
|
+
remove: [CLAUDE_PROJECT_SETTINGS, CLAUDE_LOCAL_SETTINGS, GATE_SCRIPT_REL]
|
|
3444
3519
|
});
|
|
3445
3520
|
}
|
|
3446
3521
|
|