@unerr-ai/unerr 0.2.1 → 0.2.3
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/README.md +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint FRP — `file_read` with token-budget awareness, ranked entity matching,
|
|
3
|
-
* disambiguation feedback, and graceful degradation.
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import { relative, resolve } from "node:path";
|
|
7
|
-
import { extractEntities } from "../../intelligence/ast-extractor.js";
|
|
8
|
-
import { appendFileReadLog, } from "../../proxy/shell-compression-log.js";
|
|
9
|
-
import { buildFileOutline } from "./file-outline.js";
|
|
10
|
-
const LINE_GATE = 200;
|
|
11
|
-
const ENTITY_CONTEXT = 5;
|
|
12
|
-
const LOG_TAIL_LINES = 200;
|
|
13
|
-
const MAX_READ_LINES = 10_000;
|
|
14
|
-
const HARD_GATE_CEILING = 10_000;
|
|
15
|
-
const CHARS_PER_TOKEN = 4;
|
|
16
|
-
const AVG_CHARS_PER_LINE = 80;
|
|
17
|
-
const GRAPH_TIMEOUT_MS = 100;
|
|
18
|
-
function isGeneratedPath(rel) {
|
|
19
|
-
return /(?:^|\/)node_modules\/|(?:^|\/)dist\/|\/\.next\/|\.generated\./.test(rel);
|
|
20
|
-
}
|
|
21
|
-
function isProbableLogPath(rel) {
|
|
22
|
-
return /\.(log|txt)$/i.test(rel) || /\/logs?\//i.test(rel);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Rank entities against a query string.
|
|
26
|
-
* Scoring: exact(100) > case_insensitive(90) > prefix(80) > camelCase_segment(70) > substring(40-60)
|
|
27
|
-
*/
|
|
28
|
-
export function rankEntityMatches(entities, query) {
|
|
29
|
-
const results = [];
|
|
30
|
-
const queryLower = query.toLowerCase();
|
|
31
|
-
for (const entity of entities) {
|
|
32
|
-
const name = entity.name;
|
|
33
|
-
const nameLower = name.toLowerCase();
|
|
34
|
-
if (name === query) {
|
|
35
|
-
results.push({ entity, score: 100, matchType: "exact" });
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (nameLower === queryLower) {
|
|
39
|
-
results.push({ entity, score: 90, matchType: "case_insensitive" });
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (nameLower.startsWith(queryLower)) {
|
|
43
|
-
results.push({ entity, score: 80, matchType: "prefix" });
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
// CamelCase segment match ("compress" matches "compressShellOutput")
|
|
47
|
-
const segments = name
|
|
48
|
-
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
49
|
-
.toLowerCase()
|
|
50
|
-
.split(/[_\-]/);
|
|
51
|
-
if (segments.some((s) => s === queryLower)) {
|
|
52
|
-
results.push({ entity, score: 70, matchType: "camelCase_segment" });
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (nameLower.includes(queryLower)) {
|
|
56
|
-
const specificity = query.length / name.length;
|
|
57
|
-
results.push({
|
|
58
|
-
entity,
|
|
59
|
-
score: 40 + specificity * 20,
|
|
60
|
-
matchType: "substring",
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return results.sort((a, b) => b.score - a.score);
|
|
65
|
-
}
|
|
66
|
-
// ─── File Read Logging ──────────────────────────────────────────────────────
|
|
67
|
-
function logFileRead(cwd, file, mode, totalLines, returnedLines, entity, tokenEstimate) {
|
|
68
|
-
const savedPct = totalLines > 0
|
|
69
|
-
? Math.round(((totalLines - returnedLines) / totalLines) * 100)
|
|
70
|
-
: 0;
|
|
71
|
-
appendFileReadLog(cwd, {
|
|
72
|
-
ts: new Date().toISOString(),
|
|
73
|
-
file,
|
|
74
|
-
mode,
|
|
75
|
-
totalLines,
|
|
76
|
-
returnedLines,
|
|
77
|
-
savedPct,
|
|
78
|
-
entity,
|
|
79
|
-
tokenEstimate,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
// ─── Core File Read ─────────────────────────────────────────────────────────
|
|
83
|
-
/**
|
|
84
|
-
* MCP / QueryRouter entry — returns structured content; `_layer6_meta` is merged into `ToolResult._meta`.
|
|
85
|
-
*/
|
|
86
|
-
export async function runFileReadForRouter(args, ctx) {
|
|
87
|
-
const filePathArg = args.file_path;
|
|
88
|
-
if (!filePathArg)
|
|
89
|
-
throw new Error("file_read requires file_path");
|
|
90
|
-
const rawPurpose = args.purpose?.trim() || "explore";
|
|
91
|
-
// purpose:'edit' and force_full are removed — treat as 'explore' if passed
|
|
92
|
-
const purpose = rawPurpose === "edit" ? "explore" : rawPurpose;
|
|
93
|
-
const entityName = args.entity?.trim();
|
|
94
|
-
let entityWindowApplied = false;
|
|
95
|
-
let entityMatchInfo;
|
|
96
|
-
let offset = args.offset != null ? Math.max(1, Number(args.offset)) : undefined;
|
|
97
|
-
let limit = args.limit != null
|
|
98
|
-
? Math.min(MAX_READ_LINES, Math.max(1, Number(args.limit)))
|
|
99
|
-
: undefined;
|
|
100
|
-
// Token budget — adaptive gating and output sizing
|
|
101
|
-
const defaultBudget = purpose === "reference" ? 1000 : 2000;
|
|
102
|
-
const tokenBudget = typeof args.token_budget === "number" && args.token_budget >= 100
|
|
103
|
-
? args.token_budget
|
|
104
|
-
: defaultBudget;
|
|
105
|
-
const budgetLines = Math.floor((tokenBudget * CHARS_PER_TOKEN) / AVG_CHARS_PER_LINE);
|
|
106
|
-
const abs = resolve(ctx.cwd, filePathArg);
|
|
107
|
-
const rel = relative(ctx.cwd, abs).replace(/\\/g, "/") || filePathArg;
|
|
108
|
-
// Out-of-project files have no graph data — skip graph queries to prevent hangs
|
|
109
|
-
const isOutOfProject = rel.startsWith("..");
|
|
110
|
-
if (!existsSync(abs)) {
|
|
111
|
-
return { content: { error: `File not found: ${abs}` } };
|
|
112
|
-
}
|
|
113
|
-
const raw = readFileSync(abs);
|
|
114
|
-
const sampleEnd = Math.min(raw.length, 8192);
|
|
115
|
-
for (let i = 0; i < sampleEnd; i++) {
|
|
116
|
-
if (raw[i] === 0) {
|
|
117
|
-
return {
|
|
118
|
-
content: {
|
|
119
|
-
error: "Binary file — not returned as text. Use a binary-capable tool.",
|
|
120
|
-
},
|
|
121
|
-
_layer6_meta: { format: "json" },
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const text = raw.toString("utf-8");
|
|
126
|
-
const lines = text.split("\n");
|
|
127
|
-
const totalLines = lines.length;
|
|
128
|
-
// Adaptive gating: use budget-derived threshold but respect hard ceiling
|
|
129
|
-
const effectiveGate = Math.max(LINE_GATE, Math.min(HARD_GATE_CEILING, budgetLines));
|
|
130
|
-
if (totalLines > effectiveGate &&
|
|
131
|
-
totalLines > LINE_GATE &&
|
|
132
|
-
offset === undefined &&
|
|
133
|
-
!entityName &&
|
|
134
|
-
!entityName) {
|
|
135
|
-
const outline = await buildFileOutline({
|
|
136
|
-
cwd: ctx.cwd,
|
|
137
|
-
filePathArg,
|
|
138
|
-
graph: isOutOfProject ? null : ctx.graph,
|
|
139
|
-
});
|
|
140
|
-
logFileRead(ctx.cwd, rel, "gated", totalLines, outline.entities.length, undefined, outline.token_estimate);
|
|
141
|
-
return {
|
|
142
|
-
content: {
|
|
143
|
-
...outline,
|
|
144
|
-
gated: true,
|
|
145
|
-
_gate_reason: `File has ${totalLines} lines (> ${effectiveGate}). Structure shown — call file_read again with entity or offset/limit for targeted access.`,
|
|
146
|
-
},
|
|
147
|
-
_layer6_meta: {
|
|
148
|
-
format: "outline",
|
|
149
|
-
gated: true,
|
|
150
|
-
total_lines: totalLines,
|
|
151
|
-
total_chars: text.length,
|
|
152
|
-
// outline.token_estimate is computed from the FULL FILE content
|
|
153
|
-
// (file-outline.ts:228). For the gated path we want the size of what
|
|
154
|
-
// we ACTUALLY delivered (the outline JSON), not the file we replaced
|
|
155
|
-
// it with. Use the serialized outline body length.
|
|
156
|
-
tokens_estimate: Math.ceil(JSON.stringify(outline).length / 4),
|
|
157
|
-
optimization: `file_read gated \u2192 outline (${totalLines} lines)`,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
// ─── Entity Resolution (with fallback chain) ─────────────────────────────
|
|
162
|
-
if (entityName) {
|
|
163
|
-
try {
|
|
164
|
-
let resolvedFromGraph = false;
|
|
165
|
-
if (ctx.graph && !isOutOfProject) {
|
|
166
|
-
const entities = await Promise.race([
|
|
167
|
-
ctx.graph.getEntitiesByFile(rel),
|
|
168
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("graph_timeout")), GRAPH_TIMEOUT_MS)),
|
|
169
|
-
]).catch(() => []);
|
|
170
|
-
if (entities.length > 0) {
|
|
171
|
-
const ranked = rankEntityMatches(entities, entityName);
|
|
172
|
-
const topRanked = ranked[0];
|
|
173
|
-
if (ranked.length > 0 && topRanked && topRanked.score >= 70) {
|
|
174
|
-
const match = topRanked.entity;
|
|
175
|
-
if (match.start_line >= 1 && match.start_line <= totalLines) {
|
|
176
|
-
const start = Math.max(1, match.start_line - ENTITY_CONTEXT);
|
|
177
|
-
const endLine = (match.end_line ?? 0) > match.start_line
|
|
178
|
-
? match.end_line
|
|
179
|
-
: match.start_line + match.body.split("\n").length - 1;
|
|
180
|
-
const end = Math.min(totalLines, endLine + ENTITY_CONTEXT);
|
|
181
|
-
offset = start;
|
|
182
|
-
limit = Math.min(MAX_READ_LINES, end - start + 1);
|
|
183
|
-
resolvedFromGraph = true;
|
|
184
|
-
entityWindowApplied = true;
|
|
185
|
-
entityMatchInfo = {
|
|
186
|
-
matched: true,
|
|
187
|
-
name: match.name,
|
|
188
|
-
score: topRanked.score,
|
|
189
|
-
matchType: topRanked.matchType,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else if (ranked.length > 0) {
|
|
194
|
-
entityMatchInfo = {
|
|
195
|
-
matched: false,
|
|
196
|
-
query: entityName,
|
|
197
|
-
suggestions: ranked.slice(0, 5).map((r) => r.entity.name),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
entityMatchInfo = {
|
|
202
|
-
matched: false,
|
|
203
|
-
query: entityName,
|
|
204
|
-
suggestions: [],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (!resolvedFromGraph) {
|
|
210
|
-
const extracted = extractEntities(text, rel);
|
|
211
|
-
const astEntities = extracted.map((e) => ({
|
|
212
|
-
name: e.name,
|
|
213
|
-
start_line: e.line_start,
|
|
214
|
-
body: lines.slice(e.line_start - 1, e.line_end).join("\n"),
|
|
215
|
-
}));
|
|
216
|
-
const ranked = rankEntityMatches(astEntities, entityName);
|
|
217
|
-
const topAstRank = ranked[0];
|
|
218
|
-
if (topAstRank && topAstRank.score >= 70 && topAstRank.entity) {
|
|
219
|
-
const match = topAstRank.entity;
|
|
220
|
-
if (match.start_line >= 1 && match.start_line <= totalLines) {
|
|
221
|
-
const start = Math.max(1, match.start_line - ENTITY_CONTEXT);
|
|
222
|
-
const endLine = match.start_line + match.body.split("\n").length - 1;
|
|
223
|
-
const end = Math.min(totalLines, endLine + ENTITY_CONTEXT);
|
|
224
|
-
offset = start;
|
|
225
|
-
limit = Math.min(MAX_READ_LINES, end - start + 1);
|
|
226
|
-
entityWindowApplied = true;
|
|
227
|
-
entityMatchInfo = {
|
|
228
|
-
matched: true,
|
|
229
|
-
name: match.name,
|
|
230
|
-
score: topAstRank.score,
|
|
231
|
-
matchType: topAstRank.matchType,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
else if (ranked.length > 0 && !entityMatchInfo) {
|
|
236
|
-
entityMatchInfo = {
|
|
237
|
-
matched: false,
|
|
238
|
-
query: entityName,
|
|
239
|
-
suggestions: ranked.slice(0, 5).map((r) => r.entity.name),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
else if (!entityMatchInfo) {
|
|
243
|
-
entityMatchInfo = {
|
|
244
|
-
matched: false,
|
|
245
|
-
query: entityName,
|
|
246
|
-
suggestions: [],
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
// Entity resolution failed entirely — fall through to normal read
|
|
253
|
-
entityMatchInfo = {
|
|
254
|
-
matched: false,
|
|
255
|
-
query: entityName,
|
|
256
|
-
error: "resolution_failed",
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
// Entity requested but not found on a large file → return outline with feedback
|
|
260
|
-
if (!entityWindowApplied && totalLines > LINE_GATE) {
|
|
261
|
-
const outline = await buildFileOutline({
|
|
262
|
-
cwd: ctx.cwd,
|
|
263
|
-
filePathArg,
|
|
264
|
-
graph: isOutOfProject ? null : ctx.graph,
|
|
265
|
-
});
|
|
266
|
-
logFileRead(ctx.cwd, rel, "gated", totalLines, outline.entities.length, entityName, outline.token_estimate);
|
|
267
|
-
return {
|
|
268
|
-
content: {
|
|
269
|
-
...outline,
|
|
270
|
-
gated: true,
|
|
271
|
-
entity_search: entityMatchInfo ?? {
|
|
272
|
-
matched: false,
|
|
273
|
-
query: entityName,
|
|
274
|
-
suggestions: [],
|
|
275
|
-
},
|
|
276
|
-
_gate_reason: `Entity "${entityName}" not found with high confidence. Use one of the suggestions or specify offset/limit. NOTE: If you plan to Edit this file, you MUST call built-in Read (not file_read) first.`,
|
|
277
|
-
},
|
|
278
|
-
_layer6_meta: {
|
|
279
|
-
format: "outline",
|
|
280
|
-
gated: true,
|
|
281
|
-
total_lines: totalLines,
|
|
282
|
-
total_chars: text.length,
|
|
283
|
-
// outline.token_estimate is full-file size; we need delivered size.
|
|
284
|
-
tokens_estimate: Math.ceil(JSON.stringify(outline).length / 4),
|
|
285
|
-
optimization: `file_read gated \u2192 outline (${totalLines} lines, entity-fallback)`,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// ─── Log tail optimization ────────────────────────────────────────────────
|
|
291
|
-
if (offset === undefined &&
|
|
292
|
-
limit === undefined &&
|
|
293
|
-
totalLines > LINE_GATE &&
|
|
294
|
-
isProbableLogPath(rel)) {
|
|
295
|
-
offset = Math.max(1, totalLines - LOG_TAIL_LINES + 1);
|
|
296
|
-
limit = LOG_TAIL_LINES;
|
|
297
|
-
}
|
|
298
|
-
// ─── Apply budget-capped limit ────────────────────────────────────────────
|
|
299
|
-
const effOffset = offset ?? 1;
|
|
300
|
-
const effLimit = limit ?? Math.min(budgetLines, totalLines);
|
|
301
|
-
const sliced = lines.slice(effOffset - 1, effOffset - 1 + effLimit);
|
|
302
|
-
const numbered = sliced
|
|
303
|
-
.map((line, i) => `${effOffset + i}\t${line}`)
|
|
304
|
-
.join("\n");
|
|
305
|
-
// Entity-overflow fallback: when an entity *was* resolved but its body
|
|
306
|
-
// alone exceeds the wire byte cap, returning a generic 'too_large' is the
|
|
307
|
-
// wrong move — the caller already narrowed via entity:. Hand back a
|
|
308
|
-
// concrete chunk plan + the token_budget that would fit, so the next call
|
|
309
|
-
// lands. Mirror wire-cap.ts HARD_BYTE_CAP (8192). Only triggers when the
|
|
310
|
-
// caller did NOT explicitly lift token_budget — if they did, trust them
|
|
311
|
-
// and let wire-cap report the precise needed budget.
|
|
312
|
-
const WIRE_HARD_BYTE_CAP = 8192;
|
|
313
|
-
const explicitTokenBudget = typeof args.token_budget === "number" && args.token_budget >= 100;
|
|
314
|
-
if (entityWindowApplied &&
|
|
315
|
-
!explicitTokenBudget &&
|
|
316
|
-
numbered.length > WIRE_HARD_BYTE_CAP) {
|
|
317
|
-
const entityStart = effOffset;
|
|
318
|
-
const entityEnd = effOffset + sliced.length - 1;
|
|
319
|
-
const totalEntityLines = sliced.length;
|
|
320
|
-
const chunkSize = Math.max(40, Math.ceil(totalEntityLines / 4));
|
|
321
|
-
const chunks = [];
|
|
322
|
-
for (let s = entityStart; s <= entityEnd; s += chunkSize) {
|
|
323
|
-
chunks.push({
|
|
324
|
-
offset: s,
|
|
325
|
-
limit: Math.min(chunkSize, entityEnd - s + 1),
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
const neededTokens = Math.ceil(Math.ceil(numbered.length / CHARS_PER_TOKEN) / 100) * 100;
|
|
329
|
-
const entityLabel = entityMatchInfo?.name ?? entityName ?? "(entity)";
|
|
330
|
-
logFileRead(ctx.cwd, rel, "gated", totalLines, sliced.length, entityLabel, neededTokens);
|
|
331
|
-
return {
|
|
332
|
-
content: {
|
|
333
|
-
entity_overflow: true,
|
|
334
|
-
entity: entityLabel,
|
|
335
|
-
file_path: rel,
|
|
336
|
-
start_line: entityStart,
|
|
337
|
-
end_line: entityEnd,
|
|
338
|
-
total_entity_lines: totalEntityLines,
|
|
339
|
-
bytes: numbered.length,
|
|
340
|
-
cap_bytes: WIRE_HARD_BYTE_CAP,
|
|
341
|
-
suggested_token_budget: neededTokens,
|
|
342
|
-
suggested_chunks: chunks,
|
|
343
|
-
_hint: `entity_too_large — "${entityLabel}" spans lines ${entityStart}-${entityEnd} (~${neededTokens} tokens). Pick a suggested_chunks entry and call file_read again with offset+limit, or retry with token_budget:${neededTokens}.`,
|
|
344
|
-
},
|
|
345
|
-
_layer6_meta: {
|
|
346
|
-
format: "json",
|
|
347
|
-
total_lines: totalLines,
|
|
348
|
-
total_chars: text.length,
|
|
349
|
-
tokens_estimate: Math.ceil(numbered.length / CHARS_PER_TOKEN),
|
|
350
|
-
optimization: `file_read entity_overflow → chunk plan (${totalEntityLines} lines)`,
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
let body = effOffset > 1 || sliced.length < totalLines || entityWindowApplied
|
|
355
|
-
? `${numbered}\n\n(Showing lines ${effOffset}-${effOffset + sliced.length - 1} of ${totalLines} total)`
|
|
356
|
-
: numbered;
|
|
357
|
-
// Never return empty content for a valid file
|
|
358
|
-
if (body.trim().length === 0 && totalLines > 0) {
|
|
359
|
-
const fallbackSlice = lines.slice(0, Math.min(50, totalLines));
|
|
360
|
-
body = fallbackSlice.map((l, i) => `${i + 1}\t${l}`).join("\n");
|
|
361
|
-
body += `\n\n(Fallback: showing first ${fallbackSlice.length} of ${totalLines} lines)`;
|
|
362
|
-
}
|
|
363
|
-
const warnings = [];
|
|
364
|
-
if (isGeneratedPath(rel)) {
|
|
365
|
-
warnings.push("Path looks generated or vendor (`node_modules` / `dist` / `.next`) — verify you intended to read it.");
|
|
366
|
-
}
|
|
367
|
-
if (warnings.length > 0) {
|
|
368
|
-
body = `${warnings.join("\n")}\n\n${body}`;
|
|
369
|
-
}
|
|
370
|
-
const meta = {
|
|
371
|
-
format: "json",
|
|
372
|
-
tokens_estimate: Math.ceil(sliced.join("\n").length / CHARS_PER_TOKEN),
|
|
373
|
-
total_lines: totalLines,
|
|
374
|
-
total_chars: text.length,
|
|
375
|
-
};
|
|
376
|
-
if (effOffset > 1 || sliced.length < totalLines || entityWindowApplied) {
|
|
377
|
-
meta.optimization = `file_read window lines ${effOffset}-${effOffset + sliced.length - 1}`;
|
|
378
|
-
}
|
|
379
|
-
if (entityWindowApplied) {
|
|
380
|
-
meta.optimization = `${meta.optimization ?? "file_read"} · entity`;
|
|
381
|
-
if (entityMatchInfo?.matchType && entityMatchInfo.matchType !== "exact") {
|
|
382
|
-
meta.optimization += ` (${entityMatchInfo.matchType})`;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (isProbableLogPath(rel) &&
|
|
386
|
-
offset !== undefined &&
|
|
387
|
-
totalLines > LINE_GATE) {
|
|
388
|
-
meta.optimization = `${meta.optimization ?? "file_read"} · log_tail`;
|
|
389
|
-
}
|
|
390
|
-
if (isOutOfProject) {
|
|
391
|
-
meta.out_of_project = true;
|
|
392
|
-
meta._hint =
|
|
393
|
-
"File is outside the indexed project. Graph features (references, callers) unavailable.";
|
|
394
|
-
}
|
|
395
|
-
// Log file read efficiency
|
|
396
|
-
const returnedLineCount = sliced.length;
|
|
397
|
-
const readMode = entityWindowApplied
|
|
398
|
-
? "entity"
|
|
399
|
-
: isProbableLogPath(rel) && offset !== undefined && totalLines > LINE_GATE
|
|
400
|
-
? "log_tail"
|
|
401
|
-
: effOffset > 1 || returnedLineCount < totalLines
|
|
402
|
-
? "slice"
|
|
403
|
-
: "full";
|
|
404
|
-
logFileRead(ctx.cwd, rel, readMode, totalLines, returnedLineCount, entityWindowApplied ? entityMatchInfo?.name : undefined, meta.tokens_estimate);
|
|
405
|
-
return {
|
|
406
|
-
content: body,
|
|
407
|
-
_layer6_meta: meta,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
/** Interactive / coding-tools entry — same semantics as router. */
|
|
411
|
-
export async function runFileReadTool(args, ctx) {
|
|
412
|
-
try {
|
|
413
|
-
const r = await runFileReadForRouter(args, {
|
|
414
|
-
cwd: ctx.cwd,
|
|
415
|
-
graph: ctx.graph ?? null,
|
|
416
|
-
});
|
|
417
|
-
return { content: r.content };
|
|
418
|
-
}
|
|
419
|
-
catch (e) {
|
|
420
|
-
return {
|
|
421
|
-
content: `file_read failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
422
|
-
isError: true,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileRead Tool — read file contents with Sprint FE-B metadata-first protocol.
|
|
3
|
-
*/
|
|
4
|
-
import { runFileReadTool } from "./file-read-protocol.js";
|
|
5
|
-
export const fileReadTool = {
|
|
6
|
-
name: "file_read",
|
|
7
|
-
description: "Read file text with line numbers. Large files (>200 lines) return a structural outline unless you pass offset/limit or entity (symbol name). Logs default to tail window when large.",
|
|
8
|
-
inputSchema: {
|
|
9
|
-
type: "object",
|
|
10
|
-
properties: {
|
|
11
|
-
file_path: {
|
|
12
|
-
type: "string",
|
|
13
|
-
description: "Absolute or relative path to the file to read",
|
|
14
|
-
},
|
|
15
|
-
offset: {
|
|
16
|
-
type: "number",
|
|
17
|
-
description: "1-based start line (optional)",
|
|
18
|
-
},
|
|
19
|
-
limit: {
|
|
20
|
-
type: "number",
|
|
21
|
-
description: "Max lines to read (optional, default 2000, max 10000)",
|
|
22
|
-
},
|
|
23
|
-
entity: {
|
|
24
|
-
type: "string",
|
|
25
|
-
description: "Entity/symbol name — returns that definition ±5 lines context when found",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
required: ["file_path"],
|
|
29
|
-
},
|
|
30
|
-
isReadOnly: true,
|
|
31
|
-
requiresPermission: false,
|
|
32
|
-
execute(args, ctx) {
|
|
33
|
-
return runFileReadTool(args, ctx);
|
|
34
|
-
},
|
|
35
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileWrite Tool — create or overwrite a file.
|
|
3
|
-
* Requires explicit user permission since it modifies the filesystem.
|
|
4
|
-
*/
|
|
5
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { dirname, resolve } from "node:path";
|
|
7
|
-
export const fileWriteTool = {
|
|
8
|
-
name: "file_write",
|
|
9
|
-
description: "Write content to a file. Creates the file if it doesn't exist, or overwrites it if it does. " +
|
|
10
|
-
"Parent directories are created automatically.",
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
file_path: {
|
|
15
|
-
type: "string",
|
|
16
|
-
description: "Absolute or relative path to the file to write",
|
|
17
|
-
},
|
|
18
|
-
content: {
|
|
19
|
-
type: "string",
|
|
20
|
-
description: "The content to write to the file",
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
required: ["file_path", "content"],
|
|
24
|
-
},
|
|
25
|
-
isReadOnly: false,
|
|
26
|
-
requiresPermission: true,
|
|
27
|
-
async execute(args, ctx) {
|
|
28
|
-
const filePath = resolve(ctx.cwd, args.file_path);
|
|
29
|
-
const content = args.content;
|
|
30
|
-
try {
|
|
31
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
32
|
-
writeFileSync(filePath, content, "utf-8");
|
|
33
|
-
const lineCount = content.split("\n").length;
|
|
34
|
-
return { content: `Wrote ${lineCount} lines to ${filePath}` };
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
return {
|
|
38
|
-
content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`,
|
|
39
|
-
isError: true,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Glob Tool — find files by name patterns.
|
|
3
|
-
* Uses Node.js built-in fs for portability.
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
|
-
import { join, relative, resolve } from "node:path";
|
|
7
|
-
const MAX_RESULTS = 500;
|
|
8
|
-
const MAX_DEPTH = 15;
|
|
9
|
-
const SKIP_DIRS = new Set([
|
|
10
|
-
"node_modules",
|
|
11
|
-
".git",
|
|
12
|
-
"dist",
|
|
13
|
-
"build",
|
|
14
|
-
".next",
|
|
15
|
-
"coverage",
|
|
16
|
-
"__pycache__",
|
|
17
|
-
]);
|
|
18
|
-
function matchPattern(filepath, pattern) {
|
|
19
|
-
// Convert glob to regex: ** → any path, * → any name segment, ? → any char
|
|
20
|
-
const regex = pattern
|
|
21
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // Escape regex specials
|
|
22
|
-
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
|
|
23
|
-
.replace(/\*/g, "[^/]*")
|
|
24
|
-
.replace(/<<<GLOBSTAR>>>/g, ".*")
|
|
25
|
-
.replace(/\?/g, "[^/]");
|
|
26
|
-
return (new RegExp(`^${regex}$`).test(filepath) ||
|
|
27
|
-
new RegExp(`(^|/)${regex}$`).test(filepath));
|
|
28
|
-
}
|
|
29
|
-
function walkDir(dir, base, depth) {
|
|
30
|
-
if (depth > MAX_DEPTH)
|
|
31
|
-
return [];
|
|
32
|
-
const files = [];
|
|
33
|
-
try {
|
|
34
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
35
|
-
if (entry.name.startsWith(".") && entry.name !== ".")
|
|
36
|
-
continue;
|
|
37
|
-
if (SKIP_DIRS.has(entry.name))
|
|
38
|
-
continue;
|
|
39
|
-
const full = join(dir, entry.name);
|
|
40
|
-
const rel = relative(base, full);
|
|
41
|
-
if (entry.isDirectory()) {
|
|
42
|
-
files.push(...walkDir(full, base, depth + 1));
|
|
43
|
-
}
|
|
44
|
-
else if (entry.isFile()) {
|
|
45
|
-
files.push(rel);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
// Permission denied, etc.
|
|
51
|
-
}
|
|
52
|
-
return files;
|
|
53
|
-
}
|
|
54
|
-
export const globTool = {
|
|
55
|
-
name: "glob",
|
|
56
|
-
description: "Find files matching a glob pattern. Returns matching file paths sorted by modification time. " +
|
|
57
|
-
'Examples: "**/*.ts", "src/**/*.test.ts", "package.json"',
|
|
58
|
-
inputSchema: {
|
|
59
|
-
type: "object",
|
|
60
|
-
properties: {
|
|
61
|
-
pattern: {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: 'Glob pattern to match files (e.g., "**/*.ts", "src/**/*.tsx")',
|
|
64
|
-
},
|
|
65
|
-
path: {
|
|
66
|
-
type: "string",
|
|
67
|
-
description: "Directory to search in. Default: project root",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ["pattern"],
|
|
71
|
-
},
|
|
72
|
-
isReadOnly: true,
|
|
73
|
-
requiresPermission: false,
|
|
74
|
-
async execute(args, ctx) {
|
|
75
|
-
const pattern = args.pattern;
|
|
76
|
-
const searchPath = resolve(ctx.cwd, args.path ?? ".");
|
|
77
|
-
if (!existsSync(searchPath)) {
|
|
78
|
-
return {
|
|
79
|
-
content: `Path not found: ${searchPath}. For code lookups, call search_code (entity-aware, no path required) instead.`,
|
|
80
|
-
isError: true,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
const allFiles = walkDir(searchPath, searchPath, 0);
|
|
84
|
-
const matches = allFiles
|
|
85
|
-
.filter((f) => matchPattern(f, pattern))
|
|
86
|
-
.slice(0, MAX_RESULTS);
|
|
87
|
-
// Sort by modification time (newest first)
|
|
88
|
-
const withMtime = matches.map((f) => {
|
|
89
|
-
try {
|
|
90
|
-
const stat = statSync(join(searchPath, f));
|
|
91
|
-
return { path: f, mtime: stat.mtimeMs };
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return { path: f, mtime: 0 };
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
98
|
-
if (withMtime.length === 0) {
|
|
99
|
-
return {
|
|
100
|
-
content: `No files matching: ${pattern}. For finding code entities by name, call search_code (graph-indexed, no glob pattern required).`,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const result = withMtime.map((f) => f.path).join("\n");
|
|
104
|
-
const truncated = matches.length >= MAX_RESULTS
|
|
105
|
-
? `\n\n(Truncated at ${MAX_RESULTS} results)`
|
|
106
|
-
: "";
|
|
107
|
-
return { content: result + truncated };
|
|
108
|
-
},
|
|
109
|
-
};
|