functionalscript 0.39.0 → 0.41.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/README.md +1 -1
- package/fjs/bnf/descent/module.f.d.ts +43 -2
- package/fjs/bnf/descent/module.f.js +37 -12
- package/fjs/bnf/descent/proof.f.d.ts +1 -0
- package/fjs/bnf/descent/proof.f.js +73 -32
- package/fjs/bnf/ll1/module.f.js +2 -2
- package/fjs/cas/evo/module.f.d.ts +28 -8
- package/fjs/cas/evo/module.f.js +43 -10
- package/fjs/cas/evo/proof.f.d.ts +4 -0
- package/fjs/cas/evo/proof.f.js +82 -1
- package/fjs/ci/config/module.f.d.ts +10 -7
- package/fjs/ci/config/module.f.js +23 -8
- package/fjs/ci/module.f.js +12 -5
- package/fjs/ci/nix/module.f.d.ts +61 -0
- package/fjs/ci/nix/module.f.js +92 -0
- package/fjs/ci/nix/proof.f.d.ts +23 -0
- package/fjs/ci/nix/proof.f.js +109 -0
- package/fjs/ci/node/module.f.d.ts +23 -1
- package/fjs/ci/node/module.f.js +47 -3
- package/fjs/ci/node/proof.f.d.ts +3 -0
- package/fjs/ci/node/proof.f.js +17 -0
- package/fjs/ci/proof.f.d.ts +2 -0
- package/fjs/ci/proof.f.js +47 -9
- package/fjs/dev/module.f.d.ts +1 -0
- package/fjs/dev/module.f.js +13 -2
- package/fjs/dev/update/module.f.d.ts +11 -0
- package/fjs/dev/update/module.f.js +20 -0
- package/fjs/dev/update/proof.f.d.ts +7 -0
- package/fjs/dev/update/proof.f.js +35 -0
- package/fjs/djs/ast/module.f.d.ts +47 -0
- package/fjs/djs/ast/module.f.js +9 -0
- package/fjs/djs/tokenizer/module.f.js +2 -2
- package/fjs/djs/tokenizer/proof.f.d.ts +1 -0
- package/fjs/djs/tokenizer/proof.f.js +70 -16
- package/fjs/effects/module.f.d.ts +20 -0
- package/fjs/effects/module.f.js +25 -1
- package/fjs/effects/node/module.d.ts +3 -3
- package/fjs/effects/node/module.f.d.ts +17 -7
- package/fjs/effects/node/module.f.js +22 -0
- package/fjs/effects/node/module.js +12 -12
- package/fjs/effects/node/proof.f.d.ts +1 -0
- package/fjs/effects/node/proof.f.js +14 -1
- package/fjs/effects/node/virtual/module.f.js +1 -1
- package/fjs/effects/proof.f.d.ts +5 -0
- package/fjs/effects/proof.f.js +22 -0
- package/fjs/emergent_testing/all.test.js +2 -1
- package/fjs/emergent_testing/module.f.d.ts +3 -3
- package/fjs/emergent_testing/module.f.js +8 -10
- package/fjs/emergent_testing/proof.f.js +2 -2
- package/fjs/emergent_testing/scenarios/thenable.pass.js +1 -1
- package/fjs/fsc/module.f.js +4 -4
- package/fjs/fsm/module.f.js +1 -1
- package/fjs/js/tokenizer/module.f.d.ts +1 -0
- package/fjs/js/tokenizer/module.f.js +13 -6
- package/fjs/{cas/mcp → mcp/cas}/module.f.d.ts +3 -25
- package/fjs/{cas/mcp → mcp/cas}/module.f.js +18 -58
- package/fjs/mcp/evo/module.f.d.ts +32 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/module.f.js +19 -14
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +1 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +23 -7
- package/fjs/mcp/module.f.d.ts +54 -237
- package/fjs/mcp/module.f.js +55 -258
- package/fjs/mcp/proof.f.d.ts +43 -32
- package/fjs/mcp/proof.f.js +508 -200
- package/fjs/media/nix/module.f.d.ts +30 -0
- package/fjs/media/nix/module.f.js +166 -0
- package/fjs/media/nix/proof.f.d.ts +32 -0
- package/fjs/media/nix/proof.f.js +127 -0
- package/fjs/module.f.js +1 -1
- package/fjs/protocol/json_rpc/module.f.d.ts +114 -0
- package/fjs/{media/json/rpc → protocol/json_rpc}/module.f.js +3 -3
- package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.js +3 -3
- package/fjs/protocol/mcp/module.f.d.ts +239 -0
- package/fjs/protocol/mcp/module.f.js +272 -0
- package/fjs/protocol/mcp/proof.f.d.ts +34 -0
- package/fjs/protocol/mcp/proof.f.js +208 -0
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.d.ts +5 -5
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +11 -11
- package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
- package/fjs/types/range_map/module.f.d.ts +14 -13
- package/fjs/types/range_map/module.f.js +18 -13
- package/fjs/types/range_map/proof.f.js +26 -39
- package/fjs/types/range_set/module.f.d.ts +5 -0
- package/fjs/types/range_set/module.f.js +16 -0
- package/fjs/types/range_set/proof.f.d.ts +1 -0
- package/fjs/types/range_set/proof.f.js +19 -0
- package/package.json +5 -5
- package/fjs/cas/evo/mcp/module.f.d.ts +0 -27
- package/fjs/cas/mcp/proof.f.d.ts +0 -45
- package/fjs/cas/mcp/proof.f.js +0 -545
- package/fjs/ci/playwright/module.f.d.ts +0 -2
- package/fjs/ci/playwright/module.f.js +0 -25
- package/fjs/media/json/rpc/module.f.d.ts +0 -114
- /package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.d.ts +0 -0
- /package/fjs/{mcp → protocol/mcp}/stdio/proof.f.d.ts +0 -0
|
@@ -224,8 +224,7 @@ export type Await = readonly ['await', (p: unknown) => readonly [unknown]];
|
|
|
224
224
|
export declare const awaitIfPromise: (p: unknown) => Effect<Await, unknown>;
|
|
225
225
|
/**
|
|
226
226
|
* Signature of a framework test-registration function (e.g. `nodeTest.test`,
|
|
227
|
-
* `bunTest.test
|
|
228
|
-
* be awaited.
|
|
227
|
+
* `bunTest.test`). Returns `Promise<void>` so async sub-tests can be awaited.
|
|
229
228
|
*/
|
|
230
229
|
export type TestFn = (name: string, options: {
|
|
231
230
|
readonly expectFailure: boolean;
|
|
@@ -233,8 +232,8 @@ export type TestFn = (name: string, options: {
|
|
|
233
232
|
/**
|
|
234
233
|
* A thin wrapper around a framework's `test` function. Passed through
|
|
235
234
|
* `registerModule` so nested test registration uses the appropriate context
|
|
236
|
-
* (e.g. `inlineContext` on Bun
|
|
237
|
-
*
|
|
235
|
+
* (e.g. `inlineContext` on Bun, which does not support nested `test()` calls
|
|
236
|
+
* inside a callback).
|
|
238
237
|
*/
|
|
239
238
|
export type TestContext = {
|
|
240
239
|
readonly test: TestFn;
|
|
@@ -258,7 +257,16 @@ export type Env = {
|
|
|
258
257
|
readonly [k: string]: string | undefined;
|
|
259
258
|
};
|
|
260
259
|
/** Identifies the JavaScript runtime detected at startup. */
|
|
261
|
-
export type Engine = 'node' | 'bun' | '
|
|
260
|
+
export type Engine = 'node' | 'bun' | 'deno';
|
|
261
|
+
/** Compares semantic versions numerically by major, minor, then patch. */
|
|
262
|
+
export declare const versionLessThan: (version: string, minimum: string) => boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Reports whether an external runner needs FunctionalScript's flattened test
|
|
265
|
+
* registration strategy. Node uses the native `expectFailure` option only
|
|
266
|
+
* from the Node 26 baseline; Deno is deliberately exempt from this Node-only
|
|
267
|
+
* version check.
|
|
268
|
+
*/
|
|
269
|
+
export declare const usesInlineTestContext: (engine: Engine, nodeVersion?: string) => boolean;
|
|
262
270
|
/**
|
|
263
271
|
* Runtime options passed to every `NodeProgram`.
|
|
264
272
|
*
|
|
@@ -269,8 +277,9 @@ export type Engine = 'node' | 'bun' | 'playwright';
|
|
|
269
277
|
* - `testContext`: Node `--test` context; used by `register` on Node.
|
|
270
278
|
* - `bunTestContext`: Bun-compatible context that flattens nested tests inline,
|
|
271
279
|
* working around Bun's lack of nested `test()` support.
|
|
272
|
-
* - `playwrightTestContext`: Playwright context using the same inline strategy.
|
|
273
280
|
* - `engine`: runtime detected at startup; controls which context `register` selects.
|
|
281
|
+
* - `nodeVersion`: detected Node version; absent for other and virtual runtimes.
|
|
282
|
+
* - `inlineTestContext`: whether the selected context flattens nested tests.
|
|
274
283
|
*/
|
|
275
284
|
export type NodeProgramOptions = {
|
|
276
285
|
readonly args: readonly string[];
|
|
@@ -283,8 +292,9 @@ export type NodeProgramOptions = {
|
|
|
283
292
|
};
|
|
284
293
|
readonly testContext: TestContext;
|
|
285
294
|
readonly bunTestContext: TestContext;
|
|
286
|
-
readonly playwrightTestContext: TestContext;
|
|
287
295
|
readonly engine: Engine;
|
|
296
|
+
readonly nodeVersion?: string;
|
|
297
|
+
readonly inlineTestContext: boolean;
|
|
288
298
|
};
|
|
289
299
|
export type Program<O extends Operation> = (options: NodeProgramOptions) => Effect<O, number>;
|
|
290
300
|
export type NodeProgram = Program<NodeOp>;
|
|
@@ -147,3 +147,25 @@ export const test = do_('test');
|
|
|
147
147
|
* compose `mapStep(error(s), () => n)` directly.
|
|
148
148
|
*/
|
|
149
149
|
export const errorExit = (s) => mapStep(error(s), () => 1);
|
|
150
|
+
const versionParts = (version) => version.replace(/^v/, '').split('.').map(Number);
|
|
151
|
+
/** Compares semantic versions numerically by major, minor, then patch. */
|
|
152
|
+
export const versionLessThan = (version, minimum) => {
|
|
153
|
+
const [major = 0, minor = 0, patch = 0] = versionParts(version);
|
|
154
|
+
const [minMajor = 0, minMinor = 0, minPatch = 0] = versionParts(minimum);
|
|
155
|
+
return major < minMajor || major === minMajor && (minor < minMinor || minor === minMinor && patch < minPatch);
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Reports whether an external runner needs FunctionalScript's flattened test
|
|
159
|
+
* registration strategy. Node uses the native `expectFailure` option only
|
|
160
|
+
* from the Node 26 baseline; Deno is deliberately exempt from this Node-only
|
|
161
|
+
* version check.
|
|
162
|
+
*/
|
|
163
|
+
export const usesInlineTestContext = (engine, nodeVersion) => {
|
|
164
|
+
if (engine === 'bun') {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (engine !== 'node' || nodeVersion === undefined) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return versionLessThan(nodeVersion, '26.0.0');
|
|
171
|
+
};
|
|
@@ -32,7 +32,7 @@ import { concat, normalize, toPosix } from '../../path/module.f.js';
|
|
|
32
32
|
import {} from '../module.f.js';
|
|
33
33
|
import { asyncRun } from '../module.js';
|
|
34
34
|
import { memoryOperationMap } from './memory/module.js';
|
|
35
|
-
import {} from './module.f.js';
|
|
35
|
+
import { usesInlineTestContext, } from './module.f.js';
|
|
36
36
|
import { asBase, asNominal } from '../../types/nominal/module.f.js';
|
|
37
37
|
import { error, ok } from '../../types/result/module.f.js';
|
|
38
38
|
import { asyncTryCatch } from '../../types/result/module.js';
|
|
@@ -250,10 +250,6 @@ const runNodeEffect = asyncRun({
|
|
|
250
250
|
read: readStdinByte,
|
|
251
251
|
test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => runNodeEffect(test(t))),
|
|
252
252
|
});
|
|
253
|
-
const isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
|
|
254
|
-
const pwTest = isPlaywright
|
|
255
|
-
? (await import('@playwright/test')).test
|
|
256
|
-
: undefined;
|
|
257
253
|
const inlineTest = async (name, { expectFailure }, fn) => {
|
|
258
254
|
if (expectFailure) {
|
|
259
255
|
try {
|
|
@@ -273,25 +269,29 @@ const wrapInlineTest = (register) => ({
|
|
|
273
269
|
test: (name, opts, fn) => register(name, () => inlineTest(name, opts, fn))
|
|
274
270
|
});
|
|
275
271
|
const bunTestContext = wrapInlineTest(testContext.test);
|
|
276
|
-
const
|
|
272
|
+
const engine = 'Bun' in globalThis ? 'bun' :
|
|
273
|
+
'Deno' in globalThis ? 'deno' : 'node';
|
|
274
|
+
const nodeVersion = engine === 'node' ? process.version : undefined;
|
|
275
|
+
const inlineTestContext = usesInlineTestContext(engine, nodeVersion);
|
|
277
276
|
const options = {
|
|
278
277
|
args: process.argv.slice(2),
|
|
279
278
|
env: process.env,
|
|
280
279
|
home: toPosix(os.homedir()),
|
|
281
280
|
std: { stdout: process.stdout, stderr: process.stderr },
|
|
282
|
-
testContext,
|
|
281
|
+
testContext: inlineTestContext ? wrapInlineTest(testContext.test) : testContext,
|
|
283
282
|
bunTestContext,
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
engine,
|
|
284
|
+
...(nodeVersion === undefined ? {} : { nodeVersion }),
|
|
285
|
+
inlineTestContext,
|
|
286
286
|
};
|
|
287
287
|
/**
|
|
288
288
|
* Runs a `NodeProgram` against the real Node globals and process arguments,
|
|
289
289
|
* resolving to its exit code **without** terminating the process.
|
|
290
290
|
*
|
|
291
291
|
* Use this when the caller must stay alive afterwards — e.g. when proofs are
|
|
292
|
-
* registered under an external test runner (Node `--test`, Bun,
|
|
293
|
-
*
|
|
294
|
-
*
|
|
292
|
+
* registered under an external test runner (Node `--test`, Bun, Deno) that owns
|
|
293
|
+
* the process lifecycle. For a standalone CLI entry point that should exit with
|
|
294
|
+
* the program's code, use {@link run} instead.
|
|
295
295
|
*/
|
|
296
296
|
export const runEffect = program => runNodeEffect(program(options));
|
|
297
297
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { empty, isVec, uint, vec, vec8 } from "../../types/bit_vec/module.f.js";
|
|
2
2
|
import { utf8, utf8ToString } from "../../text/module.f.js";
|
|
3
3
|
import { match, pure, step } from "../module.f.js";
|
|
4
|
-
import { both, fetch, mkdir, now, readdir, readFile, readUtf8File, rm, sandbox, writeFile, writeUtf8File, rename, readBytes, randomInt, writeFromStream } from "./module.f.js";
|
|
4
|
+
import { both, fetch, mkdir, now, readdir, readFile, readUtf8File, rm, sandbox, writeFile, writeUtf8File, rename, readBytes, randomInt, writeFromStream, usesInlineTestContext, versionLessThan } from "./module.f.js";
|
|
5
5
|
import { create as memCreate, read as memRead, write as memWrite } from "../memory/module.f.js";
|
|
6
6
|
import { empty as listEmpty, nonEmpty as listNonEmpty } from "../list/module.f.js";
|
|
7
7
|
import { emptyState, virtual } from "./virtual/module.f.js";
|
|
@@ -17,6 +17,19 @@ const readHello = match({
|
|
|
17
17
|
},
|
|
18
18
|
});
|
|
19
19
|
export const proof = {
|
|
20
|
+
externalTestContext: () => {
|
|
21
|
+
assert(usesInlineTestContext('node', 'v22.20.0'));
|
|
22
|
+
assert(usesInlineTestContext('node', '25.99.99'));
|
|
23
|
+
assert(!usesInlineTestContext('node', '26.0.0'));
|
|
24
|
+
assert(!usesInlineTestContext('node', '26.1.0'));
|
|
25
|
+
assert(!usesInlineTestContext('node'));
|
|
26
|
+
assert(usesInlineTestContext('bun'));
|
|
27
|
+
assert(!usesInlineTestContext('deno', '22.0.0'));
|
|
28
|
+
assert(versionLessThan('25.99.99', '26.0.0'));
|
|
29
|
+
assert(versionLessThan('26.0.0', '26.1.0'));
|
|
30
|
+
assert(versionLessThan('26.1.0', '26.1.1'));
|
|
31
|
+
assert(!versionLessThan('26.1.1', '26.1.1'));
|
|
32
|
+
},
|
|
20
33
|
map: () => {
|
|
21
34
|
const e = step(readFile('hello'), ([k, v]) => {
|
|
22
35
|
assert(k !== 'error', v);
|
package/fjs/effects/proof.f.d.ts
CHANGED
package/fjs/effects/proof.f.js
CHANGED
|
@@ -15,6 +15,7 @@ export const assertPure = (e, expected) => {
|
|
|
15
15
|
assertEq(o[0], expected);
|
|
16
16
|
};
|
|
17
17
|
const next = match({ add: (a, b) => a + b });
|
|
18
|
+
const anyNext = match({ add: a => a + 1 });
|
|
18
19
|
export const proof = {
|
|
19
20
|
foldStep: {
|
|
20
21
|
empty: () => {
|
|
@@ -103,6 +104,27 @@ export const proof = {
|
|
|
103
104
|
assert(r2[0] === 'done', r2);
|
|
104
105
|
assertEq(r2[1], 5);
|
|
105
106
|
},
|
|
107
|
+
ownCommand: () => {
|
|
108
|
+
// The same map the two cases below dispatch against: an own
|
|
109
|
+
// property still resolves, so what they prove is refusal of
|
|
110
|
+
// inherited names, not a map that dispatches nothing.
|
|
111
|
+
const r = anyNext(do_('add')(41));
|
|
112
|
+
assert(r[0] === 'cont', r);
|
|
113
|
+
assertEq(r[1], 42);
|
|
114
|
+
},
|
|
115
|
+
// A `command` naming an `Object.prototype` member must not dispatch to
|
|
116
|
+
// the inherited value. `map['constructor']` is `Object` and
|
|
117
|
+
// `map['toString']` is `Function.prototype.toString` — both callable,
|
|
118
|
+
// neither a handler — so a plain index read would call one of them with
|
|
119
|
+
// the node's payload instead of throwing.
|
|
120
|
+
throw: {
|
|
121
|
+
constructorCommand: () => {
|
|
122
|
+
anyNext(do_('constructor')(1));
|
|
123
|
+
},
|
|
124
|
+
toStringCommand: () => {
|
|
125
|
+
anyNext(do_('toString')(1));
|
|
126
|
+
},
|
|
127
|
+
},
|
|
106
128
|
},
|
|
107
129
|
step: {
|
|
108
130
|
pure: () => {
|
|
@@ -62,8 +62,8 @@ export type Reporter<O extends Operation> = {
|
|
|
62
62
|
* Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
|
|
63
63
|
* lets the external framework own scheduling: each registered test callback
|
|
64
64
|
* calls `fn`, then recursively registers any sub-trees returned by the function.
|
|
65
|
-
* This is the correct model for Node `--test`, Bun, and
|
|
66
|
-
*
|
|
65
|
+
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
66
|
+
* be declared upfront and the framework drives execution.
|
|
67
67
|
*/
|
|
68
68
|
export declare const registerModule: (ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void>;
|
|
69
69
|
/**
|
|
@@ -130,7 +130,7 @@ export declare const defaultReporter: (options: NodeProgramOptions) => Reporter<
|
|
|
130
130
|
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
131
131
|
export declare const main: NodeProgram;
|
|
132
132
|
/**
|
|
133
|
-
* Entry point for external test frameworks (Node `--test`, Bun,
|
|
133
|
+
* Entry point for external test frameworks (Node `--test`, Bun, Deno).
|
|
134
134
|
*
|
|
135
135
|
* Discovers test modules via `loadModuleMap`, then registers each with the
|
|
136
136
|
* framework-appropriate `TestContext` selected from `NodeProgramOptions`
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* - `runModule` / `Reporter<O>` — self-hosted Effects runner used by `fjs t`;
|
|
6
6
|
* sandboxes each leaf call individually and accumulates `TestState`.
|
|
7
7
|
* - `registerModule` / `TestContext` — registers tests with an external
|
|
8
|
-
* framework (Node `--test`, Bun,
|
|
9
|
-
*
|
|
8
|
+
* framework (Node `--test`, Bun, Deno) at import time; the framework owns
|
|
9
|
+
* scheduling and pass/fail counting.
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
@@ -79,12 +79,12 @@ export const collectTests = (path, throws, v) => {
|
|
|
79
79
|
* Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
|
|
80
80
|
* lets the external framework own scheduling: each registered test callback
|
|
81
81
|
* calls `fn`, then recursively registers any sub-trees returned by the function.
|
|
82
|
-
* This is the correct model for Node `--test`, Bun, and
|
|
83
|
-
*
|
|
82
|
+
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
83
|
+
* be declared upfront and the framework drives execution.
|
|
84
84
|
*/
|
|
85
85
|
export const registerModule = (ctx, k, v, star) => {
|
|
86
86
|
const registerOne = (ctx, [path, { fn, throws }]) => {
|
|
87
|
-
// ' *' (non-empty only for Bun
|
|
87
|
+
// ' *' (non-empty only for Bun) signals that all sub-tests run
|
|
88
88
|
// inline inside this single registration. Not appended to throw-tests since
|
|
89
89
|
// those never produce sub-tests. The path already contains '.throw' when a
|
|
90
90
|
// test is expected to throw, so no extra suffix is needed.
|
|
@@ -273,17 +273,15 @@ export const defaultReporter = (options) => {
|
|
|
273
273
|
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
274
274
|
export const main = options => testAll(defaultReporter(options))(options);
|
|
275
275
|
/**
|
|
276
|
-
* Entry point for external test frameworks (Node `--test`, Bun,
|
|
276
|
+
* Entry point for external test frameworks (Node `--test`, Bun, Deno).
|
|
277
277
|
*
|
|
278
278
|
* Discovers test modules via `loadModuleMap`, then registers each with the
|
|
279
279
|
* framework-appropriate `TestContext` selected from `NodeProgramOptions`
|
|
280
280
|
* based on the detected `engine`.
|
|
281
281
|
*/
|
|
282
282
|
export const register = o => {
|
|
283
|
-
const star = o.
|
|
284
|
-
const ctx = o.engine === 'bun' ? o.bunTestContext :
|
|
285
|
-
o.engine === 'playwright' ? o.playwrightTestContext :
|
|
286
|
-
o.testContext;
|
|
283
|
+
const star = o.inlineTestContext ? ' ...' : '';
|
|
284
|
+
const ctx = o.engine === 'bun' ? o.bunTestContext : o.testContext;
|
|
287
285
|
return eff(loadModuleMap(o.env))
|
|
288
286
|
.step(registerModuleMap(ctx, star))
|
|
289
287
|
.step(() => pure(0))
|
|
@@ -236,7 +236,7 @@ const makeRegisterRunner = (testOp) => {
|
|
|
236
236
|
});
|
|
237
237
|
return runner;
|
|
238
238
|
};
|
|
239
|
-
// registerModule appends ' ...' for inline runners (Bun
|
|
239
|
+
// registerModule appends ' ...' for inline runners (Bun).
|
|
240
240
|
// This mock never invokes the registered callback; it only records names.
|
|
241
241
|
export const registerSuffixes = () => {
|
|
242
242
|
const runner = makeRegisterRunner((_runner, _ctx, name, _xf, _fn) => (s) => [[...s, name], undefined]);
|
|
@@ -249,7 +249,7 @@ export const registerSuffixes = () => {
|
|
|
249
249
|
assertEq(nodeNames.length, 2);
|
|
250
250
|
assert(nodeNames[0] === 'import("./a.f.ts").proof.ok()');
|
|
251
251
|
assertEq(nodeNames[1], 'import("./a.f.ts").proof.throw.a()');
|
|
252
|
-
// Bun
|
|
252
|
+
// Bun (star = ' ...'): ... on normal tests, path shows throw for throw-tests
|
|
253
253
|
const [inlineNames] = runner([])(registerModule(registerNoopCtx, './a.f.ts', proof, ' ...'));
|
|
254
254
|
assertEq(inlineNames.length, 2);
|
|
255
255
|
assert(inlineNames[0] === 'import("./a.f.ts").proof.ok() ...');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A test that returns a thenable (Promise-like object, not a real Promise).
|
|
2
2
|
// Per FunctionalScript convention, thenables are treated as plain values —
|
|
3
|
-
// not awaited. Both sandbox (fjs) and registerModule (node/bun/deno
|
|
3
|
+
// not awaited. Both sandbox (fjs) and registerModule (node/bun/deno)
|
|
4
4
|
// must exit 0: the thenable object is walked as a sub-tree whose only key
|
|
5
5
|
// `then` is a function with parameters, so no leaf tests are found and the
|
|
6
6
|
// test trivially passes.
|
package/fjs/fsc/module.f.js
CHANGED
|
@@ -7,7 +7,7 @@ import { strictEqual } from '../types/function/operator/module.f.js';
|
|
|
7
7
|
import { merge as rangeMapMerge, fromRange, get, } from '../types/range_map/module.f.js';
|
|
8
8
|
import { reduce as listReduce, toArray, map } from '../types/list/module.f.js';
|
|
9
9
|
import { range as asciiRange } from '../text/ascii/module.f.js';
|
|
10
|
-
import { fn } from '../types/function/module.f.js';
|
|
10
|
+
import { flip, fn } from '../types/function/module.f.js';
|
|
11
11
|
import { one } from '../types/range/module.f.js';
|
|
12
12
|
import { assertEq } from '../asserts/module.f.js';
|
|
13
13
|
const fromCharCode = String.fromCharCode;
|
|
@@ -31,17 +31,17 @@ const reduce = (a) => {
|
|
|
31
31
|
});
|
|
32
32
|
return toArray(listReduce(merge)(empty)(a));
|
|
33
33
|
};
|
|
34
|
-
const codePointRange = fromRange(def);
|
|
34
|
+
const codePointRange = flip(fromRange(def));
|
|
35
35
|
const range = fn(asciiRange).map(codePointRange).result;
|
|
36
36
|
const rangeSet = (l) => (f) => {
|
|
37
|
-
const codePointRange = fromRange(def);
|
|
37
|
+
const codePointRange = flip(fromRange(def));
|
|
38
38
|
const g = r => codePointRange(asciiRange(r))(f);
|
|
39
39
|
return reduce(map(g)(l));
|
|
40
40
|
};
|
|
41
41
|
const create = (a) => {
|
|
42
42
|
const i = reduce(a);
|
|
43
43
|
const x = get(def);
|
|
44
|
-
return v => c => x(
|
|
44
|
+
return v => c => x(i)(c)(v)(c);
|
|
45
45
|
};
|
|
46
46
|
export const terminal = -1;
|
|
47
47
|
const toInit = () => () => [[], init];
|
package/fjs/fsm/module.f.js
CHANGED
|
@@ -51,5 +51,5 @@ const initialState = [''];
|
|
|
51
51
|
const initialStateStringify = stringifyIdentity(initialState);
|
|
52
52
|
export const dfa = grammar => addEntry(grammar)(initialState)({});
|
|
53
53
|
const get = rangeMapGet(emptyStateStringify);
|
|
54
|
-
const runOp = dfa => input => s => get(
|
|
54
|
+
const runOp = dfa => input => s => get(dfa[s] ?? [])(input);
|
|
55
55
|
export const run = (dfa) => (input) => foldScan(runOp(dfa))(initialStateStringify)(input);
|
|
@@ -96,6 +96,7 @@ type ErrorMessage = '" are missing' | 'unescaped character' | 'invalid hex value
|
|
|
96
96
|
export declare const isKeywordToken: (token: JsToken) => boolean;
|
|
97
97
|
export declare const tokenize: (input: List<number>) => (path: string) => List<JsTokenWithMetadata>;
|
|
98
98
|
export declare const proof: {
|
|
99
|
+
getOperatorTokenInvalid: () => void;
|
|
99
100
|
throw: {
|
|
100
101
|
unionConflict: () => void;
|
|
101
102
|
};
|
|
@@ -27,7 +27,7 @@ leftSquareBracket, reverseSolidus, rightSquareBracket, lowLine,
|
|
|
27
27
|
latinSmallLetterRange, latinSmallLetterA, latinSmallLetterB, latinSmallLetterE, latinSmallLetterF, latinSmallLetterN, latinSmallLetterR, latinSmallLetterT, latinSmallLetterU,
|
|
28
28
|
//
|
|
29
29
|
leftCurlyBracket, rightCurlyBracket, dollarSign } from '../../text/ascii/module.f.js';
|
|
30
|
-
import { todo } from '../../asserts/module.f.js';
|
|
30
|
+
import { todo, assertEq } from '../../asserts/module.f.js';
|
|
31
31
|
const { fromCharCode } = String;
|
|
32
32
|
const rangeOneNine = range('19');
|
|
33
33
|
const rangeSetNewLine = [
|
|
@@ -111,21 +111,20 @@ const rangeMapMerge = def => merge({
|
|
|
111
111
|
equal: strictEqual,
|
|
112
112
|
def,
|
|
113
113
|
});
|
|
114
|
-
const rangeFunc = r => f => def => fromRange(def)(
|
|
114
|
+
const rangeFunc = r => f => def => fromRange(def)(f)(r);
|
|
115
115
|
const scanRangeOp = def => f => [f(def), scanRangeOp(def)];
|
|
116
116
|
const reduceRangeMap = def => a => {
|
|
117
117
|
const rm = scan(scanRangeOp(def))(a);
|
|
118
118
|
return toArray(listReduce(rangeMapMerge(def))(empty)(rm));
|
|
119
119
|
};
|
|
120
|
-
const scanRangeSetOp = def => f => r => [fromRange(def)(
|
|
120
|
+
const scanRangeSetOp = def => f => r => [fromRange(def)(f)(r), scanRangeSetOp(def)(f)];
|
|
121
121
|
const rangeSetFunc = rs => f => def => {
|
|
122
122
|
const rm = scan(scanRangeSetOp(def)(f))(rs);
|
|
123
123
|
return toArray(listReduce(rangeMapMerge(def))(empty)(rm));
|
|
124
124
|
};
|
|
125
125
|
const create = (def) => (a) => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
return v => c => x(c)(i)(v)(c);
|
|
126
|
+
const x = get(def)(reduceRangeMap(def)(a));
|
|
127
|
+
return v => c => x(c)(v)(c);
|
|
129
128
|
};
|
|
130
129
|
const digitToBigInt = d => BigInt(d - digit0);
|
|
131
130
|
const startNumber = digit => ({ s: 1n, m: digitToBigInt(digit), f: 0, es: 1, e: 0 });
|
|
@@ -527,6 +526,14 @@ export const tokenize = input => path => {
|
|
|
527
526
|
return flat(scan(flat([input /*satisfies List<CharCodeOrEof>*/, [null]])));
|
|
528
527
|
};
|
|
529
528
|
export const proof = {
|
|
529
|
+
// `getOperatorToken` is only ever called with a value already confirmed to be
|
|
530
|
+
// a known operator (`hasOperatorToken`, or a single char from `rangeOpStart`),
|
|
531
|
+
// so its `??` fallback is unreachable through `tokenize`. Call it directly
|
|
532
|
+
// with a non-operator string to cover that branch.
|
|
533
|
+
getOperatorTokenInvalid: () => {
|
|
534
|
+
const result = getOperatorToken('@');
|
|
535
|
+
assertEq(result.kind, 'error');
|
|
536
|
+
},
|
|
530
537
|
throw: {
|
|
531
538
|
// union throws when two distinct non-default handlers are merged for the same range;
|
|
532
539
|
// this path is unreachable through the public API (no overlapping ranges in practice).
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { type Effect } from '../../effects/module.f.ts';
|
|
2
1
|
import { type MemOp } from '../../effects/memory/module.f.ts';
|
|
3
|
-
import { type
|
|
4
|
-
import { type
|
|
5
|
-
import { type
|
|
6
|
-
import { type Cache } from '../evo/module.f.ts';
|
|
2
|
+
import { type ToolEntry } from '../../protocol/mcp/module.f.ts';
|
|
3
|
+
import { type FileCasOperation } from '../../cas/module.f.ts';
|
|
4
|
+
import { type Cache } from '../../cas/evo/module.f.ts';
|
|
7
5
|
import type { Key } from '../../effects/memory/module.f.ts';
|
|
8
6
|
/** Arguments for `cas_add`: content to store, with optional encoding type. */
|
|
9
7
|
export declare const casAddArgs: {
|
|
@@ -27,23 +25,3 @@ export declare const casListArgs: {};
|
|
|
27
25
|
* rescan.
|
|
28
26
|
*/
|
|
29
27
|
export declare const casToolRegistry: (home: string) => (cacheKey: Key<Cache>) => readonly ToolEntry<FileCasOperation | MemOp>[];
|
|
30
|
-
/**
|
|
31
|
-
* MCP handlers for `FileCas` plus the Evo API (`fjs/cas/evo`) layered on it,
|
|
32
|
-
* bound to `home` and an already-built Evo cache slot (see `initEvo`).
|
|
33
|
-
*/
|
|
34
|
-
export declare const casMcpHandlers: (home: string) => (cacheKey: Key<Cache>) => McpHandlers<FileCasOperation | MemOp>;
|
|
35
|
-
/**
|
|
36
|
-
* Static MCP configuration for the CAS server: advertises the `tools`
|
|
37
|
-
* capability, identifies the server, and pins the protocol version.
|
|
38
|
-
*/
|
|
39
|
-
export declare const casConfig: McpConfig;
|
|
40
|
-
/**
|
|
41
|
-
* Runs the combined CAS + Evo MCP server over stdio: scans `~/.cas/` once to
|
|
42
|
-
* build the Evo subject/head cache (`initEvo`), allocates the session-state
|
|
43
|
-
* slot, builds the `mcpStep` for the merged tool registry, and drives the
|
|
44
|
-
* read → parse → dispatch → write loop until stdin EOF.
|
|
45
|
-
*/
|
|
46
|
-
export declare const casMcpServer: (home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>;
|
|
47
|
-
export declare const proof: {
|
|
48
|
-
casMcpServer: () => void;
|
|
49
|
-
};
|
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* store a blob and get back its hash, fetch a blob by hash, and enumerate
|
|
7
|
-
* what is stored — without shelling out to the `cas` CLI. The store itself
|
|
2
|
+
* The `cas_add` / `cas_get` / `cas_list` tool registry: maps `Cas<O>`
|
|
3
|
+
* operations onto MCP tools, so an agent that speaks MCP can store a blob and
|
|
4
|
+
* get back its hash, fetch a blob by hash, and enumerate what is stored —
|
|
5
|
+
* without shelling out to the `cas` CLI. The store itself
|
|
8
6
|
* (`fjs/cas/module.f.ts`) stays transport-agnostic; this is an additional
|
|
9
|
-
* front end alongside the CLI `main`.
|
|
10
|
-
* `fjs/
|
|
11
|
-
* (
|
|
12
|
-
*
|
|
13
|
-
* startup (`initEvo`).
|
|
7
|
+
* front end alongside the CLI `main`. This registry is one of the tool sets
|
|
8
|
+
* `fjs/mcp/module.f.ts` composes into the FJS MCP server — see that module
|
|
9
|
+
* for the full tool table (including `fjs/mcp/evo`'s `evo_*` tools) and the
|
|
10
|
+
* server entry point.
|
|
14
11
|
*
|
|
15
12
|
* ## Tools
|
|
16
13
|
*
|
|
17
|
-
* | Tool | args
|
|
18
|
-
*
|
|
19
|
-
* | `cas_add` | `{ content, type? }`
|
|
20
|
-
* | `cas_get` | `{ hash, content?: boolean }`
|
|
21
|
-
* | `cas_list` | `{}`
|
|
22
|
-
* | `evo_list` | `{}` | `e.list()` | subjects, one per line |
|
|
23
|
-
* | `evo_head` | `{ subject }` | `e.head(...)` | head hashes, one per line |
|
|
24
|
-
* | `evo_revision` | `{ hash }` | `e.revision(...)`| the revision, as JSON |
|
|
25
|
-
* | `evo_add` | `{ parents, snapshot?, subject?, archived? }` | `e.add(...)` | hash (cBase32) |
|
|
14
|
+
* | Tool | args | action | result |
|
|
15
|
+
* |----------------|--------------------------------|------------------|--------------------------------------|
|
|
16
|
+
* | `cas_add` | `{ content, type? }` | `c.write(...)` | hash (cBase32) |
|
|
17
|
+
* | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,mimeType,type[,uri][,text\|blob]}` |
|
|
18
|
+
* | `cas_list` | `{}` | `c.list()` | hashes, one per line |
|
|
26
19
|
*
|
|
27
20
|
* ## `cas_add` input encoding
|
|
28
21
|
*
|
|
@@ -35,7 +28,7 @@
|
|
|
35
28
|
*
|
|
36
29
|
* Inline content (either encoding) is capped at 128 KiB (`maxLength`). There is
|
|
37
30
|
* no MCP route for larger content — the server never opens a local path named by
|
|
38
|
-
* the client (see the invariant in `fjs/
|
|
31
|
+
* the client (see the invariant in `fjs/mcp/README.md`); large files go
|
|
39
32
|
* through the `cas` CLI (`cas add <path>`) instead, run directly by the user.
|
|
40
33
|
*
|
|
41
34
|
* ## `cas_get` output
|
|
@@ -111,7 +104,7 @@
|
|
|
111
104
|
import { string, option, or, boolean } from '../../types/rtti/module.f.js';
|
|
112
105
|
import { stringify } from '../../media/json/module.f.js';
|
|
113
106
|
import { pure, step } from '../../effects/module.f.js';
|
|
114
|
-
import {
|
|
107
|
+
import {} from '../../effects/memory/module.f.js';
|
|
115
108
|
import { cBase32ToVec, vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
116
109
|
import { decode as base64Decode, encode as base64Encode } from '../../basen/base64/module.f.js';
|
|
117
110
|
import { tryUtf8 } from '../../text/module.f.js';
|
|
@@ -119,16 +112,13 @@ import { detectStream } from '../../media/type/module.f.js';
|
|
|
119
112
|
import { detect as detectDialect } from '../../media/module.f.js';
|
|
120
113
|
import { maxLengthBytes } from '../../types/bit_vec/module.f.js';
|
|
121
114
|
import { ok } from '../../types/result/module.f.js';
|
|
122
|
-
import {} from '../../
|
|
123
|
-
import {
|
|
124
|
-
import { mcpStep, uninitializedState, toolEntry, fromRegistry, errorResult, okResult, } from '../../mcp/module.f.js';
|
|
125
|
-
import { collectRead, fileCas } from '../module.f.js';
|
|
115
|
+
import { toolEntry, errorResult, okResult, } from '../../protocol/mcp/module.f.js';
|
|
116
|
+
import { collectRead, fileCas } from '../../cas/module.f.js';
|
|
126
117
|
import { fromVec } from '../../text/utf8/module.f.js';
|
|
127
118
|
import { identity } from '../../types/function/module.f.js';
|
|
128
119
|
import { sha256 } from '../../crypto/sha2/module.f.js';
|
|
129
120
|
import { nonEmpty, empty as elEmpty } from '../../effects/list/module.f.js';
|
|
130
|
-
import {
|
|
131
|
-
import { evoToolRegistry } from '../evo/mcp/module.f.js';
|
|
121
|
+
import { syncRevision } from '../../cas/evo/module.f.js';
|
|
132
122
|
// ── Argument schemas (declared once, used for both inputSchema and validate) ─────
|
|
133
123
|
/** Arguments for `cas_add`: content to store, with optional encoding type. */
|
|
134
124
|
export const casAddArgs = {
|
|
@@ -237,33 +227,3 @@ export const casToolRegistry = (home) => (cacheKey) => {
|
|
|
237
227
|
toolEntry('cas_list', 'List all stored content hashes (cBase32), one per line.', casListArgs, () => step(c.list(), hashes => pure(okResult(hashes.map(vecToCBase32).join('\n'))))),
|
|
238
228
|
];
|
|
239
229
|
};
|
|
240
|
-
// ── Handlers ────────────────────────────────────────────────────────────────────
|
|
241
|
-
/**
|
|
242
|
-
* MCP handlers for `FileCas` plus the Evo API (`fjs/cas/evo`) layered on it,
|
|
243
|
-
* bound to `home` and an already-built Evo cache slot (see `initEvo`).
|
|
244
|
-
*/
|
|
245
|
-
export const casMcpHandlers = (home) => (cacheKey) => fromRegistry([...casToolRegistry(home)(cacheKey), ...evoToolRegistry(evo(fileCas(sha256)(home))(cacheKey))]);
|
|
246
|
-
// ── Session configuration ───────────────────────────────────────────────────────
|
|
247
|
-
/**
|
|
248
|
-
* Static MCP configuration for the CAS server: advertises the `tools`
|
|
249
|
-
* capability, identifies the server, and pins the protocol version.
|
|
250
|
-
*/
|
|
251
|
-
export const casConfig = {
|
|
252
|
-
serverInfo: { name: 'functionalscript-cas', version: '0.30.0' },
|
|
253
|
-
capabilities: { tools: {} },
|
|
254
|
-
protocolVersion: '2024-11-05',
|
|
255
|
-
};
|
|
256
|
-
// ── Server ──────────────────────────────────────────────────────────────────────
|
|
257
|
-
/**
|
|
258
|
-
* Runs the combined CAS + Evo MCP server over stdio: scans `~/.cas/` once to
|
|
259
|
-
* build the Evo subject/head cache (`initEvo`), allocates the session-state
|
|
260
|
-
* slot, builds the `mcpStep` for the merged tool registry, and drives the
|
|
261
|
-
* read → parse → dispatch → write loop until stdin EOF.
|
|
262
|
-
*/
|
|
263
|
-
export const casMcpServer = (home) => step(initEvo(fileCas(sha256)(home)), cacheKey => step(create(uninitializedState), sessionKey => stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey))));
|
|
264
|
-
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
265
|
-
export const proof = {
|
|
266
|
-
// casMcpServer is never called in integration tests because it drives a
|
|
267
|
-
// real stdio server; call it here to cover its Effect-building body.
|
|
268
|
-
casMcpServer: () => { casMcpServer('/'); },
|
|
269
|
-
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Operation } from '../../effects/module.f.ts';
|
|
2
|
+
import { type MemOp } from '../../effects/memory/module.f.ts';
|
|
3
|
+
import { type ToolEntry } from '../../protocol/mcp/module.f.ts';
|
|
4
|
+
import { type Evo } from '../../cas/evo/module.f.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Arguments for `evo_list`: an optional status filter, forwarded unchanged to
|
|
7
|
+
* `Evo.list` — omitted lists the active subjects, `true` the archived ones.
|
|
8
|
+
*/
|
|
9
|
+
export declare const evoListArgs: {
|
|
10
|
+
readonly archived: import("../../types/rtti/module.f.ts").Or<readonly [true, undefined]>;
|
|
11
|
+
};
|
|
12
|
+
/** Arguments for `evo_head`: the subject whose current heads are requested. */
|
|
13
|
+
export declare const evoHeadArgs: {
|
|
14
|
+
readonly subject: import("../../types/rtti/module.f.ts").String;
|
|
15
|
+
};
|
|
16
|
+
/** Arguments for `evo_revision`: the hash of the revision to read. */
|
|
17
|
+
export declare const evoRevisionArgs: {
|
|
18
|
+
readonly hash: import("../../types/rtti/module.f.ts").String;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Arguments for `evo_add`: a new revision, per `fjs/cas/evo`'s
|
|
22
|
+
* `RevisionData` — every field of it the caller supplies, i.e. all but
|
|
23
|
+
* `generation`, which the server computes.
|
|
24
|
+
*/
|
|
25
|
+
export declare const evoAddArgs: {
|
|
26
|
+
readonly parents: import("../../types/rtti/module.f.ts").Type1<"array", import("../../types/rtti/module.f.ts").String>;
|
|
27
|
+
readonly snapshot: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
28
|
+
readonly subject: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
|
|
29
|
+
readonly archived: import("../../types/rtti/module.f.ts").Or<readonly [true, undefined]>;
|
|
30
|
+
};
|
|
31
|
+
/** Registry of all Evo tools, bound to an `Evo<O>`. */
|
|
32
|
+
export declare const evoToolRegistry: <O extends Operation>(e: Evo<O>) => readonly ToolEntry<O | MemOp>[];
|