@wrongstack/tools 0.305.1 → 0.306.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/dist/_shell-pick.d.ts +4 -5
- package/dist/_util.d.ts +22 -5
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +135 -46
- package/dist/bash.js +61 -37
- package/dist/browser/index.js +29 -9
- package/dist/browser/types.d.ts +7 -1
- package/dist/builtin.js +1279 -726
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.js +223 -152
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +78 -12
- package/dist/document.js +18 -6
- package/dist/edit.js +69 -16
- package/dist/exec.js +44 -22
- package/dist/fetch.js +13 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.js +81 -31
- package/dist/glob.js +12 -4
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +15 -4
- package/dist/index.js +1342 -761
- package/dist/install.js +96 -37
- package/dist/kanban-tool-types.d.ts +6 -1
- package/dist/kanban.js +60 -0
- package/dist/languages/index.js +28 -13
- package/dist/lint.js +28 -13
- package/dist/logs.d.ts +0 -1
- package/dist/logs.js +44 -13
- package/dist/memory.d.ts +8 -0
- package/dist/memory.js +23 -3
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +3 -0
- package/dist/next-steps.d.ts +2 -3
- package/dist/next-steps.js +3 -3
- package/dist/outdated.d.ts +0 -3
- package/dist/outdated.js +89 -48
- package/dist/pack.js +1279 -726
- package/dist/plan.js +76 -3
- package/dist/process-registry.d.ts +8 -2
- package/dist/process-registry.js +28 -13
- package/dist/ps-slash.js +22 -12
- package/dist/read.js +10 -3
- package/dist/replace.d.ts +4 -0
- package/dist/replace.js +104 -7
- package/dist/search.d.ts +6 -0
- package/dist/search.js +47 -26
- package/dist/skill.d.ts +6 -0
- package/dist/skill.js +9 -10
- package/dist/task.js +66 -2
- package/dist/test.js +28 -13
- package/dist/todo.js +64 -2
- package/dist/tool-icons.js +4 -2
- package/dist/tool-summary.d.ts +1 -1
- package/dist/tool-summary.js +76 -1
- package/dist/tool-tier.js +1279 -726
- package/dist/tree.js +9 -10
- package/dist/typecheck.d.ts +0 -2
- package/dist/typecheck.js +98 -31
- package/dist/write.js +58 -10
- package/package.json +4 -4
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Tool } from '@wrongstack/core/types';
|
|
19
19
|
import type { SearchResult } from './schema.js';
|
|
20
|
+
/**
|
|
21
|
+
* Language ids the index understands. Single source for the `lang` enum here
|
|
22
|
+
* and the `langs` validation in codebase-index-tool.ts.
|
|
23
|
+
*/
|
|
24
|
+
export declare const INDEXABLE_LANG_IDS: readonly ['ts', 'tsx', 'js', 'jsx', 'go', 'py', 'rs', 'json', 'yaml'];
|
|
20
25
|
export declare const codebaseSearchTool: Tool<CodebaseSearchInput, CodebaseSearchOutput>;
|
|
21
26
|
interface CodebaseSearchInput {
|
|
22
27
|
query: string;
|
|
@@ -8491,6 +8491,160 @@ function ensureCodebaseIndexServer(options) {
|
|
|
8491
8491
|
}
|
|
8492
8492
|
|
|
8493
8493
|
// src/codebase-index/codebase-index-tool.ts
|
|
8494
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
8495
|
+
|
|
8496
|
+
// src/codebase-index/codebase-search-tool.ts
|
|
8497
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
8498
|
+
var INDEXABLE_LANG_IDS = [
|
|
8499
|
+
"ts",
|
|
8500
|
+
"tsx",
|
|
8501
|
+
"js",
|
|
8502
|
+
"jsx",
|
|
8503
|
+
"go",
|
|
8504
|
+
"py",
|
|
8505
|
+
"rs",
|
|
8506
|
+
"json",
|
|
8507
|
+
"yaml"
|
|
8508
|
+
];
|
|
8509
|
+
var codebaseSearchTool = {
|
|
8510
|
+
name: "codebase-search",
|
|
8511
|
+
category: "Project",
|
|
8512
|
+
icon: "index",
|
|
8513
|
+
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Prefer this before broad `tree`, `glob`, or `grep` exploration when finding code by name or concept. Use `grep` instead for exact text, regexes, unsupported content, or concrete usage sites. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
8514
|
+
usageHint: "FIRST CHOICE FOR INDEXABLE CODE UNDERSTANDING:\n\n- Call before broad `tree`, `glob`, or `grep` exploration when locating symbols, concepts, definitions, or candidate modules.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\n- If `indexStatus` reports no persisted data, run `codebase-index` and retry.",
|
|
8515
|
+
permission: "auto",
|
|
8516
|
+
mutating: false,
|
|
8517
|
+
capabilities: ["fs.read"],
|
|
8518
|
+
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
8519
|
+
// worker teardown and structured timeout reporting.
|
|
8520
|
+
timeoutMs: 35e3,
|
|
8521
|
+
inputSchema: {
|
|
8522
|
+
type: "object",
|
|
8523
|
+
properties: {
|
|
8524
|
+
query: {
|
|
8525
|
+
type: "string",
|
|
8526
|
+
description: "Search query \u2014 searches symbol names, signatures, and doc comments"
|
|
8527
|
+
},
|
|
8528
|
+
kind: {
|
|
8529
|
+
type: "string",
|
|
8530
|
+
enum: [
|
|
8531
|
+
"class",
|
|
8532
|
+
"interface",
|
|
8533
|
+
"enum",
|
|
8534
|
+
"type",
|
|
8535
|
+
"function",
|
|
8536
|
+
"method",
|
|
8537
|
+
"var",
|
|
8538
|
+
"const",
|
|
8539
|
+
"let",
|
|
8540
|
+
"property",
|
|
8541
|
+
"parameter",
|
|
8542
|
+
"namespace",
|
|
8543
|
+
"object",
|
|
8544
|
+
"literal",
|
|
8545
|
+
"schema",
|
|
8546
|
+
"struct",
|
|
8547
|
+
"trait",
|
|
8548
|
+
"impl",
|
|
8549
|
+
"static",
|
|
8550
|
+
"mod"
|
|
8551
|
+
],
|
|
8552
|
+
description: "Filter by indexed symbol kind"
|
|
8553
|
+
},
|
|
8554
|
+
lang: {
|
|
8555
|
+
type: "string",
|
|
8556
|
+
enum: [...INDEXABLE_LANG_IDS],
|
|
8557
|
+
description: "Filter by indexed language"
|
|
8558
|
+
},
|
|
8559
|
+
lspKind: {
|
|
8560
|
+
type: "integer",
|
|
8561
|
+
description: "Filter by LSP SymbolKind number (e.g. 5=Class, 12=Function, 11=Interface, 10=Enum)"
|
|
8562
|
+
},
|
|
8563
|
+
file: {
|
|
8564
|
+
type: "string",
|
|
8565
|
+
description: "Filter to files matching this path substring"
|
|
8566
|
+
},
|
|
8567
|
+
limit: {
|
|
8568
|
+
type: "integer",
|
|
8569
|
+
description: "Maximum results to return (default 20, max 100)",
|
|
8570
|
+
minimum: 1,
|
|
8571
|
+
maximum: 100
|
|
8572
|
+
},
|
|
8573
|
+
preferLsp: {
|
|
8574
|
+
type: "boolean",
|
|
8575
|
+
description: "Prefer live LSP results over the index. Ignored unless the LSP plugin is active; when it is active and this is true, results come from live workspaceSymbol queries."
|
|
8576
|
+
}
|
|
8577
|
+
},
|
|
8578
|
+
required: ["query"]
|
|
8579
|
+
},
|
|
8580
|
+
async execute(input, ctx, execOpts) {
|
|
8581
|
+
const state = getIndexState();
|
|
8582
|
+
if (state.indexing && !state.ready) {
|
|
8583
|
+
return {
|
|
8584
|
+
results: [],
|
|
8585
|
+
total: 0,
|
|
8586
|
+
query: input.query,
|
|
8587
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
8588
|
+
};
|
|
8589
|
+
}
|
|
8590
|
+
if (state.lastError) {
|
|
8591
|
+
const circuit = state.circuit;
|
|
8592
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s); the user can run /codebase-reindex to retry now.` : "Try /codebase-reindex.";
|
|
8593
|
+
return {
|
|
8594
|
+
results: [],
|
|
8595
|
+
total: 0,
|
|
8596
|
+
query: input.query,
|
|
8597
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
8598
|
+
};
|
|
8599
|
+
}
|
|
8600
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
8601
|
+
let searched;
|
|
8602
|
+
try {
|
|
8603
|
+
searched = await searchCodebaseIndex(
|
|
8604
|
+
{
|
|
8605
|
+
projectRoot: ctx.projectRoot,
|
|
8606
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
8607
|
+
query: input.query,
|
|
8608
|
+
kind: input.kind?.toLowerCase(),
|
|
8609
|
+
lang: input.lang?.toLowerCase(),
|
|
8610
|
+
file: input.file,
|
|
8611
|
+
lspKind: input.lspKind,
|
|
8612
|
+
limit
|
|
8613
|
+
},
|
|
8614
|
+
{ signal: execOpts?.signal }
|
|
8615
|
+
);
|
|
8616
|
+
} catch (err) {
|
|
8617
|
+
if (execOpts?.signal?.aborted) throw err;
|
|
8618
|
+
return {
|
|
8619
|
+
results: [],
|
|
8620
|
+
total: 0,
|
|
8621
|
+
query: input.query,
|
|
8622
|
+
indexStatus: `Index query failed: ${toErrorMessage2(err)}. Fall back to grep/glob for this lookup.`
|
|
8623
|
+
};
|
|
8624
|
+
}
|
|
8625
|
+
const { results, total } = searched;
|
|
8626
|
+
let hasPersistedIndex = state.ready || total > 0;
|
|
8627
|
+
if (!hasPersistedIndex) {
|
|
8628
|
+
try {
|
|
8629
|
+
const stats = await codebaseIndexStats(
|
|
8630
|
+
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
8631
|
+
{ signal: execOpts?.signal }
|
|
8632
|
+
);
|
|
8633
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
8634
|
+
} catch {
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
return {
|
|
8638
|
+
results,
|
|
8639
|
+
total,
|
|
8640
|
+
query: input.query,
|
|
8641
|
+
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
8642
|
+
};
|
|
8643
|
+
}
|
|
8644
|
+
};
|
|
8645
|
+
|
|
8646
|
+
// src/codebase-index/codebase-index-tool.ts
|
|
8647
|
+
var MAX_REPORTED_ERRORS = 20;
|
|
8494
8648
|
var codebaseIndexTool = {
|
|
8495
8649
|
name: "codebase-index",
|
|
8496
8650
|
category: "Project",
|
|
@@ -8516,12 +8670,23 @@ var codebaseIndexTool = {
|
|
|
8516
8670
|
},
|
|
8517
8671
|
langs: {
|
|
8518
8672
|
type: "array",
|
|
8519
|
-
items: { type: "string" },
|
|
8520
|
-
description:
|
|
8673
|
+
items: { type: "string", enum: [...INDEXABLE_LANG_IDS] },
|
|
8674
|
+
description: `Limit reindex to specific languages: ${INDEXABLE_LANG_IDS.join(", ")}`
|
|
8521
8675
|
}
|
|
8522
8676
|
}
|
|
8523
8677
|
},
|
|
8524
8678
|
async execute(input, ctx, execOpts) {
|
|
8679
|
+
if (input.langs) {
|
|
8680
|
+
const unknown = input.langs.filter(
|
|
8681
|
+
(lang) => !INDEXABLE_LANG_IDS.includes(lang)
|
|
8682
|
+
);
|
|
8683
|
+
if (unknown.length > 0) {
|
|
8684
|
+
throw new ToolValidationError({
|
|
8685
|
+
message: `codebase-index: unknown lang(s) ${unknown.map((l) => `"${l}"`).join(", ")}. Valid ids: ${INDEXABLE_LANG_IDS.join(", ")}.`,
|
|
8686
|
+
field: "langs"
|
|
8687
|
+
});
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8525
8690
|
if (isIndexing()) {
|
|
8526
8691
|
return {
|
|
8527
8692
|
filesIndexed: 0,
|
|
@@ -8543,23 +8708,32 @@ var codebaseIndexTool = {
|
|
|
8543
8708
|
note: `Codebase indexing is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}). Auto-retry possible in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s; the user can run /codebase-reindex to retry immediately.`
|
|
8544
8709
|
};
|
|
8545
8710
|
}
|
|
8546
|
-
|
|
8711
|
+
const result = await runStartupIndex({
|
|
8547
8712
|
projectRoot: ctx.projectRoot,
|
|
8548
8713
|
force: input.force ?? false,
|
|
8549
8714
|
langs: input.langs,
|
|
8550
8715
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
8551
8716
|
signal: execOpts?.signal
|
|
8552
8717
|
});
|
|
8718
|
+
if (result.errors.length > MAX_REPORTED_ERRORS) {
|
|
8719
|
+
const hidden = result.errors.length - MAX_REPORTED_ERRORS;
|
|
8720
|
+
return {
|
|
8721
|
+
...result,
|
|
8722
|
+
errors: [...result.errors.slice(0, MAX_REPORTED_ERRORS), `+${hidden} more`]
|
|
8723
|
+
};
|
|
8724
|
+
}
|
|
8725
|
+
return result;
|
|
8553
8726
|
}
|
|
8554
8727
|
};
|
|
8555
8728
|
|
|
8556
8729
|
// src/codebase-index/codebase-incoming-calls-tool.ts
|
|
8730
|
+
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
8557
8731
|
var codebaseIncomingCallsTool = {
|
|
8558
8732
|
name: "codebase-incoming-calls",
|
|
8559
8733
|
category: "Project",
|
|
8560
8734
|
icon: "index",
|
|
8561
|
-
description: "Find all callers of a function, method, or symbol \u2014 who invokes or references it. Uses the codebase index ref graph for instant, exact results.
|
|
8562
|
-
usageHint: 'CALL THIS BEFORE REFACTORING OR CHANGING ANY FUNCTION:\n\n-
|
|
8735
|
+
description: "Find all callers of a function, method, or symbol \u2014 who invokes or references it. Uses the codebase index ref graph for instant, exact results. Prefer this over grep for change-impact checks when the index is available.",
|
|
8736
|
+
usageHint: 'CALL THIS BEFORE REFACTORING OR CHANGING ANY FUNCTION:\n\n- Prefer this over grep when the index is available; fall back to grep when the index is cold/unavailable or for dynamic dispatch the ref graph cannot see.\n- Call codebase-incoming-calls({ symbol: "funcName" }) before editing the symbol.\n- Returns exact files, line numbers, caller signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Combine with codebase-outgoing-calls to see what the symbol itself calls.\nIf the index is not built, run codebase-index first.',
|
|
8563
8737
|
permission: "auto",
|
|
8564
8738
|
mutating: false,
|
|
8565
8739
|
capabilities: ["fs.read"],
|
|
@@ -8611,16 +8785,27 @@ var codebaseIncomingCallsTool = {
|
|
|
8611
8785
|
}
|
|
8612
8786
|
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
8613
8787
|
const transitive = input.transitive === true;
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8788
|
+
let serviced;
|
|
8789
|
+
try {
|
|
8790
|
+
serviced = await incomingCallsService2(
|
|
8791
|
+
{
|
|
8792
|
+
projectRoot: ctx.projectRoot,
|
|
8793
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
8794
|
+
symbol: input.symbol,
|
|
8795
|
+
file: input.file,
|
|
8796
|
+
limit,
|
|
8797
|
+
transitive
|
|
8798
|
+
}
|
|
8799
|
+
);
|
|
8800
|
+
} catch (err) {
|
|
8801
|
+
return {
|
|
8618
8802
|
symbol: input.symbol,
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
}
|
|
8623
|
-
|
|
8803
|
+
calls: [],
|
|
8804
|
+
total: 0,
|
|
8805
|
+
indexStatus: `Index query failed: ${toErrorMessage3(err)}. Fall back to grep for this lookup.`
|
|
8806
|
+
};
|
|
8807
|
+
}
|
|
8808
|
+
const { calls, symbolFound, ambiguous, totalMatches } = serviced;
|
|
8624
8809
|
if (!symbolFound) {
|
|
8625
8810
|
let hasPersistedIndex = state.ready;
|
|
8626
8811
|
if (!hasPersistedIndex) {
|
|
@@ -8665,12 +8850,13 @@ var codebaseIncomingCallsTool = {
|
|
|
8665
8850
|
};
|
|
8666
8851
|
|
|
8667
8852
|
// src/codebase-index/codebase-outgoing-calls-tool.ts
|
|
8853
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
8668
8854
|
var codebaseOutgoingCallsTool = {
|
|
8669
8855
|
name: "codebase-outgoing-calls",
|
|
8670
8856
|
category: "Project",
|
|
8671
8857
|
icon: "index",
|
|
8672
8858
|
description: "Find all functions/methods/symbols that a given symbol calls or depends on \u2014 its callees. Uses the codebase index ref graph for instant, exact results. Use this to understand a function's dependencies before modifying it.",
|
|
8673
|
-
usageHint: 'USE THIS TO UNDERSTAND A FUNCTION\'S DEPENDENCIES:\n\n- Call codebase-outgoing-calls({ symbol: "funcName" }) to see everything it calls.\n- Returns exact files, line numbers, callee signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Pair with codebase-incoming-calls for a complete impact picture: incoming = who calls you, outgoing = what you call.\nIf the index is not built, run codebase-index first.',
|
|
8859
|
+
usageHint: 'USE THIS TO UNDERSTAND A FUNCTION\'S DEPENDENCIES:\n\n- Prefer this over grep when the index is available; fall back to grep when the index is cold/unavailable or for dynamic dispatch the ref graph cannot see.\n- Call codebase-outgoing-calls({ symbol: "funcName" }) to see everything it calls.\n- Returns exact files, line numbers, callee signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Pair with codebase-incoming-calls for a complete impact picture: incoming = who calls you, outgoing = what you call.\nIf the index is not built, run codebase-index first.',
|
|
8674
8860
|
permission: "auto",
|
|
8675
8861
|
mutating: false,
|
|
8676
8862
|
capabilities: ["fs.read"],
|
|
@@ -8722,16 +8908,27 @@ var codebaseOutgoingCallsTool = {
|
|
|
8722
8908
|
}
|
|
8723
8909
|
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
8724
8910
|
const transitive = input.transitive === true;
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8911
|
+
let serviced;
|
|
8912
|
+
try {
|
|
8913
|
+
serviced = await outgoingCallsService2(
|
|
8914
|
+
{
|
|
8915
|
+
projectRoot: ctx.projectRoot,
|
|
8916
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
8917
|
+
symbol: input.symbol,
|
|
8918
|
+
file: input.file,
|
|
8919
|
+
limit,
|
|
8920
|
+
transitive
|
|
8921
|
+
}
|
|
8922
|
+
);
|
|
8923
|
+
} catch (err) {
|
|
8924
|
+
return {
|
|
8729
8925
|
symbol: input.symbol,
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
}
|
|
8734
|
-
|
|
8926
|
+
calls: [],
|
|
8927
|
+
total: 0,
|
|
8928
|
+
indexStatus: `Index query failed: ${toErrorMessage4(err)}. Fall back to grep for this lookup.`
|
|
8929
|
+
};
|
|
8930
|
+
}
|
|
8931
|
+
const { calls, symbolFound, unresolvedCount, totalMatches } = serviced;
|
|
8735
8932
|
if (!symbolFound) {
|
|
8736
8933
|
let hasPersistedIndex = state.ready;
|
|
8737
8934
|
if (!hasPersistedIndex) {
|
|
@@ -8775,132 +8972,6 @@ var codebaseOutgoingCallsTool = {
|
|
|
8775
8972
|
}
|
|
8776
8973
|
};
|
|
8777
8974
|
|
|
8778
|
-
// src/codebase-index/codebase-search-tool.ts
|
|
8779
|
-
var codebaseSearchTool = {
|
|
8780
|
-
name: "codebase-search",
|
|
8781
|
-
category: "Project",
|
|
8782
|
-
icon: "index",
|
|
8783
|
-
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Prefer this before broad `tree`, `glob`, or `grep` exploration when finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
8784
|
-
usageHint: "FIRST CHOICE FOR INDEXABLE CODE UNDERSTANDING:\n\n- Call before broad `tree`, `glob`, or `grep` exploration when locating symbols, concepts, definitions, or candidate modules.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\n- If `indexStatus` reports no persisted data, run `codebase-index` and retry.\nUse `grep` afterwards for exact text, regexes, unsupported content, or concrete usage sites.",
|
|
8785
|
-
permission: "auto",
|
|
8786
|
-
mutating: false,
|
|
8787
|
-
capabilities: ["fs.read"],
|
|
8788
|
-
// The index host has its own 30s read watchdog. Leave enough headroom for
|
|
8789
|
-
// worker teardown and structured timeout reporting.
|
|
8790
|
-
timeoutMs: 35e3,
|
|
8791
|
-
inputSchema: {
|
|
8792
|
-
type: "object",
|
|
8793
|
-
properties: {
|
|
8794
|
-
query: {
|
|
8795
|
-
type: "string",
|
|
8796
|
-
description: "Search query \u2014 searches symbol names, signatures, and doc comments"
|
|
8797
|
-
},
|
|
8798
|
-
kind: {
|
|
8799
|
-
type: "string",
|
|
8800
|
-
enum: [
|
|
8801
|
-
"class",
|
|
8802
|
-
"interface",
|
|
8803
|
-
"enum",
|
|
8804
|
-
"type",
|
|
8805
|
-
"function",
|
|
8806
|
-
"method",
|
|
8807
|
-
"var",
|
|
8808
|
-
"const",
|
|
8809
|
-
"let",
|
|
8810
|
-
"property",
|
|
8811
|
-
"parameter",
|
|
8812
|
-
"namespace",
|
|
8813
|
-
"object",
|
|
8814
|
-
"literal",
|
|
8815
|
-
"schema",
|
|
8816
|
-
"struct",
|
|
8817
|
-
"trait",
|
|
8818
|
-
"impl",
|
|
8819
|
-
"static",
|
|
8820
|
-
"mod"
|
|
8821
|
-
],
|
|
8822
|
-
description: "Filter by indexed symbol kind"
|
|
8823
|
-
},
|
|
8824
|
-
lang: {
|
|
8825
|
-
type: "string",
|
|
8826
|
-
enum: ["ts", "tsx", "js", "jsx", "go", "py", "rs", "json", "yaml"],
|
|
8827
|
-
description: "Filter by indexed language"
|
|
8828
|
-
},
|
|
8829
|
-
lspKind: {
|
|
8830
|
-
type: "integer",
|
|
8831
|
-
description: "Filter by LSP SymbolKind number (e.g. 5=Class, 12=Function, 11=Interface, 10=Enum)"
|
|
8832
|
-
},
|
|
8833
|
-
file: {
|
|
8834
|
-
type: "string",
|
|
8835
|
-
description: "Filter to files matching this path substring"
|
|
8836
|
-
},
|
|
8837
|
-
limit: {
|
|
8838
|
-
type: "integer",
|
|
8839
|
-
description: "Maximum results to return (default 20, max 100)",
|
|
8840
|
-
minimum: 1,
|
|
8841
|
-
maximum: 100
|
|
8842
|
-
},
|
|
8843
|
-
preferLsp: {
|
|
8844
|
-
type: "boolean",
|
|
8845
|
-
description: "Prefer live LSP results over the index. Index-only when the LSP plugin is not active. When the LSP plugin is active and this is true, results come from live workspaceSymbol queries."
|
|
8846
|
-
}
|
|
8847
|
-
},
|
|
8848
|
-
required: ["query"]
|
|
8849
|
-
},
|
|
8850
|
-
async execute(input, ctx, execOpts) {
|
|
8851
|
-
const state = getIndexState();
|
|
8852
|
-
if (state.indexing && !state.ready) {
|
|
8853
|
-
return {
|
|
8854
|
-
results: [],
|
|
8855
|
-
total: 0,
|
|
8856
|
-
query: input.query,
|
|
8857
|
-
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
8858
|
-
};
|
|
8859
|
-
}
|
|
8860
|
-
if (state.lastError) {
|
|
8861
|
-
const circuit = state.circuit;
|
|
8862
|
-
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s); the user can run /codebase-reindex to retry now.` : "Try /codebase-reindex.";
|
|
8863
|
-
return {
|
|
8864
|
-
results: [],
|
|
8865
|
-
total: 0,
|
|
8866
|
-
query: input.query,
|
|
8867
|
-
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
8868
|
-
};
|
|
8869
|
-
}
|
|
8870
|
-
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
|
|
8871
|
-
const { results, total } = await searchCodebaseIndex(
|
|
8872
|
-
{
|
|
8873
|
-
projectRoot: ctx.projectRoot,
|
|
8874
|
-
indexDir: codebaseIndexDirOverride(ctx),
|
|
8875
|
-
query: input.query,
|
|
8876
|
-
kind: input.kind?.toLowerCase(),
|
|
8877
|
-
lang: input.lang?.toLowerCase(),
|
|
8878
|
-
file: input.file,
|
|
8879
|
-
lspKind: input.lspKind,
|
|
8880
|
-
limit
|
|
8881
|
-
},
|
|
8882
|
-
{ signal: execOpts?.signal }
|
|
8883
|
-
);
|
|
8884
|
-
let hasPersistedIndex = state.ready || total > 0;
|
|
8885
|
-
if (!hasPersistedIndex) {
|
|
8886
|
-
try {
|
|
8887
|
-
const stats = await codebaseIndexStats(
|
|
8888
|
-
{ projectRoot: ctx.projectRoot, indexDir: codebaseIndexDirOverride(ctx) },
|
|
8889
|
-
{ signal: execOpts?.signal }
|
|
8890
|
-
);
|
|
8891
|
-
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
8892
|
-
} catch {
|
|
8893
|
-
}
|
|
8894
|
-
}
|
|
8895
|
-
return {
|
|
8896
|
-
results,
|
|
8897
|
-
total,
|
|
8898
|
-
query: input.query,
|
|
8899
|
-
...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
|
|
8900
|
-
};
|
|
8901
|
-
}
|
|
8902
|
-
};
|
|
8903
|
-
|
|
8904
8975
|
// src/codebase-index/codebase-stats-tool.ts
|
|
8905
8976
|
var codebaseStatsTool = {
|
|
8906
8977
|
name: "codebase-stats",
|
|
@@ -8994,9 +9065,9 @@ import * as path14 from "node:path";
|
|
|
8994
9065
|
var deadCodeScanTool = {
|
|
8995
9066
|
name: "dead-code-scan",
|
|
8996
9067
|
category: "Project",
|
|
8997
|
-
icon: "
|
|
9068
|
+
icon: "index",
|
|
8998
9069
|
description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project. Uses the codebase-index reference graph (import/call/type-ref edges) to compute transitive reachability from package.json entry points. Requires a built codebase-index (run `codebase-index` first if you get no results).",
|
|
8999
|
-
usageHint:
|
|
9070
|
+
usageHint: "SCANS ALL INDEXED FILES UNDER THE PROJECT ROOT:\n\n- `projectRoot` defaults to the current project root; `indexDir` overrides the resolved index location.\n- `entryPoints` is an array of file paths that AUGMENTS the auto-discovered entry points (package.json bin/main/exports/types plus conventional src/index.ts-style files) \u2014 it does not replace them.\n\nThe scan runs against the existing index; results are best-effort (dynamic imports, external consumers, and config-driven registration are invisible).",
|
|
9000
9071
|
permission: "auto",
|
|
9001
9072
|
mutating: false,
|
|
9002
9073
|
capabilities: ["fs.read"],
|
package/dist/diff.d.ts
CHANGED
|
@@ -12,7 +12,12 @@ interface DiffOutput {
|
|
|
12
12
|
diff: string;
|
|
13
13
|
files: string[];
|
|
14
14
|
truncated: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The format actually produced: 'unified' or 'stat' on the git path,
|
|
17
|
+
* 'dump' on the files-only line-numbered dump path.
|
|
18
|
+
*/
|
|
15
19
|
mode: string;
|
|
20
|
+
note?: string | undefined;
|
|
16
21
|
}
|
|
17
22
|
export declare const diffTool: Tool<DiffInput, DiffOutput>;
|
|
18
23
|
export {};
|
package/dist/diff.js
CHANGED
|
@@ -4,8 +4,10 @@ import { statSync } from "node:fs";
|
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path2 from "node:path";
|
|
6
6
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
7
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
7
8
|
|
|
8
9
|
// src/_util.ts
|
|
10
|
+
import * as fsp from "node:fs/promises";
|
|
9
11
|
import * as path from "node:path";
|
|
10
12
|
import * as Core from "@wrongstack/core/utils";
|
|
11
13
|
function resolvePath(input, ctx) {
|
|
@@ -29,14 +31,48 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
29
31
|
function safeResolve(input, ctx) {
|
|
30
32
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
31
33
|
}
|
|
34
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
35
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
36
|
+
const realRoots = await Promise.all(
|
|
37
|
+
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
38
|
+
);
|
|
39
|
+
let probe = absPath;
|
|
40
|
+
const pendingTail = [];
|
|
41
|
+
for (; ; ) {
|
|
42
|
+
let real;
|
|
43
|
+
try {
|
|
44
|
+
real = await fsp.realpath(probe);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (err.code === "ENOENT") {
|
|
47
|
+
const parent = path.dirname(probe);
|
|
48
|
+
if (parent === probe) return absPath;
|
|
49
|
+
pendingTail.unshift(path.basename(probe));
|
|
50
|
+
probe = parent;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
if (isInsideAny(real, realRoots)) {
|
|
56
|
+
return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function safeResolveReal(input, ctx) {
|
|
64
|
+
const abs = safeResolve(input, ctx);
|
|
65
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
66
|
+
}
|
|
32
67
|
|
|
33
68
|
// src/diff.ts
|
|
34
69
|
var MAX_FILE_DUMP_BYTES = 5 * 1024 * 1024;
|
|
70
|
+
var MAX_GIT_DIFF_CHARS = 1e5;
|
|
35
71
|
var diffTool = {
|
|
36
72
|
name: "diff",
|
|
37
73
|
category: "Filesystem",
|
|
38
74
|
description: "Show file content with line numbers, staged/working-tree diffs via git, or commit/branch diffs. A safer and more structured alternative to raw `git diff` via shell.",
|
|
39
|
-
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes).\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode`
|
|
75
|
+
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes). Result `mode` is "dump".\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode` only affects the git-diff path: "stat" runs `git diff --stat`; "side-by-side" is not supported and falls back to unified (result `mode` reports what was produced).\n- `context` sets the unified-diff context line count on the git path (`-U<n>`); the dump path has no context notion.\n\nNOTE: For a true file-vs-file unified diff, supply `a` and `b` so the tool delegates to `git diff`. The `files`-only path is a line-numbered dump, not a diff.\n\nThis tool has important safety guards against flag injection (see previous security findings).',
|
|
40
76
|
permission: "auto",
|
|
41
77
|
mutating: false,
|
|
42
78
|
maxOutputBytes: 262144,
|
|
@@ -69,11 +105,12 @@ var diffTool = {
|
|
|
69
105
|
mode: {
|
|
70
106
|
type: "string",
|
|
71
107
|
enum: ["unified", "side-by-side", "stat"],
|
|
72
|
-
description: 'Output format. "unified" is default
|
|
108
|
+
description: 'Output format for the git-diff path. "unified" is default; "stat" shows a summary only; "side-by-side" is not supported and falls back to unified. The `files`-only dump path ignores this.'
|
|
73
109
|
},
|
|
74
110
|
context: {
|
|
75
111
|
type: "integer",
|
|
76
|
-
|
|
112
|
+
minimum: 0,
|
|
113
|
+
description: "Number of context lines for git unified diffs (default: 3, passed as -U<n>). Ignored by the `files`-only dump path."
|
|
77
114
|
}
|
|
78
115
|
}
|
|
79
116
|
},
|
|
@@ -86,16 +123,31 @@ var diffTool = {
|
|
|
86
123
|
};
|
|
87
124
|
async function gitDiff(input, ctx, signal) {
|
|
88
125
|
if (input.a?.startsWith("-")) {
|
|
89
|
-
throw new
|
|
126
|
+
throw new ToolValidationError({
|
|
127
|
+
message: `diff: unsafe ref "${input.a}" \u2014 refs may not begin with '-' (flag injection)`,
|
|
128
|
+
field: "a"
|
|
129
|
+
});
|
|
90
130
|
}
|
|
91
131
|
if (input.b?.startsWith("-")) {
|
|
92
|
-
throw new
|
|
132
|
+
throw new ToolValidationError({
|
|
133
|
+
message: `diff: unsafe ref "${input.b}" \u2014 refs may not begin with '-' (flag injection)`,
|
|
134
|
+
field: "b"
|
|
135
|
+
});
|
|
93
136
|
}
|
|
137
|
+
const requestedMode = input.mode ?? "unified";
|
|
138
|
+
const statMode = requestedMode === "stat";
|
|
139
|
+
const effectiveMode = statMode ? "stat" : "unified";
|
|
140
|
+
const sideBySideNote = requestedMode === "side-by-side" ? "side-by-side output is not supported; a unified diff was produced instead." : void 0;
|
|
94
141
|
const gitDir = findGitDir(ctx.cwd);
|
|
95
142
|
if (!gitDir) {
|
|
96
|
-
return { diff: "", files: [], truncated: false, mode:
|
|
143
|
+
return { diff: "", files: [], truncated: false, mode: effectiveMode };
|
|
97
144
|
}
|
|
98
145
|
const args = ["diff", "--no-color"];
|
|
146
|
+
if (statMode) args.push("--stat");
|
|
147
|
+
if (!statMode && input.context !== void 0) {
|
|
148
|
+
const contextLines = Math.max(0, Math.floor(input.context));
|
|
149
|
+
if (Number.isFinite(contextLines)) args.push(`-U${contextLines}`);
|
|
150
|
+
}
|
|
99
151
|
if (input.staged) args.push("--staged");
|
|
100
152
|
if (input.a) args.push(input.a);
|
|
101
153
|
if (input.b) args.push(input.b);
|
|
@@ -104,11 +156,22 @@ async function gitDiff(input, ctx, signal) {
|
|
|
104
156
|
args.push("--", ...files.map((f) => f.trim()));
|
|
105
157
|
}
|
|
106
158
|
const result = await runGit(args, gitDir, signal);
|
|
159
|
+
let diff = result.stdout;
|
|
160
|
+
let truncated = false;
|
|
161
|
+
if (diff.length > MAX_GIT_DIFF_CHARS) {
|
|
162
|
+
let clipped = diff.slice(0, MAX_GIT_DIFF_CHARS);
|
|
163
|
+
const nl = clipped.lastIndexOf("\n");
|
|
164
|
+
if (nl > 0) clipped = clipped.slice(0, nl);
|
|
165
|
+
diff = `${clipped}
|
|
166
|
+
\u2026[git diff truncated: ${result.stdout.length - clipped.length} of ${result.stdout.length} characters omitted]`;
|
|
167
|
+
truncated = true;
|
|
168
|
+
}
|
|
107
169
|
return {
|
|
108
|
-
diff
|
|
170
|
+
diff,
|
|
109
171
|
files: [],
|
|
110
|
-
truncated
|
|
111
|
-
mode:
|
|
172
|
+
truncated,
|
|
173
|
+
mode: effectiveMode,
|
|
174
|
+
note: sideBySideNote
|
|
112
175
|
};
|
|
113
176
|
}
|
|
114
177
|
function findGitDir(cwd) {
|
|
@@ -154,13 +217,13 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
154
217
|
diff: "No files specified",
|
|
155
218
|
files: [],
|
|
156
219
|
truncated: false,
|
|
157
|
-
mode:
|
|
220
|
+
mode: "dump"
|
|
158
221
|
};
|
|
159
222
|
}
|
|
160
223
|
const results = [];
|
|
161
224
|
let truncated = false;
|
|
162
225
|
for (const file of files) {
|
|
163
|
-
const absPath =
|
|
226
|
+
const absPath = await safeResolveReal(file, ctx);
|
|
164
227
|
const stat2 = await fs.stat(absPath).catch(() => null);
|
|
165
228
|
if (!stat2?.isFile()) continue;
|
|
166
229
|
if (stat2.size > MAX_FILE_DUMP_BYTES) {
|
|
@@ -178,7 +241,10 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
178
241
|
diff: results.join("\n\n"),
|
|
179
242
|
files,
|
|
180
243
|
truncated,
|
|
181
|
-
mode:
|
|
244
|
+
// Honest mode: this path always produces a line-numbered dump — it never
|
|
245
|
+
// honors `mode`, so it must not echo the requested value back.
|
|
246
|
+
mode: "dump",
|
|
247
|
+
note: input.mode !== void 0 ? "The `files`-only path is a line-numbered dump; `mode` only affects the git-diff path (`a`/`b`)." : void 0
|
|
182
248
|
};
|
|
183
249
|
}
|
|
184
250
|
function formatWithLineNumbers(file, lines) {
|