repowisestage 0.0.59 → 0.0.61
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 +81 -6
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -3046,7 +3046,7 @@ Treat RepoWise as your default starting point for every task that touches this c
|
|
|
3046
3046
|
2. **Use the three-tier hierarchy in this order:**
|
|
3047
3047
|
- **Tier 1 \u2014 RepoWise MCP** for surgical structural queries: symbol resolution, callers, references, call graphs, refactor impact, dependency graphs, type info (\`find_symbol\`, \`get_impact\`, \`lsp_definition\`, etc. \u2014 the server is named "RepoWise MCP for <repo>", so hosts that prefix tool names expose them accordingly). Each tool's description tells you when to use it.
|
|
3048
3048
|
- **Tier 2 \u2014 Context files in \`repowise-context/\`** for narrative context: how a domain is designed, what patterns exist, what conventions to follow. Use the *Context File Routing Map* below to pick which files to read.
|
|
3049
|
-
- **Tier 3 \u2014 Native code search** (\`read_file\` / \`grep\` / \`glob\` on raw source) ONLY when tiers 1-2 don't cover the question, or for ephemeral state (recent commits, in-flight branches).
|
|
3049
|
+
- **Tier 3 \u2014 Native code search** (\`read_file\` / \`grep\` / \`glob\` on raw source) ONLY when tiers 1-2 don't cover the question, or for ephemeral state (recent commits, in-flight branches). An enforcement hook may DENY a Tier-3 search until you have used Tier 1 or Tier 2 this turn \u2014 that is expected, not an error: consult RepoWise first, then retry the same search and it will proceed.
|
|
3050
3050
|
|
|
3051
3051
|
3. **Batch tool calls in parallel.** For maximum efficiency, whenever you need multiple independent operations to gather context, invoke all relevant tools SIMULTANEOUSLY rather than sequentially. Examples:
|
|
3052
3052
|
- Reading 3 context files \u2192 3 \`read_file\` calls in parallel.
|
|
@@ -3072,7 +3072,7 @@ Treat RepoWise as your default starting point for every task that touches this c
|
|
|
3072
3072
|
|
|
3073
3073
|
That's it \u2014 one short line, no longer preamble. The host's tool-call UI already shows RepoWise MCP calls visibly; this tag adds the same surface for context-file reads. If you didn't use RepoWise on this turn, omit the tag \u2014 never fake it.
|
|
3074
3074
|
|
|
3075
|
-
8. **
|
|
3075
|
+
8. **MCP unavailable vs. a RepoWise miss \u2014 handle them differently.** If RepoWise MCP is entirely unavailable (no \`.mcp.json\` configured, listener not running), tell the user. But if RepoWise IS available and simply does not cover a specific question, just continue with Tier 3 \u2014 do NOT announce that "RepoWise doesn't have it." That fallback is normal and expected; the system records it on its own to improve coverage.
|
|
3076
3076
|
${END_MARKER}`;
|
|
3077
3077
|
function injectAgentInstructions(content) {
|
|
3078
3078
|
const lineEnding = content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -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
|
|