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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/fjs/bnf/descent/module.f.d.ts +43 -2
  3. package/fjs/bnf/descent/module.f.js +37 -12
  4. package/fjs/bnf/descent/proof.f.d.ts +1 -0
  5. package/fjs/bnf/descent/proof.f.js +73 -32
  6. package/fjs/bnf/ll1/module.f.js +2 -2
  7. package/fjs/cas/evo/module.f.d.ts +28 -8
  8. package/fjs/cas/evo/module.f.js +43 -10
  9. package/fjs/cas/evo/proof.f.d.ts +4 -0
  10. package/fjs/cas/evo/proof.f.js +82 -1
  11. package/fjs/ci/config/module.f.d.ts +10 -7
  12. package/fjs/ci/config/module.f.js +23 -8
  13. package/fjs/ci/module.f.js +12 -5
  14. package/fjs/ci/nix/module.f.d.ts +61 -0
  15. package/fjs/ci/nix/module.f.js +92 -0
  16. package/fjs/ci/nix/proof.f.d.ts +23 -0
  17. package/fjs/ci/nix/proof.f.js +109 -0
  18. package/fjs/ci/node/module.f.d.ts +23 -1
  19. package/fjs/ci/node/module.f.js +47 -3
  20. package/fjs/ci/node/proof.f.d.ts +3 -0
  21. package/fjs/ci/node/proof.f.js +17 -0
  22. package/fjs/ci/proof.f.d.ts +2 -0
  23. package/fjs/ci/proof.f.js +47 -9
  24. package/fjs/dev/module.f.d.ts +1 -0
  25. package/fjs/dev/module.f.js +13 -2
  26. package/fjs/dev/update/module.f.d.ts +11 -0
  27. package/fjs/dev/update/module.f.js +20 -0
  28. package/fjs/dev/update/proof.f.d.ts +7 -0
  29. package/fjs/dev/update/proof.f.js +35 -0
  30. package/fjs/djs/ast/module.f.d.ts +47 -0
  31. package/fjs/djs/ast/module.f.js +9 -0
  32. package/fjs/djs/tokenizer/module.f.js +2 -2
  33. package/fjs/djs/tokenizer/proof.f.d.ts +1 -0
  34. package/fjs/djs/tokenizer/proof.f.js +70 -16
  35. package/fjs/effects/module.f.d.ts +20 -0
  36. package/fjs/effects/module.f.js +25 -1
  37. package/fjs/effects/node/module.d.ts +3 -3
  38. package/fjs/effects/node/module.f.d.ts +17 -7
  39. package/fjs/effects/node/module.f.js +22 -0
  40. package/fjs/effects/node/module.js +12 -12
  41. package/fjs/effects/node/proof.f.d.ts +1 -0
  42. package/fjs/effects/node/proof.f.js +14 -1
  43. package/fjs/effects/node/virtual/module.f.js +1 -1
  44. package/fjs/effects/proof.f.d.ts +5 -0
  45. package/fjs/effects/proof.f.js +22 -0
  46. package/fjs/emergent_testing/all.test.js +2 -1
  47. package/fjs/emergent_testing/module.f.d.ts +3 -3
  48. package/fjs/emergent_testing/module.f.js +8 -10
  49. package/fjs/emergent_testing/proof.f.js +2 -2
  50. package/fjs/emergent_testing/scenarios/thenable.pass.js +1 -1
  51. package/fjs/fsc/module.f.js +4 -4
  52. package/fjs/fsm/module.f.js +1 -1
  53. package/fjs/js/tokenizer/module.f.d.ts +1 -0
  54. package/fjs/js/tokenizer/module.f.js +13 -6
  55. package/fjs/{cas/mcp → mcp/cas}/module.f.d.ts +3 -25
  56. package/fjs/{cas/mcp → mcp/cas}/module.f.js +18 -58
  57. package/fjs/mcp/evo/module.f.d.ts +32 -0
  58. package/fjs/{cas/evo/mcp → mcp/evo}/module.f.js +19 -14
  59. package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +1 -0
  60. package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +23 -7
  61. package/fjs/mcp/module.f.d.ts +54 -237
  62. package/fjs/mcp/module.f.js +55 -258
  63. package/fjs/mcp/proof.f.d.ts +43 -32
  64. package/fjs/mcp/proof.f.js +508 -200
  65. package/fjs/media/nix/module.f.d.ts +30 -0
  66. package/fjs/media/nix/module.f.js +166 -0
  67. package/fjs/media/nix/proof.f.d.ts +32 -0
  68. package/fjs/media/nix/proof.f.js +127 -0
  69. package/fjs/module.f.js +1 -1
  70. package/fjs/protocol/json_rpc/module.f.d.ts +114 -0
  71. package/fjs/{media/json/rpc → protocol/json_rpc}/module.f.js +3 -3
  72. package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.js +3 -3
  73. package/fjs/protocol/mcp/module.f.d.ts +239 -0
  74. package/fjs/protocol/mcp/module.f.js +272 -0
  75. package/fjs/protocol/mcp/proof.f.d.ts +34 -0
  76. package/fjs/protocol/mcp/proof.f.js +208 -0
  77. package/fjs/{mcp → protocol/mcp}/stdio/module.f.d.ts +5 -5
  78. package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +11 -11
  79. package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
  80. package/fjs/types/range_map/module.f.d.ts +14 -13
  81. package/fjs/types/range_map/module.f.js +18 -13
  82. package/fjs/types/range_map/proof.f.js +26 -39
  83. package/fjs/types/range_set/module.f.d.ts +5 -0
  84. package/fjs/types/range_set/module.f.js +16 -0
  85. package/fjs/types/range_set/proof.f.d.ts +1 -0
  86. package/fjs/types/range_set/proof.f.js +19 -0
  87. package/package.json +5 -5
  88. package/fjs/cas/evo/mcp/module.f.d.ts +0 -27
  89. package/fjs/cas/mcp/proof.f.d.ts +0 -45
  90. package/fjs/cas/mcp/proof.f.js +0 -545
  91. package/fjs/ci/playwright/module.f.d.ts +0 -2
  92. package/fjs/ci/playwright/module.f.js +0 -25
  93. package/fjs/media/json/rpc/module.f.d.ts +0 -114
  94. /package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.d.ts +0 -0
  95. /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`, `pwTest`). Returns `Promise<void>` so async sub-tests can
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 and Playwright, which do not support nested
237
- * `test()` calls inside a callback).
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' | 'playwright';
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 playwrightTestContext = wrapInlineTest(pwTest);
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
- playwrightTestContext,
285
- engine: isPlaywright ? 'playwright' : 'Bun' in globalThis ? 'bun' : 'node',
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, Playwright)
293
- * that owns the process lifecycle. For a standalone CLI entry point that should
294
- * exit with the program's code, use {@link run} instead.
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,4 +1,5 @@
1
1
  export declare const proof: {
2
+ externalTestContext: () => void;
2
3
  map: () => void;
3
4
  fetch: () => void;
4
5
  mkdir: {
@@ -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);
@@ -435,6 +435,6 @@ export const defaultNodeProgramOptions = {
435
435
  std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
436
436
  testContext,
437
437
  bunTestContext: testContext,
438
- playwrightTestContext: testContext,
439
438
  engine: 'node',
439
+ inlineTestContext: false,
440
440
  };
@@ -31,6 +31,11 @@ export declare const proof: {
31
31
  match: {
32
32
  done: () => void;
33
33
  cont: () => void;
34
+ ownCommand: () => void;
35
+ throw: {
36
+ constructorCommand: () => void;
37
+ toStringCommand: () => void;
38
+ };
34
39
  };
35
40
  step: {
36
41
  pure: () => void;
@@ -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: () => {
@@ -1,4 +1,5 @@
1
1
  import { runEffect } from '../effects/node/module.js';
2
2
  import { register } from './module.f.js';
3
- // we need `await` for Playwright.
3
+ // Top-level `await`: every proof must be registered before the runner starts
4
+ // collecting tests.
4
5
  await runEffect(register);
@@ -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 Playwright, where tests
66
- * must be declared upfront and the framework drives execution.
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, Playwright).
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, Playwright) at import time; the framework
9
- * owns scheduling and pass/fail counting.
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 Playwright, where tests
83
- * must be declared upfront and the framework drives execution.
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/Playwright) signals that all sub-tests run
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, Playwright).
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.engine === 'bun' || o.engine === 'playwright' ? ' ...' : '';
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/Playwright).
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/Playwright (star = ' ...'): ... on normal tests, path shows throw for throw-tests
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/playwright)
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.
@@ -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(c)(i)(v)(c);
44
+ return v => c => x(i)(c)(v)(c);
45
45
  };
46
46
  export const terminal = -1;
47
47
  const toInit = () => () => [[], init];
@@ -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(input)(dfa[s] ?? []);
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)(r)(f);
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)(r)(f), scanRangeSetOp(def)(f)];
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 i = reduceRangeMap(def)(a);
127
- const x = get(def);
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 Read, type Write } from '../../effects/node/module.f.ts';
4
- import { type McpConfig, type McpHandlers, type ToolEntry } from '../../mcp/module.f.ts';
5
- import { type FileCasOperation } from '../module.f.ts';
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
- * MCP adapter for the content-addressable store and the Evo API layered on
3
- * top of it.
4
- *
5
- * Maps `Cas<O>` operations onto MCP tools, so an agent that speaks MCP can
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`. The same server also exposes
10
- * `fjs/cas/evo`'s subject/head API
11
- * (`evo_list`/`evo_head`/`evo_revision`/`evo_add`, `fjs/cas/evo/mcp`) one
12
- * process, one `~/.cas/` store, one in-memory Evo cache scanned once at
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 | action | result |
18
- * |----------------|----------------------------------------------|------------------|--------------------------------------|
19
- * | `cas_add` | `{ content, type? }` | `c.write(...)` | hash (cBase32) |
20
- * | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,mimeType,type[,uri][,text\|blob]}` |
21
- * | `cas_list` | `{}` | `c.list()` | hashes, one per line |
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/cas/mcp/README.md`); large files go
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 { create } from '../../effects/memory/module.f.js';
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 '../../effects/node/module.f.js';
123
- import { stdioTransport } from '../../mcp/stdio/module.f.js';
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 { initEvo, evo, syncRevision } from '../evo/module.f.js';
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>[];