@causyne/agent 0.0.0-stage → 1.10.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/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
|
|
4
|
+
// Phase 3 AGI Upgrade: Fast Levenshtein Distance for Fuzzy Matching
|
|
5
|
+
function getLevenshteinDistance(a, b) {
|
|
6
|
+
const matrix = Array(a.length + 1).fill(null).map(() => Array(b.length + 1).fill(null));
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
|
|
9
|
+
for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
|
|
10
|
+
|
|
11
|
+
for (let i = 1; i <= a.length; i++) {
|
|
12
|
+
for (let j = 1; j <= b.length; j++) {
|
|
13
|
+
const indicator = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
14
|
+
matrix[i][j] = Math.min(
|
|
15
|
+
matrix[i - 1][j] + 1, // deletion
|
|
16
|
+
matrix[i][j - 1] + 1, // insertion
|
|
17
|
+
matrix[i - 1][j - 1] + indicator // substitution
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return matrix[a.length][b.length];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function calculateConfidence(original, match) {
|
|
25
|
+
const dist = getLevenshteinDistance(original, match);
|
|
26
|
+
const maxLen = Math.max(original.length, match.length);
|
|
27
|
+
if (maxLen === 0) return 100;
|
|
28
|
+
return ((maxLen - dist) / maxLen) * 100;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const fsEdit = {
|
|
32
|
+
schema: {
|
|
33
|
+
name: 'fs.edit',
|
|
34
|
+
description: 'Surgically edit specific lines/blocks in a file. Employs 90% confidence Levenshtein fuzzy matching if exact match fails.',
|
|
35
|
+
parameters: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
path: { type: 'string' },
|
|
39
|
+
edits: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
searchContent: { type: 'string' },
|
|
45
|
+
replaceContent: { type: 'string' }
|
|
46
|
+
},
|
|
47
|
+
required: ['searchContent', 'replaceContent']
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
required: ['path', 'edits']
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
async execute(args) {
|
|
55
|
+
try {
|
|
56
|
+
const target = args.path.replace(/^~\//, os.homedir() + '/');
|
|
57
|
+
let originalContent = await fs.readFile(target, 'utf8');
|
|
58
|
+
let workingContent = originalContent; // Phase 3 AGI Upgrade: Atomic Rollback prep
|
|
59
|
+
|
|
60
|
+
let replacements = 0;
|
|
61
|
+
for (let i = 0; i < args.edits.length; i++) {
|
|
62
|
+
const edit = args.edits[i];
|
|
63
|
+
|
|
64
|
+
// Try exact match first
|
|
65
|
+
if (workingContent.includes(edit.searchContent)) {
|
|
66
|
+
workingContent = workingContent.replace(edit.searchContent, edit.replaceContent);
|
|
67
|
+
replacements++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Phase 3 AGI Upgrade: Fuzzy Match Fallback
|
|
72
|
+
const lines = workingContent.split('\n');
|
|
73
|
+
const oldLines = edit.searchContent.split('\n');
|
|
74
|
+
let bestMatch = null;
|
|
75
|
+
let highestConfidence = 0;
|
|
76
|
+
|
|
77
|
+
for (let j = 0; j <= lines.length - oldLines.length; j++) {
|
|
78
|
+
const chunk = lines.slice(j, j + oldLines.length).join('\n');
|
|
79
|
+
const conf = calculateConfidence(edit.searchContent, chunk);
|
|
80
|
+
if (conf > highestConfidence) {
|
|
81
|
+
highestConfidence = conf;
|
|
82
|
+
bestMatch = chunk;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (highestConfidence >= 90 && bestMatch) {
|
|
87
|
+
workingContent = workingContent.replace(bestMatch, edit.replaceContent);
|
|
88
|
+
replacements++;
|
|
89
|
+
} else {
|
|
90
|
+
// Atomic Rollback
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: `ATOMIC ROLLBACK: Edit index ${i} failed. Exact match not found, and fuzzy match confidence was too low (${highestConfidence.toFixed(2)}% < 90%). Previous edits in this batch were rolled back.`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Commit the transaction
|
|
99
|
+
await fs.writeFile(target, workingContent, 'utf8');
|
|
100
|
+
return { success: true, output: `Transaction Success: ${replacements} replacement(s) safely applied to ${args.path}` };
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return { success: false, error: e.message };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// Phase 6 AGI Upgrade: Pure JS Levenshtein Distance for native fuzzy searching
|
|
6
|
+
function levenshteinDistance(a, b) {
|
|
7
|
+
const matrix = [];
|
|
8
|
+
for (let i = 0; i <= b.length; i++) {
|
|
9
|
+
matrix[i] = [i];
|
|
10
|
+
}
|
|
11
|
+
for (let j = 0; j <= a.length; j++) {
|
|
12
|
+
matrix[0][j] = j;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 1; i <= b.length; i++) {
|
|
15
|
+
for (let j = 1; j <= a.length; j++) {
|
|
16
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
17
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
18
|
+
} else {
|
|
19
|
+
matrix[i][j] = Math.min(
|
|
20
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
21
|
+
matrix[i][j - 1] + 1, // insertion
|
|
22
|
+
matrix[i - 1][j] + 1 // deletion
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return matrix[b.length][a.length];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isMatch(filename, keywords, extensions, fuzzyMatch) {
|
|
31
|
+
const lowerFilename = filename.toLowerCase();
|
|
32
|
+
|
|
33
|
+
if (extensions && extensions.length > 0) {
|
|
34
|
+
const extMatch = extensions.some(ext => lowerFilename.endsWith(ext.toLowerCase()));
|
|
35
|
+
if (!extMatch) return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!keywords || keywords.length === 0) return true;
|
|
39
|
+
|
|
40
|
+
for (const kw of keywords) {
|
|
41
|
+
const lowerKw = kw.toLowerCase();
|
|
42
|
+
if (lowerFilename.includes(lowerKw)) return true;
|
|
43
|
+
|
|
44
|
+
// Fuzzy matching for typos (only applied if keyword is >= 4 chars)
|
|
45
|
+
if (fuzzyMatch && lowerKw.length >= 4) {
|
|
46
|
+
for (let i = 0; i <= lowerFilename.length - lowerKw.length + 1; i++) {
|
|
47
|
+
const chunk = lowerFilename.substr(i, lowerKw.length + 1);
|
|
48
|
+
const distance = levenshteinDistance(chunk, lowerKw);
|
|
49
|
+
const maxDistance = Math.floor(lowerKw.length / 4);
|
|
50
|
+
if (distance <= maxDistance) return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const fsFind = {
|
|
58
|
+
schema: {
|
|
59
|
+
name: 'fs.find',
|
|
60
|
+
description: 'Blazing fast semantic and fuzzy file search by name. Non-blocking Async Indexer.',
|
|
61
|
+
parameters: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
path: { type: 'string', description: 'Root directory to start the search.' },
|
|
65
|
+
keywords: { type: 'array', items: { type: 'string' } },
|
|
66
|
+
extensions: { type: 'array', items: { type: 'string' } },
|
|
67
|
+
fuzzyMatch: { type: 'boolean', description: 'Set to true to enable typo tolerance.' }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async execute(args) {
|
|
73
|
+
try {
|
|
74
|
+
const targetDir = (args.path || process.cwd()).replace(/^~\//, os.homedir() + '/');
|
|
75
|
+
const keywords = args.keywords || [];
|
|
76
|
+
const extensions = args.extensions || [];
|
|
77
|
+
const fuzzyMatch = args.fuzzyMatch === true;
|
|
78
|
+
|
|
79
|
+
const EXCLUDE_DIRS = new Set(['node_modules', '.git', '.gradle', 'build', 'dist', '.idea', '.causyne_trash']);
|
|
80
|
+
|
|
81
|
+
const matches = [];
|
|
82
|
+
let scanned = 0;
|
|
83
|
+
const MAX_MATCHES = 50;
|
|
84
|
+
|
|
85
|
+
// Phase 6 AGI Upgrade: Asynchronous Non-Blocking Traversal to prevent Event Loop freezing
|
|
86
|
+
async function walkAsync(dir) {
|
|
87
|
+
if (matches.length >= MAX_MATCHES) return;
|
|
88
|
+
|
|
89
|
+
let dirents;
|
|
90
|
+
try {
|
|
91
|
+
dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return; // Ignore permission/missing errors gracefully
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const promises = [];
|
|
97
|
+
for (const dirent of dirents) {
|
|
98
|
+
if (matches.length >= MAX_MATCHES) break;
|
|
99
|
+
|
|
100
|
+
if (dirent.isDirectory()) {
|
|
101
|
+
if (!EXCLUDE_DIRS.has(dirent.name)) {
|
|
102
|
+
promises.push(walkAsync(path.join(dir, dirent.name)));
|
|
103
|
+
}
|
|
104
|
+
} else if (dirent.isFile()) {
|
|
105
|
+
scanned++;
|
|
106
|
+
if (isMatch(dirent.name, keywords, extensions, fuzzyMatch)) {
|
|
107
|
+
matches.push(path.join(dir, dirent.name));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await Promise.all(promises);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await walkAsync(targetDir);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
output: JSON.stringify({
|
|
119
|
+
scanned_files: scanned,
|
|
120
|
+
total_matches: matches.length,
|
|
121
|
+
matches: matches.length > 0 ? matches : "No files matched your semantic parameters."
|
|
122
|
+
}, null, 2)
|
|
123
|
+
};
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return { success: false, error: e.message };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// Phase 2 AGI Upgrade: Tree-Map Algorithm with Depth Limiting
|
|
6
|
+
async function generateTree(dirPath, currentDepth, maxDepth, prefix = '') {
|
|
7
|
+
if (currentDepth > maxDepth) return '';
|
|
8
|
+
|
|
9
|
+
let treeStr = '';
|
|
10
|
+
try {
|
|
11
|
+
const items = await fs.readdir(dirPath, { withFileTypes: true });
|
|
12
|
+
|
|
13
|
+
// Sort directories first
|
|
14
|
+
items.sort((a, b) => {
|
|
15
|
+
if (a.isDirectory() === b.isDirectory()) return a.name.localeCompare(b.name);
|
|
16
|
+
return a.isDirectory() ? -1 : 1;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < items.length; i++) {
|
|
20
|
+
const item = items[i];
|
|
21
|
+
// AGI Optimization: Aggressively ignore massive hidden folders
|
|
22
|
+
if (item.name === 'node_modules' || item.name === '.git' || item.name === '.gradle') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isLast = i === items.length - 1;
|
|
27
|
+
const marker = isLast ? '└── ' : '├── ';
|
|
28
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
29
|
+
|
|
30
|
+
let metadata = '';
|
|
31
|
+
if (!item.isDirectory()) {
|
|
32
|
+
try {
|
|
33
|
+
const stats = await fs.stat(path.join(dirPath, item.name));
|
|
34
|
+
const sizeKb = Math.round(stats.size / 1024);
|
|
35
|
+
metadata = ` (${sizeKb}KB)`;
|
|
36
|
+
} catch (e) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
treeStr += `${prefix}${marker}${item.name}${item.isDirectory() ? '/' : ''}${metadata}\n`;
|
|
40
|
+
|
|
41
|
+
if (item.isDirectory()) {
|
|
42
|
+
treeStr += await generateTree(path.join(dirPath, item.name), currentDepth + 1, maxDepth, newPrefix);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
treeStr += `${prefix}└── [Access Denied]\n`;
|
|
47
|
+
}
|
|
48
|
+
return treeStr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const fsList = {
|
|
52
|
+
schema: {
|
|
53
|
+
name: 'fs.list',
|
|
54
|
+
description: 'List directory contents as an interactive Tree-Map. Gives spatial awareness of a project layout.',
|
|
55
|
+
parameters: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
path: { type: 'string', description: 'Directory to list' },
|
|
59
|
+
depth: { type: 'number', description: 'Recursion depth (default 2)' }
|
|
60
|
+
},
|
|
61
|
+
required: ['path']
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async execute(args) {
|
|
65
|
+
try {
|
|
66
|
+
const target = args.path.replace(/^~\//, os.homedir() + '/');
|
|
67
|
+
const maxDepth = args.depth || 2;
|
|
68
|
+
|
|
69
|
+
const tree = await generateTree(target, 1, maxDepth);
|
|
70
|
+
const header = `📁 ${target}\n`;
|
|
71
|
+
|
|
72
|
+
return { success: true, output: header + (tree || ' (Empty or access denied)') };
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return { success: false, error: e.message };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'fs';
|
|
3
|
+
import { pipeline } from 'stream/promises';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
export const fsMove = {
|
|
7
|
+
schema: {
|
|
8
|
+
name: 'fs.move',
|
|
9
|
+
description: 'Move or rename a file/directory atomically. Safely handles cross-partition (EXDEV) moves without crashing.',
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
source: { type: 'string' },
|
|
14
|
+
destination: { type: 'string' },
|
|
15
|
+
overwrite: { type: 'boolean' }
|
|
16
|
+
},
|
|
17
|
+
required: ['source', 'destination']
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
async execute(args) {
|
|
21
|
+
try {
|
|
22
|
+
const src = args.source.replace(/^~\//, os.homedir() + '/');
|
|
23
|
+
const dest = args.destination.replace(/^~\//, os.homedir() + '/');
|
|
24
|
+
|
|
25
|
+
if (!args.overwrite) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(dest);
|
|
28
|
+
return { success: false, error: 'Destination already exists and overwrite is false.' };
|
|
29
|
+
} catch (e) { /* dest does not exist, safe */ }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Phase 4 AGI Upgrade: EXDEV Safe Rename
|
|
33
|
+
try {
|
|
34
|
+
await fs.rename(src, dest);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.code === 'EXDEV') {
|
|
37
|
+
// Cross-device link error (different partitions) - Use stream copy then unlink
|
|
38
|
+
const stat = await fs.stat(src);
|
|
39
|
+
if (stat.isDirectory()) {
|
|
40
|
+
return { success: false, error: "Cannot atomically move entire directories across partitions yet. Please compress, move, and extract." };
|
|
41
|
+
}
|
|
42
|
+
await pipeline(
|
|
43
|
+
createReadStream(src),
|
|
44
|
+
createWriteStream(dest)
|
|
45
|
+
);
|
|
46
|
+
await fs.unlink(src);
|
|
47
|
+
} else {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { success: true, output: `Atomically moved ${args.source} to ${args.destination}` };
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return { success: false, error: e.message };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
// Phase 3 AGI Upgrade: Fast Binary Detection
|
|
7
|
+
async function isBinary(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
const fd = await fs.promises.open(filePath, 'r');
|
|
10
|
+
const buffer = Buffer.alloc(1024);
|
|
11
|
+
const { bytesRead } = await fd.read(buffer, 0, 1024, 0);
|
|
12
|
+
await fd.close();
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
15
|
+
if (buffer[i] === 0) return true; // Null byte indicates binary
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const fsRead = {
|
|
24
|
+
schema: {
|
|
25
|
+
name: 'fs.read',
|
|
26
|
+
description: 'Read file contents intelligently. Automatically pages large files to protect Context Window.',
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
path: { type: 'string' },
|
|
31
|
+
startLine: { type: 'number' },
|
|
32
|
+
endLine: { type: 'number' }
|
|
33
|
+
},
|
|
34
|
+
required: ['path']
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
async execute(args) {
|
|
38
|
+
try {
|
|
39
|
+
const target = args.path.replace(/^~\//, os.homedir() + '/');
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(target)) {
|
|
42
|
+
return { success: false, error: "File does not exist." };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Phase 3 AGI Upgrade: Binary Rejection
|
|
46
|
+
if (await isBinary(target)) {
|
|
47
|
+
return { success: false, error: "Cannot read binary file as text. Use viewImage or OCR tools instead." };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const start = args.startLine ? Math.max(1, args.startLine) : 1;
|
|
51
|
+
let end = args.endLine ? args.endLine : start + 799;
|
|
52
|
+
|
|
53
|
+
// Phase 3 AGI Upgrade: Mandatory Paging Limit (800)
|
|
54
|
+
if (end - start + 1 > 800) {
|
|
55
|
+
end = start + 799;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let currentLine = 1;
|
|
59
|
+
let outputLines = [];
|
|
60
|
+
let totalLines = 0;
|
|
61
|
+
|
|
62
|
+
// Phase 3 AGI Upgrade: Stream (no full buffering)
|
|
63
|
+
const rl = readline.createInterface({
|
|
64
|
+
input: fs.createReadStream(target),
|
|
65
|
+
crlfDelay: Infinity
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
for await (const line of rl) {
|
|
69
|
+
totalLines++;
|
|
70
|
+
if (currentLine >= start && currentLine <= end) {
|
|
71
|
+
outputLines.push(line);
|
|
72
|
+
}
|
|
73
|
+
currentLine++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let finalOutput = outputLines.join('\n');
|
|
77
|
+
|
|
78
|
+
if (totalLines > end) {
|
|
79
|
+
finalOutput += `\n\n[WARNING: File truncated at line ${end} out of ${totalLines}. Use { startLine: ${end + 1}, endLine: ${end + 800} } to read more.]`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { success: true, output: finalOutput };
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return { success: false, error: e.message };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// Text search across a directory tree.
|
|
5
|
+
//
|
|
6
|
+
// The previous implementation had four problems that only show up on a real
|
|
7
|
+
// repository (finding D-13):
|
|
8
|
+
//
|
|
9
|
+
// 1. It recursed without tracking realpaths, so a symlink pointing at an
|
|
10
|
+
// ancestor sent it round forever.
|
|
11
|
+
// 2. It read every candidate file whole into memory with no size cap — one
|
|
12
|
+
|
|
13
|
+
// 3. It decided "binary" from a fixed list of nine extensions, so `.woff2`,
|
|
14
|
+
// `.mp4`, `.class`, `.pdf` and every unknown format were parsed as text.
|
|
15
|
+
// 4. It excluded exactly five directory names, ignoring `.gitignore`, so it
|
|
16
|
+
// spent most of its time inside build output the user never wanted.
|
|
17
|
+
//
|
|
18
|
+
// And when it found nothing it said "No matches found. (Semantic Search module
|
|
19
|
+
// pending activation)" — advertising a module that was two comment lines. That
|
|
20
|
+
// sentence is gone: the tool now reports only what it actually did.
|
|
21
|
+
|
|
22
|
+
const DEFAULT_EXCLUDES = [
|
|
23
|
+
'node_modules', '.git', '.hg', '.svn', 'dist', 'build', 'out', 'target',
|
|
24
|
+
'.gradle', '.idea', '.vscode', '.next', '.nuxt', '.cache', '__pycache__',
|
|
25
|
+
'venv', '.venv', 'vendor', 'Pods', 'DerivedData', 'coverage', '.terraform'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Above this, a file is treated as data rather than source. Large minified
|
|
29
|
+
// bundles and lockfiles are the common case and are rarely what a search is for.
|
|
30
|
+
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
31
|
+
|
|
32
|
+
// A NUL byte in the first block is the standard binary heuristic — the same one
|
|
33
|
+
// grep uses. It is content-based, so it works for formats nobody listed.
|
|
34
|
+
const SNIFF_BYTES = 8192;
|
|
35
|
+
|
|
36
|
+
const DEFAULT_MAX_RESULTS = 100;
|
|
37
|
+
|
|
38
|
+
/** Turn a .gitignore into a matcher. Deliberately simple: names and globs. */
|
|
39
|
+
async function loadIgnorePatterns(root) {
|
|
40
|
+
const patterns = [];
|
|
41
|
+
try {
|
|
42
|
+
const raw = await fs.readFile(path.join(root, '.gitignore'), 'utf8');
|
|
43
|
+
for (const line of raw.split('\n')) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) continue;
|
|
46
|
+
patterns.push(trimmed.replace(/^\/+|\/+$/g, ''));
|
|
47
|
+
}
|
|
48
|
+
} catch (e) { /* no .gitignore is the normal case outside a repo */ }
|
|
49
|
+
return patterns;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isIgnored(name, patterns) {
|
|
53
|
+
for (const pattern of patterns) {
|
|
54
|
+
if (pattern === name) return true;
|
|
55
|
+
// Only the two glob shapes that actually appear in most .gitignore files.
|
|
56
|
+
if (pattern.startsWith('*') && name.endsWith(pattern.slice(1))) return true;
|
|
57
|
+
if (pattern.endsWith('*') && name.startsWith(pattern.slice(0, -1))) return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Walk a tree, yielding file paths.
|
|
64
|
+
*
|
|
65
|
+
* `seen` holds the realpath of every directory already entered, which is what
|
|
66
|
+
* stops a symlink cycle: `a/link -> ..` resolves to a directory already in the
|
|
67
|
+
* set and is skipped rather than followed forever.
|
|
68
|
+
*/
|
|
69
|
+
async function* walk(dir, excludes, ignorePatterns, seen) {
|
|
70
|
+
let real;
|
|
71
|
+
try {
|
|
72
|
+
real = await fs.realpath(dir);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (seen.has(real)) return;
|
|
77
|
+
seen.add(real);
|
|
78
|
+
|
|
79
|
+
let entries;
|
|
80
|
+
try {
|
|
81
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return; // unreadable directory: skip it, do not fail the whole search
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (excludes.includes(entry.name) || isIgnored(entry.name, ignorePatterns)) continue;
|
|
88
|
+
const full = path.join(dir, entry.name);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
yield* walk(full, excludes, ignorePatterns, seen);
|
|
91
|
+
} else if (entry.isFile()) {
|
|
92
|
+
yield full;
|
|
93
|
+
} else if (entry.isSymbolicLink()) {
|
|
94
|
+
// Resolve once and classify; the `seen` set handles a directory cycle.
|
|
95
|
+
try {
|
|
96
|
+
const stat = await fs.stat(full);
|
|
97
|
+
if (stat.isDirectory()) yield* walk(full, excludes, ignorePatterns, seen);
|
|
98
|
+
else if (stat.isFile()) yield full;
|
|
99
|
+
} catch (e) { /* broken link */ }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** True when the first block contains a NUL byte. */
|
|
105
|
+
async function looksBinary(filePath) {
|
|
106
|
+
let handle;
|
|
107
|
+
try {
|
|
108
|
+
handle = await fs.open(filePath, 'r');
|
|
109
|
+
const buffer = Buffer.alloc(SNIFF_BYTES);
|
|
110
|
+
const { bytesRead } = await handle.read(buffer, 0, SNIFF_BYTES, 0);
|
|
111
|
+
return buffer.subarray(0, bytesRead).includes(0);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return true; // unreadable: treat as not searchable
|
|
114
|
+
} finally {
|
|
115
|
+
if (handle) await handle.close().catch(() => {});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const fsSearch = {
|
|
120
|
+
schema: {
|
|
121
|
+
name: 'fs.search',
|
|
122
|
+
description: 'Search for text across files in a directory tree. Skips ignored and build directories, binary files, and files larger than 2 MB.',
|
|
123
|
+
parameters: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
query: { type: 'string', description: 'Text or regular expression to find.' },
|
|
127
|
+
path: { type: 'string', description: 'Directory to search in. Defaults to the working directory.' },
|
|
128
|
+
isRegex: { type: 'boolean', description: 'Treat the query as a regular expression.' },
|
|
129
|
+
maxResults: { type: 'integer', description: `Stop after this many matching lines. Defaults to ${DEFAULT_MAX_RESULTS}.` }
|
|
130
|
+
},
|
|
131
|
+
required: ['query']
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
async execute(args) {
|
|
136
|
+
try {
|
|
137
|
+
const searchPath = args.path || process.cwd();
|
|
138
|
+
const maxResults = Number.isInteger(args.maxResults) && args.maxResults > 0
|
|
139
|
+
? Math.min(args.maxResults, 1000)
|
|
140
|
+
: DEFAULT_MAX_RESULTS;
|
|
141
|
+
|
|
142
|
+
let regex;
|
|
143
|
+
try {
|
|
144
|
+
regex = args.isRegex
|
|
145
|
+
? new RegExp(args.query)
|
|
146
|
+
: new RegExp(args.query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return { success: false, error: 'Invalid regular expression: ' + e.message };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const ignorePatterns = await loadIgnorePatterns(searchPath);
|
|
152
|
+
const results = [];
|
|
153
|
+
let filesScanned = 0;
|
|
154
|
+
let filesSkipped = 0;
|
|
155
|
+
let truncated = false;
|
|
156
|
+
|
|
157
|
+
for await (const file of walk(searchPath, DEFAULT_EXCLUDES, ignorePatterns, new Set())) {
|
|
158
|
+
if (results.length >= maxResults) { truncated = true; break; }
|
|
159
|
+
|
|
160
|
+
let stat;
|
|
161
|
+
try {
|
|
162
|
+
stat = await fs.stat(file);
|
|
163
|
+
} catch (e) { continue; }
|
|
164
|
+
if (stat.size > MAX_FILE_BYTES) { filesSkipped++; continue; }
|
|
165
|
+
if (await looksBinary(file)) { filesSkipped++; continue; }
|
|
166
|
+
|
|
167
|
+
let content;
|
|
168
|
+
try {
|
|
169
|
+
content = await fs.readFile(file, 'utf8');
|
|
170
|
+
} catch (e) { continue; }
|
|
171
|
+
filesScanned++;
|
|
172
|
+
|
|
173
|
+
const lines = content.split('\n');
|
|
174
|
+
for (let i = 0; i < lines.length; i++) {
|
|
175
|
+
if (!regex.test(lines[i])) continue;
|
|
176
|
+
results.push(`${file}:${i + 1}:${lines[i].trim().slice(0, 300)}`);
|
|
177
|
+
if (results.length >= maxResults) { truncated = true; break; }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (results.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
output: `No matches found. Scanned ${filesScanned} files under ${searchPath}` +
|
|
185
|
+
(filesSkipped > 0 ? `; skipped ${filesSkipped} binary or oversized files.` : '.')
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
output: results.join('\n') +
|
|
192
|
+
(truncated ? `\n\n[stopped at ${maxResults} matches — narrow the query or raise maxResults]` : '')
|
|
193
|
+
};
|
|
194
|
+
} catch (e) {
|
|
195
|
+
return { success: false, error: e.message };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
export const fsWrite = {
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'fs.write',
|
|
8
|
+
description: 'Create or overwrite a file with content using Zero-Corruption Atomic Swap. Guarantees safe file writing.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
path: { type: 'string' },
|
|
13
|
+
content: { type: 'string' },
|
|
14
|
+
createDirectories: { type: 'boolean' }
|
|
15
|
+
},
|
|
16
|
+
required: ['path', 'content']
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
async execute(args) {
|
|
20
|
+
try {
|
|
21
|
+
const target = args.path.replace(/^~\//, os.homedir() + '/');
|
|
22
|
+
|
|
23
|
+
if (args.createDirectories !== false) {
|
|
24
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Phase 4 AGI Upgrade: Atomic File Swap
|
|
28
|
+
// Prevent zero-byte file corruption if Node crashes during a huge write.
|
|
29
|
+
const tempPath = `${target}.causyne_tmp_${Date.now()}`;
|
|
30
|
+
|
|
31
|
+
await fs.writeFile(tempPath, args.content, 'utf8');
|
|
32
|
+
|
|
33
|
+
// Atomic rename (POSIX standard guarantee)
|
|
34
|
+
await fs.rename(tempPath, target);
|
|
35
|
+
|
|
36
|
+
return { success: true, output: `Atomically wrote ${Buffer.byteLength(args.content, 'utf8')} bytes to ${args.path}` };
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { success: false, error: `Atomic Write Failed: ${e.message}` };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|