@vibe-lang/runtime 0.2.5
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/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import * as AST from '../ast';
|
|
2
|
+
import type { RuntimeState, AIOperation, AIInteraction, StackFrame, FrameEntry, PromptToolCall, ToolCallRecord, VibeValue } from './types';
|
|
3
|
+
import { createVibeValue } from './types';
|
|
4
|
+
import type { ToolRoundResult } from './ai/tool-loop';
|
|
5
|
+
|
|
6
|
+
// Options for creating initial state
|
|
7
|
+
export interface InitialStateOptions {
|
|
8
|
+
logAiInteractions?: boolean;
|
|
9
|
+
rootDir?: string; // Root directory for file operation sandboxing (defaults to cwd)
|
|
10
|
+
maxParallel?: number; // Max concurrent async operations (default: 4)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Create initial runtime state from a program AST
|
|
14
|
+
export function createInitialState(
|
|
15
|
+
program: AST.Program,
|
|
16
|
+
options?: InitialStateOptions
|
|
17
|
+
): RuntimeState {
|
|
18
|
+
// Collect function declarations
|
|
19
|
+
const functions: Record<string, AST.FunctionDeclaration> = {};
|
|
20
|
+
for (const stmt of program.body) {
|
|
21
|
+
if (stmt.type === 'FunctionDeclaration') {
|
|
22
|
+
functions[stmt.name] = stmt;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create initial instruction stack with all top-level statements
|
|
27
|
+
// We pop from the front, so first statement should be at index 0
|
|
28
|
+
const instructionStack = program.body
|
|
29
|
+
.map((stmt) => ({ op: 'exec_statement' as const, stmt, location: stmt.location }));
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
status: 'running',
|
|
33
|
+
program,
|
|
34
|
+
functions,
|
|
35
|
+
tsModules: {},
|
|
36
|
+
vibeModules: {},
|
|
37
|
+
importedNames: {},
|
|
38
|
+
callStack: [createFrame('<entry>')],
|
|
39
|
+
instructionStack,
|
|
40
|
+
valueStack: [],
|
|
41
|
+
lastResult: null,
|
|
42
|
+
lastResultSource: null,
|
|
43
|
+
aiHistory: [],
|
|
44
|
+
executionLog: [],
|
|
45
|
+
logAiInteractions: options?.logAiInteractions ?? false,
|
|
46
|
+
aiInteractions: [],
|
|
47
|
+
localContext: [],
|
|
48
|
+
globalContext: [],
|
|
49
|
+
pendingAI: null,
|
|
50
|
+
pendingCompress: null,
|
|
51
|
+
pendingTS: null,
|
|
52
|
+
pendingImportedTsCall: null,
|
|
53
|
+
pendingToolCall: null,
|
|
54
|
+
pendingDestructuring: null,
|
|
55
|
+
expectedFields: null,
|
|
56
|
+
lastUsedModel: null,
|
|
57
|
+
rootDir: options?.rootDir ?? process.cwd(),
|
|
58
|
+
error: null,
|
|
59
|
+
errorObject: null,
|
|
60
|
+
|
|
61
|
+
// Async execution tracking
|
|
62
|
+
asyncOperations: new Map(),
|
|
63
|
+
pendingAsyncIds: new Set(),
|
|
64
|
+
asyncVarToOpId: new Map(),
|
|
65
|
+
asyncWaves: [],
|
|
66
|
+
currentWaveId: 0,
|
|
67
|
+
maxParallel: options?.maxParallel ?? 4,
|
|
68
|
+
nextAsyncId: 1,
|
|
69
|
+
awaitingAsyncIds: [],
|
|
70
|
+
|
|
71
|
+
// Async declaration context (set when inside async let/const)
|
|
72
|
+
currentAsyncVarName: null,
|
|
73
|
+
currentAsyncIsConst: false,
|
|
74
|
+
currentAsyncType: null,
|
|
75
|
+
currentAsyncIsPrivate: false,
|
|
76
|
+
currentAsyncIsDestructure: false,
|
|
77
|
+
currentAsyncIsFireAndForget: false,
|
|
78
|
+
|
|
79
|
+
// Async function isolation
|
|
80
|
+
isInAsyncIsolation: false,
|
|
81
|
+
|
|
82
|
+
// Scheduled async operations
|
|
83
|
+
pendingAsyncStarts: [],
|
|
84
|
+
|
|
85
|
+
// String interpolation context (for prompt strings in do/vibe)
|
|
86
|
+
inPromptContext: false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create a new stack frame
|
|
91
|
+
export function createFrame(name: string, parentFrameIndex: number | null = null): StackFrame {
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
locals: {},
|
|
95
|
+
parentFrameIndex,
|
|
96
|
+
orderedEntries: [],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Resume execution after AI response
|
|
101
|
+
export function resumeWithAIResponse(
|
|
102
|
+
state: RuntimeState,
|
|
103
|
+
response: unknown,
|
|
104
|
+
interaction?: AIInteraction,
|
|
105
|
+
toolRounds?: ToolRoundResult[]
|
|
106
|
+
): RuntimeState {
|
|
107
|
+
if (state.status !== 'awaiting_ai' || !state.pendingAI) {
|
|
108
|
+
throw new Error('Cannot resume: not awaiting AI response');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const responseStr = typeof response === 'string' ? response : JSON.stringify(response);
|
|
112
|
+
const pendingAI = state.pendingAI;
|
|
113
|
+
|
|
114
|
+
const aiOp: AIOperation = {
|
|
115
|
+
type: pendingAI.type,
|
|
116
|
+
prompt: pendingAI.prompt,
|
|
117
|
+
response: responseStr,
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Add interaction to log if provided and logging is enabled
|
|
122
|
+
const aiInteractions = state.logAiInteractions && interaction
|
|
123
|
+
? [...state.aiInteractions, interaction]
|
|
124
|
+
: state.aiInteractions;
|
|
125
|
+
|
|
126
|
+
// Build ordered entries with prompt containing embedded tool calls
|
|
127
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
128
|
+
|
|
129
|
+
// Convert tool rounds to PromptToolCall format (embedded in prompt entry for context)
|
|
130
|
+
const promptToolCalls: PromptToolCall[] = (toolRounds ?? []).flatMap((round) =>
|
|
131
|
+
round.toolCalls.map((call, index) => {
|
|
132
|
+
const result = round.results[index];
|
|
133
|
+
const toolCall: PromptToolCall = {
|
|
134
|
+
toolName: call.toolName,
|
|
135
|
+
args: call.args,
|
|
136
|
+
};
|
|
137
|
+
if (result?.error) {
|
|
138
|
+
toolCall.error = result.error;
|
|
139
|
+
} else if (result?.result !== undefined) {
|
|
140
|
+
toolCall.result = result.result;
|
|
141
|
+
}
|
|
142
|
+
return toolCall;
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Build ToolCallRecord array for VibeValue (all rounds accumulated)
|
|
147
|
+
const toolCallRecords: ToolCallRecord[] = (toolRounds ?? []).flatMap((round) =>
|
|
148
|
+
round.toolCalls.map((call, index) => {
|
|
149
|
+
const result = round.results[index];
|
|
150
|
+
const error = result?.error;
|
|
151
|
+
return {
|
|
152
|
+
toolName: call.toolName,
|
|
153
|
+
args: call.args,
|
|
154
|
+
result: error ? null : String(result?.result ?? ''),
|
|
155
|
+
err: !!error,
|
|
156
|
+
errDetails: error ? { message: error } : null,
|
|
157
|
+
duration: result?.duration ?? 0,
|
|
158
|
+
};
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Create VibeValue with final response value and all tool calls
|
|
163
|
+
const aiResultValue: VibeValue = createVibeValue(response, {
|
|
164
|
+
source: 'ai',
|
|
165
|
+
toolCalls: toolCallRecords,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Create prompt entry with embedded tool calls (order: prompt → tools → response)
|
|
169
|
+
// Note: promptEntry.response stores raw value for context display
|
|
170
|
+
const promptEntry: FrameEntry = {
|
|
171
|
+
kind: 'prompt' as const,
|
|
172
|
+
aiType: pendingAI.type,
|
|
173
|
+
prompt: pendingAI.prompt,
|
|
174
|
+
toolCalls: promptToolCalls.length > 0 ? promptToolCalls : undefined,
|
|
175
|
+
response, // Include response for context history
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const newOrderedEntries = [
|
|
179
|
+
...frame.orderedEntries,
|
|
180
|
+
promptEntry,
|
|
181
|
+
];
|
|
182
|
+
const updatedCallStack = [
|
|
183
|
+
...state.callStack.slice(0, -1),
|
|
184
|
+
{ ...frame, orderedEntries: newOrderedEntries },
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
...state,
|
|
189
|
+
status: 'running',
|
|
190
|
+
lastResult: aiResultValue, // Store VibeValue with response and toolCalls
|
|
191
|
+
lastResultSource: 'ai',
|
|
192
|
+
callStack: updatedCallStack,
|
|
193
|
+
aiHistory: [...state.aiHistory, aiOp],
|
|
194
|
+
aiInteractions,
|
|
195
|
+
pendingAI: null,
|
|
196
|
+
executionLog: [
|
|
197
|
+
...state.executionLog,
|
|
198
|
+
{
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
instructionType: `ai_${pendingAI.type}_response`,
|
|
201
|
+
details: { prompt: pendingAI.prompt, toolRounds: toolRounds?.length ?? 0 },
|
|
202
|
+
result: responseStr,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Resume execution after user input
|
|
209
|
+
export function resumeWithUserInput(state: RuntimeState, input: string): RuntimeState {
|
|
210
|
+
if (state.status !== 'awaiting_user' || !state.pendingAI) {
|
|
211
|
+
throw new Error('Cannot resume: not awaiting user input');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const pendingAI = state.pendingAI;
|
|
215
|
+
|
|
216
|
+
const aiOp: AIOperation = {
|
|
217
|
+
type: 'ask',
|
|
218
|
+
prompt: pendingAI.prompt,
|
|
219
|
+
response: input,
|
|
220
|
+
timestamp: Date.now(),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Add the completed prompt to orderedEntries in current frame (with response for history)
|
|
224
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
225
|
+
const newOrderedEntries = [
|
|
226
|
+
...frame.orderedEntries,
|
|
227
|
+
{
|
|
228
|
+
kind: 'prompt' as const,
|
|
229
|
+
aiType: 'ask' as const,
|
|
230
|
+
prompt: pendingAI.prompt,
|
|
231
|
+
response: input, // Include user input for context history
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
const updatedCallStack = [
|
|
235
|
+
...state.callStack.slice(0, -1),
|
|
236
|
+
{ ...frame, orderedEntries: newOrderedEntries },
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
...state,
|
|
241
|
+
status: 'running',
|
|
242
|
+
lastResult: input,
|
|
243
|
+
lastResultSource: 'user',
|
|
244
|
+
callStack: updatedCallStack,
|
|
245
|
+
aiHistory: [...state.aiHistory, aiOp],
|
|
246
|
+
pendingAI: null,
|
|
247
|
+
executionLog: [
|
|
248
|
+
...state.executionLog,
|
|
249
|
+
{
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
instructionType: 'user_input_response',
|
|
252
|
+
details: { prompt: pendingAI.prompt },
|
|
253
|
+
result: input,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Resume execution after TypeScript evaluation (inline ts block)
|
|
260
|
+
export function resumeWithTsResult(state: RuntimeState, result: unknown): RuntimeState {
|
|
261
|
+
if (state.status !== 'awaiting_ts' || !state.pendingTS) {
|
|
262
|
+
throw new Error('Cannot resume: not awaiting TypeScript result');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Normalize undefined to null for Vibe's single null concept
|
|
266
|
+
const normalizedResult = result === undefined ? null : result;
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
...state,
|
|
270
|
+
status: 'running',
|
|
271
|
+
lastResult: normalizedResult,
|
|
272
|
+
pendingTS: null,
|
|
273
|
+
executionLog: [
|
|
274
|
+
...state.executionLog,
|
|
275
|
+
{
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
instructionType: 'ts_eval_result',
|
|
278
|
+
details: { params: state.pendingTS.params },
|
|
279
|
+
result: normalizedResult,
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Resume execution after imported TS function call
|
|
286
|
+
export function resumeWithImportedTsResult(state: RuntimeState, result: unknown): RuntimeState {
|
|
287
|
+
if (state.status !== 'awaiting_ts' || !state.pendingImportedTsCall) {
|
|
288
|
+
throw new Error('Cannot resume: not awaiting imported TS function result');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Normalize undefined to null for Vibe's single null concept
|
|
292
|
+
const normalizedResult = result === undefined ? null : result;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
...state,
|
|
296
|
+
status: 'running',
|
|
297
|
+
lastResult: normalizedResult,
|
|
298
|
+
pendingImportedTsCall: null,
|
|
299
|
+
executionLog: [
|
|
300
|
+
...state.executionLog,
|
|
301
|
+
{
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
instructionType: 'imported_ts_call_result',
|
|
304
|
+
details: { funcName: state.pendingImportedTsCall.funcName },
|
|
305
|
+
result: normalizedResult,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Resume execution after compress AI summarization
|
|
312
|
+
export function resumeWithCompressResult(state: RuntimeState, summary: string): RuntimeState {
|
|
313
|
+
if (state.status !== 'awaiting_compress' || !state.pendingCompress) {
|
|
314
|
+
throw new Error('Cannot resume: not awaiting compress result');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const { entryIndex, scopeType, label } = state.pendingCompress;
|
|
318
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
319
|
+
|
|
320
|
+
// Replace loop entries with summary, keeping scope markers
|
|
321
|
+
const newOrderedEntries: FrameEntry[] = [
|
|
322
|
+
...frame.orderedEntries.slice(0, entryIndex),
|
|
323
|
+
{ kind: 'scope-enter', scopeType, label },
|
|
324
|
+
{ kind: 'summary', text: summary },
|
|
325
|
+
{ kind: 'scope-exit', scopeType, label },
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
...state,
|
|
330
|
+
status: 'running',
|
|
331
|
+
pendingCompress: null,
|
|
332
|
+
callStack: [
|
|
333
|
+
...state.callStack.slice(0, -1),
|
|
334
|
+
{ ...frame, orderedEntries: newOrderedEntries },
|
|
335
|
+
],
|
|
336
|
+
executionLog: [
|
|
337
|
+
...state.executionLog,
|
|
338
|
+
{
|
|
339
|
+
timestamp: Date.now(),
|
|
340
|
+
instructionType: 'compress_result',
|
|
341
|
+
details: { scopeType, label, prompt: state.pendingCompress.prompt },
|
|
342
|
+
result: summary,
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Resume execution after tool call
|
|
349
|
+
export function resumeWithToolResult(
|
|
350
|
+
state: RuntimeState,
|
|
351
|
+
result: unknown,
|
|
352
|
+
error?: string
|
|
353
|
+
): RuntimeState {
|
|
354
|
+
if (state.status !== 'awaiting_tool' || !state.pendingToolCall) {
|
|
355
|
+
throw new Error('Cannot resume: not awaiting tool result');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const pendingTool = state.pendingToolCall;
|
|
359
|
+
const finalResult = error ? { error } : result;
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
...state,
|
|
363
|
+
status: 'running',
|
|
364
|
+
lastResult: finalResult,
|
|
365
|
+
pendingToolCall: null,
|
|
366
|
+
executionLog: [
|
|
367
|
+
...state.executionLog,
|
|
368
|
+
{
|
|
369
|
+
timestamp: Date.now(),
|
|
370
|
+
instructionType: 'tool_call_result',
|
|
371
|
+
details: {
|
|
372
|
+
toolName: pendingTool.toolName,
|
|
373
|
+
hasError: !!error,
|
|
374
|
+
},
|
|
375
|
+
result: finalResult,
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Resume execution after async operations complete
|
|
382
|
+
export function resumeWithAsyncResults(
|
|
383
|
+
state: RuntimeState,
|
|
384
|
+
results: Map<string, VibeValue>
|
|
385
|
+
): RuntimeState {
|
|
386
|
+
if (state.status !== 'awaiting_async') {
|
|
387
|
+
throw new Error('Cannot resume: not awaiting async results');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Update variables with their async results
|
|
391
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
392
|
+
const newLocals = { ...frame.locals };
|
|
393
|
+
|
|
394
|
+
// Track the last result for operations that need it (e.g., destructuring)
|
|
395
|
+
let lastResult: VibeValue | null = null;
|
|
396
|
+
|
|
397
|
+
for (const [opId, result] of results) {
|
|
398
|
+
const operation = state.asyncOperations.get(opId);
|
|
399
|
+
if (operation?.variableName) {
|
|
400
|
+
// Preserve existing variable properties (isConst, isPrivate, typeAnnotation)
|
|
401
|
+
// when updating with the async result
|
|
402
|
+
const existingVar = newLocals[operation.variableName];
|
|
403
|
+
newLocals[operation.variableName] = {
|
|
404
|
+
...result,
|
|
405
|
+
isConst: existingVar?.isConst ?? result.isConst,
|
|
406
|
+
isPrivate: existingVar?.isPrivate ?? result.isPrivate,
|
|
407
|
+
typeAnnotation: existingVar?.typeAnnotation ?? result.typeAnnotation,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
// Mark operation as no longer pending
|
|
411
|
+
state.pendingAsyncIds.delete(opId);
|
|
412
|
+
// Keep track of the result (for destructuring and other uses)
|
|
413
|
+
lastResult = result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const updatedCallStack = [
|
|
417
|
+
...state.callStack.slice(0, -1),
|
|
418
|
+
{ ...frame, locals: newLocals },
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
...state,
|
|
423
|
+
status: 'running',
|
|
424
|
+
// Set lastResult so instructions like destructure_assign can use it
|
|
425
|
+
lastResult: lastResult ?? state.lastResult,
|
|
426
|
+
callStack: updatedCallStack,
|
|
427
|
+
awaitingAsyncIds: [],
|
|
428
|
+
executionLog: [
|
|
429
|
+
...state.executionLog,
|
|
430
|
+
{
|
|
431
|
+
timestamp: Date.now(),
|
|
432
|
+
instructionType: 'async_results',
|
|
433
|
+
details: { operationCount: results.size },
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Pause execution manually
|
|
440
|
+
export function pauseExecution(state: RuntimeState): RuntimeState {
|
|
441
|
+
if (state.status !== 'running') {
|
|
442
|
+
return state;
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
...state,
|
|
446
|
+
status: 'paused',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Resume from manual pause
|
|
451
|
+
export function resumeExecution(state: RuntimeState): RuntimeState {
|
|
452
|
+
if (state.status !== 'paused') {
|
|
453
|
+
return state;
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
...state,
|
|
457
|
+
status: 'running',
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Get current frame
|
|
462
|
+
export function currentFrame(state: RuntimeState): StackFrame {
|
|
463
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
464
|
+
if (!frame) {
|
|
465
|
+
throw new Error('No active stack frame');
|
|
466
|
+
}
|
|
467
|
+
return frame;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Get variable value (walks scope chain)
|
|
471
|
+
export function getVariable(state: RuntimeState, name: string): unknown {
|
|
472
|
+
// Walk the scope chain
|
|
473
|
+
let frameIndex: number | null = state.callStack.length - 1;
|
|
474
|
+
while (frameIndex !== null && frameIndex >= 0) {
|
|
475
|
+
const frame: StackFrame = state.callStack[frameIndex];
|
|
476
|
+
const variable = frame.locals[name];
|
|
477
|
+
if (variable) {
|
|
478
|
+
return variable.value;
|
|
479
|
+
}
|
|
480
|
+
frameIndex = frame.parentFrameIndex;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Check if it's a function
|
|
484
|
+
if (state.functions[name]) {
|
|
485
|
+
return { __vibeFunction: true, name };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
throw new Error(`ReferenceError: '${name}' is not defined`);
|
|
489
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Core system functions - automatically available in all Vibe modules
|
|
2
|
+
// These functions are injected into scope without requiring an import.
|
|
3
|
+
// They CANNOT be imported via "system" or any other module path.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Print a message to the console.
|
|
7
|
+
* @param message - The message to print
|
|
8
|
+
*/
|
|
9
|
+
export function print(message: unknown): void {
|
|
10
|
+
console.log(message);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get an environment variable value.
|
|
15
|
+
* @param name - The environment variable name
|
|
16
|
+
* @param defaultValue - Default value if not set (defaults to empty string)
|
|
17
|
+
* @returns The environment variable value or default
|
|
18
|
+
*/
|
|
19
|
+
export function env(name: string, defaultValue: string = ''): string {
|
|
20
|
+
return process.env[name] ?? defaultValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Registry of all core functions
|
|
24
|
+
// These are checked during identifier resolution before throwing "not defined" error
|
|
25
|
+
export const coreFunctions: Record<string, (...args: unknown[]) => unknown> = {
|
|
26
|
+
print,
|
|
27
|
+
env,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Set of core function names for quick lookup
|
|
31
|
+
export const coreFunctionNames = new Set(Object.keys(coreFunctions));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a name is a core function
|
|
35
|
+
*/
|
|
36
|
+
export function isCoreFunction(name: string): boolean {
|
|
37
|
+
return coreFunctionNames.has(name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get a core function by name
|
|
42
|
+
*/
|
|
43
|
+
export function getCoreFunction(name: string): ((...args: unknown[]) => unknown) | undefined {
|
|
44
|
+
return coreFunctions[name];
|
|
45
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Tests for directory tools: mkdir, listDir, dirExists
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { mkdir, listDir, dirExists } from './tools';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const TEST_DIR = path.join(__dirname, '.test-workspace-directory');
|
|
9
|
+
|
|
10
|
+
describe('mkdir tool', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
17
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('creates single directory', async () => {
|
|
22
|
+
const dirPath = path.join(TEST_DIR, 'newdir');
|
|
23
|
+
|
|
24
|
+
const result = await mkdir.executor({ path: dirPath }, { rootDir: TEST_DIR });
|
|
25
|
+
|
|
26
|
+
expect(result).toBe(true);
|
|
27
|
+
expect(fs.existsSync(dirPath)).toBe(true);
|
|
28
|
+
expect(fs.statSync(dirPath).isDirectory()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('creates nested directories with recursive: true', async () => {
|
|
32
|
+
const dirPath = path.join(TEST_DIR, 'a', 'b', 'c');
|
|
33
|
+
|
|
34
|
+
const result = await mkdir.executor({ path: dirPath, recursive: true }, { rootDir: TEST_DIR });
|
|
35
|
+
|
|
36
|
+
expect(result).toBe(true);
|
|
37
|
+
expect(fs.existsSync(dirPath)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('fails without recursive when parent does not exist', async () => {
|
|
41
|
+
const dirPath = path.join(TEST_DIR, 'parent', 'child');
|
|
42
|
+
|
|
43
|
+
await expect(mkdir.executor({ path: dirPath }, { rootDir: TEST_DIR })).rejects.toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('succeeds silently if directory already exists with recursive', async () => {
|
|
47
|
+
const dirPath = path.join(TEST_DIR, 'existing');
|
|
48
|
+
fs.mkdirSync(dirPath);
|
|
49
|
+
|
|
50
|
+
const result = await mkdir.executor({ path: dirPath, recursive: true }, { rootDir: TEST_DIR });
|
|
51
|
+
|
|
52
|
+
expect(result).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('listDir tool', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
63
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('lists files in directory', async () => {
|
|
68
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file1.txt'), '');
|
|
69
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file2.txt'), '');
|
|
70
|
+
|
|
71
|
+
const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
|
|
72
|
+
|
|
73
|
+
expect(result).toHaveLength(2);
|
|
74
|
+
expect(result).toContain('file1.txt');
|
|
75
|
+
expect(result).toContain('file2.txt');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('lists subdirectories', async () => {
|
|
79
|
+
fs.mkdirSync(path.join(TEST_DIR, 'subdir1'));
|
|
80
|
+
fs.mkdirSync(path.join(TEST_DIR, 'subdir2'));
|
|
81
|
+
fs.writeFileSync(path.join(TEST_DIR, 'file.txt'), '');
|
|
82
|
+
|
|
83
|
+
const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
|
|
84
|
+
|
|
85
|
+
expect(result).toHaveLength(3);
|
|
86
|
+
expect(result).toContain('subdir1');
|
|
87
|
+
expect(result).toContain('subdir2');
|
|
88
|
+
expect(result).toContain('file.txt');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('returns empty array for empty directory', async () => {
|
|
92
|
+
const emptyDir = path.join(TEST_DIR, 'empty');
|
|
93
|
+
fs.mkdirSync(emptyDir);
|
|
94
|
+
|
|
95
|
+
const result = (await listDir.executor({ path: emptyDir }, { rootDir: TEST_DIR })) as string[];
|
|
96
|
+
|
|
97
|
+
expect(result).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('error: directory not found', async () => {
|
|
101
|
+
const nonExistent = path.join(TEST_DIR, 'nonexistent');
|
|
102
|
+
|
|
103
|
+
await expect(listDir.executor({ path: nonExistent }, { rootDir: TEST_DIR })).rejects.toThrow();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('does not list nested contents (shallow)', async () => {
|
|
107
|
+
fs.mkdirSync(path.join(TEST_DIR, 'subdir'));
|
|
108
|
+
fs.writeFileSync(path.join(TEST_DIR, 'subdir', 'nested.txt'), '');
|
|
109
|
+
fs.writeFileSync(path.join(TEST_DIR, 'root.txt'), '');
|
|
110
|
+
|
|
111
|
+
const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
|
|
112
|
+
|
|
113
|
+
expect(result).toHaveLength(2);
|
|
114
|
+
expect(result).toContain('subdir');
|
|
115
|
+
expect(result).toContain('root.txt');
|
|
116
|
+
expect(result).not.toContain('nested.txt');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('dirExists tool', () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
127
|
+
fs.rmSync(TEST_DIR, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('returns true for existing directory', async () => {
|
|
132
|
+
const dirPath = path.join(TEST_DIR, 'existing');
|
|
133
|
+
fs.mkdirSync(dirPath);
|
|
134
|
+
|
|
135
|
+
const result = await dirExists.executor({ path: dirPath }, { rootDir: TEST_DIR });
|
|
136
|
+
|
|
137
|
+
expect(result).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('returns false for non-existent path', async () => {
|
|
141
|
+
const dirPath = path.join(TEST_DIR, 'nonexistent');
|
|
142
|
+
|
|
143
|
+
const result = await dirExists.executor({ path: dirPath }, { rootDir: TEST_DIR });
|
|
144
|
+
|
|
145
|
+
expect(result).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('returns false for file (not directory)', async () => {
|
|
149
|
+
const filePath = path.join(TEST_DIR, 'file.txt');
|
|
150
|
+
fs.writeFileSync(filePath, 'content');
|
|
151
|
+
|
|
152
|
+
const result = await dirExists.executor({ path: filePath }, { rootDir: TEST_DIR });
|
|
153
|
+
|
|
154
|
+
expect(result).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|