Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions fjs/cas/todo/filecasoperation-duplicates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## `FileCasOperation` lists three members twice

**Priority:** P5
**Status:** open

### Problem

`types.ts:19-22`:

```ts
export type FileCasOperation =
| ReadBytes | Mkdir | Readdir | Access | Rename | Rm
| RandomInt | Now | CreateExclusive | WriteBytes | Stat
| Now | Readdir | Rm
```

`Now`, `Readdir`, and `Rm` appear twice. Harmless for a union type, but it is
copy-paste residue in a type whose doc comment enumerates the members one by
one, and it misleads a reader into hunting for a difference.

### Tasks

- [ ] Drop the duplicate line
47 changes: 47 additions & 0 deletions fjs/ci/todo/dead-nix-flake-job.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## `nodeNixFlakeJob` is a dead duplicate

**Priority:** P4
**Status:** open

### Problem

`fjs/ci/node/module.f.mjs:134-137` and `fjs/ci/module.f.mjs:49` are the same
expression:

```js
export const nodeNixFlakeJob = ubuntuArm([nixInstall, ...nodeNixVersionSteps])
const nixFlakeJob = ubuntuArm([nixInstall, ...nodeNixVersionSteps])
```

Nothing imports `nodeNixFlakeJob` — the exported copy (with a 12-line
rationale comment) is dead, and the live private copy carries a shorter,
differently-worded comment. Two descriptions of one job that must be deleted
together when the flakes are adopted.

The same ~100 lines hold three no-op indirections:

- `fjs/ci/node/module.f.mjs:73` — `const nodeJob = steps => ubuntuArm(steps)`,
an eta-expansion of `ubuntuArm`;
- `:139` — `export const nodeMainSteps = platformNodeSteps`, an alias
imported by `fjs/ci/module.f.mjs` while `platformNodeSteps` is also
exported;
- `fjs/ci/module.f.mjs:44` — `const nixJobs = nodeNixJobs`.

`basicNode` (`:32-35`) is exported but referenced only by its own proof.

### Proposal

Delete `nodeNixFlakeJob` (moving its rationale comment onto the surviving
`nixFlakeJob`), drop the three aliases, and either use `basicNode` from
`nodeInstall`'s callers or make it private.

### Tasks

- [ ] Delete the dead export, keep the better comment
- [ ] Remove the `nodeJob` / `nodeMainSteps` / `nixJobs` aliases

### Related

- [669-ci-ubuntu-job-factory](669-ci-ubuntu-job-factory.md) — the
`ubuntu`/`ubuntuArm` factory; the flake-job pair is a separate duplication
its factory does not remove
43 changes: 43 additions & 0 deletions fjs/effects/node/todo/state-types-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Bring the node/virtual types onto the record-type rules

**Priority:** P3
**Status:** open

### Problem

Three deviations from rules AGENTS.md states explicitly:

1. **`Env` re-rolls `StringMap` in a file that already imports it.**
`fjs/effects/node/types.ts:291-293` spells out
`{ readonly [k: string]: string|undefined }` — that is
`StringMap<string>`, imported at `:12` and used two lines apart for
`Headers` and `Module`.
2. **Two index signatures without `?`.**
`fjs/effects/node/virtual/types.ts` — `internet:
{ readonly[url: string]: Vec }` and `memoryValues:
{ readonly [key: string]: unknown }`. §6.2: without `?`, TypeScript types
every access as `T` while the value can be `undefined` at runtime. The
consumer proves it — `virtual/module.f.mjs:361-364` checks
`result === undefined` on a read the type says is always a `Vec`. Both
should be `StringMap<…>`. (The recursive `Dir` in the same file is the
documented inline-form exception and stays.)
3. **`State`'s fields are all mutable.** `virtual/types.ts:28-41` — no
`readonly` on `stdout`, `stderr`, `stdin`, `root`, `internet`, `epochNs`,
`memoryNext`, `memoryValues`, `randomNext`, while every operation rebuilds
the record by spread. The mutability is unused and unenforced; `Dir` and
`_Entity` in the same file already have `readonly`.

### Proposal

`Env = StringMap<string>`, `internet: StringMap<Vec>`,
`memoryValues: StringMap<unknown>`, and `readonly` on every `State` field.

### Tasks

- [ ] Replace the three inline record types with `StringMap`
- [ ] Mark `State` fields `readonly`; fix any compile fallout

### Related

- [node-module-layering](../../todo/node-module-layering.md) — flags the
`NodeProgramOptions.std` deviation only; these are the remaining ones
41 changes: 41 additions & 0 deletions fjs/fsc/todo/orphaned-json-grammar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## A third JSON grammar copy is dead code

**Priority:** P3
**Status:** open

### Problem

`fjs/fsc/json.f.mjs` (125 lines) is a complete JSON grammar written with
`fjs/bnf` combinators — `string`/`character`/`escape`/`hex`,
`number`/`uint`/`fraction0`/`exponent0`, `object`/`array`/`member`,
`ws0`/`ws1` — duplicating `deterministic` in `fjs/bnf/testlib.f.mjs:136-196`
rule for rule.

[bnf-grammar-single-owner](../../media/json/todo/bnf-grammar-single-owner.md)
inventories the JSON grammar as existing in exactly two places
(`fjs/bnf/testlib` and `fjs/djs/tokenizer`); this third copy is not in that
inventory, so implementing the todo as written would strand it.

It is also dead: `fjs/fsc/bnf.f.mjs` is the only importer of `json.f.mjs`,
and nothing imports `bnf.f.mjs` (`wsModule` has zero consumers). Neither file
has proof coverage — `fjs/fsc/proof.f.mjs` imports only `./module.f.mjs`. And
`fjs/fsc` is the compiler, not a media format, so the JSON half is in the
wrong module regardless.

### Proposal

Either delete both files, or keep only `bnf.f.mjs`'s genuinely
FunctionalScript-specific rules (`fjs`, `lineComment`, `multiLine`,
`id`/`alpha`) and have them import the JSON half from the future
`fjs/media/json` grammar owner. Either way, add this pair to
`bnf-grammar-single-owner`'s inventory.

### Tasks

- [ ] Decide: delete, or rebase on the shared JSON grammar
- [ ] Update `bnf-grammar-single-owner`'s inventory and task list

### Related

- [bnf-grammar-single-owner](../../media/json/todo/bnf-grammar-single-owner.md)
— the two-copy inventory this pair is missing from
46 changes: 46 additions & 0 deletions fjs/fsm/todo/sorted-set-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## Don't use the JSON serializer as a set key

**Priority:** P3
**Status:** open

### Problem

The subset construction needs a canonical key for a `SortedSet<string>` and
reaches for a media-format serializer to get one (`module.f.mjs:19, 30, 71`):

```js
import { stringify } from '../media/json/module.f.mjs'
const stringifyIdentity = stringify(identity)
...
const s = stringifyIdentity(set)
if (s in dfa) { return dfa }
```

That inverts the layering: `fjs/fsm` is generic automaton tooling, and
`fjs/media/json/module.f.mjs` imports its tokenizer, which imports the
795-line `fjs/js/tokenizer` — so building a DFA transitively loads the whole
JavaScript lexer. It also pays for full JSON string escaping on every
state-set key. [recognizer-backend](../../bnf/todo/recognizer-backend.md)
proposes generalizing exactly this subset construction, so the dependency
would propagate.

(Also visible at `:71`: `s in dfa` reads a `StringMap` with `in` instead of
`at` from `fjs/types/object`.)

### Proposal

Name the operation for what it is — a canonical key for a sorted string
set — and put it where the data lives (`fjs/types/sorted_set` or
`fjs/types/string_set`), implemented as a `join` over a separator.
`fjs/fsm` then imports nothing from `fjs/media`.

### Tasks

- [ ] Add a canonical-key function to the sorted-set module with proof
coverage
- [ ] Convert `fjs/fsm` and drop its `fjs/media/json` import

### Related

- [recognizer-backend](../../bnf/todo/recognizer-backend.md) — will inherit
whichever key mechanism `fsm` uses
42 changes: 42 additions & 0 deletions fjs/fsm/todo/torange-ascii-range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## `toRange` re-implements `ascii.range` and crashes on one character

**Priority:** P3
**Status:** open

### Problem

```js
export const toRange = s => {
const [b, e] = toArray(stringToList(s))
return range([b, e])
}
```

`fjs/text/ascii/module.f.mjs:20-25` already owns "two-character string →
inclusive `Range`", including the one-character case. `fsm.toRange`
(`module.f.mjs:32-36`) is `compose(asciiRange)(byteSetRange)` written out by
hand — and the duplicate is worse than the original:

```
toRange('a') → RangeError: The number NaN cannot be converted to a BigInt
```

because `e` destructures to `undefined` and `byte_set.range` computes
`one(undefined - b + 1)`. `fjs/fsc/module.f.mjs:66` shows the correct
composition (`fn(asciiRange).map(codePointRange)`).

`toUnion` (`:39-45`) — "byte set from the characters of a string" — is
likewise generic byte-set vocabulary, the same shape as `fjs/bnf`'s `set(s)`
for its own alphabet. Both exports have no consumer outside
`fjs/fsm/proof.f.mjs`.

### Proposal

`export const toRange = compose(asciiRange)(byteSetRange)` (or drop the
export if the proof stays the only caller); move `toUnion` to
`fjs/types/byte_set` next to `one`/`set`.

### Tasks

- [ ] Rebuild `toRange` on `ascii.range`; cover the one-character case
- [ ] Move `toUnion` to `byte_set` (or inline it into the proof)
48 changes: 48 additions & 0 deletions fjs/mcp/todo/casmcpserver-share-cas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Flatten `casMcpServer` and build `fileCas` once

**Priority:** P3
**Status:** open

### Problem

`module.f.mjs:81-88` is the exact shape §6.4 forbids — the second `step`
nests only so the continuation can still see `cacheKey`, which is the case
`historyStep` exists for:

```js
export const casMcpServer = home => step(
initEvo(fileCas(sha256)(home)),
cacheKey => step(
create(uninitializedState),
sessionKey =>
stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey)),
),
)
```

Flattening also surfaces the real defect: `fileCas(sha256)(home)` is
constructed three times for one server — here (`:82`), in
`casMcpHandlers`' `evoToolRegistry(evo(fileCas(sha256)(home))(cacheKey))`
(`:57`), and inside `casToolRegistry`
(`fjs/mcp/cas/module.f.mjs:180-181`). It also exposes an asymmetry between
the sibling registries: `evoToolRegistry(e)` is injected with a built
`Evo<O>`, while `casToolRegistry(home)` takes a path and builds its own
store.

### Proposal

`casToolRegistry(cas)(cacheKey)` to mirror `evoToolRegistry`, with one
`const cas = fileCas(sha256)(home)` at the composition root, and the body
rewritten flat with `history`/`historyStep`/`step`
(`fjs/dev/update/module.f.mjs:99-106` is the in-repo model).

### Tasks

- [ ] Inject a built `Cas` into `casToolRegistry`
- [ ] Flatten `casMcpServer` with `historyStep`; construct `fileCas` once

### Related

- [66k-cas-cli-mcp-shared-core](../../cas/todo/66k-cas-cli-mcp-shared-core.md)
— CLI-vs-MCP sharing; this issue is the intra-server construction and §6.4
shape
50 changes: 50 additions & 0 deletions fjs/media/json/todo/escape-table-single-owner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
## The string-escape table has three copies

**Priority:** P3
**Status:** open

### Problem

One mapping — `" \ / b f n r t` ↔ `" \ / BS FF LF CR HT`, plus `\uXXXX` —
is written three times:

- `fjs/media/json/serializer/module.f.mjs:38-46` — `escapeTable`, a lookup
table (encode side);
- `fjs/js/tokenizer/module.f.mjs:582-591` — a range-map dispatch (decode
side);
- `fjs/djs/tokenizer/module.f.mjs:344-374` — a switch in `stringDecodeScan`
(decode side).

[667-js-tokenizer-handler-literals](../../../js/todo/667-js-tokenizer-handler-literals.md)
§3 proposes an `escapeTo` `(letter, char)` table, but scoped inside
`js/tokenizer` only — it never mentions `djs/tokenizer`'s decoder or the
serializer's encode-side table, so the cross-module owner question stays
open.

Also worth flagging to whoever picks up [157](../../../djs/todo/157.md): that
issue opens by asserting both tokenizers "delegate all character
classification, escape decoding, and number parsing" to `js/tokenizer`. That
is no longer true of the grammar-based DJS tokenizer, which imports only
`isKeywordToken` and re-implements escape decoding, keyword classification,
and number decoding itself.

### Proposal

One module owning the bidirectional simple-escape table — natural home: next
to the JSON string grammar that
[bnf-grammar-single-owner](bnf-grammar-single-owner.md) creates — consumed by
the serializer's `escapeCodePoint` and both decoders.

### Tasks

- [ ] Define the table once (letter ↔ code point pairs), derive encode and
decode views from it
- [ ] Convert the three sites
- [ ] Correct 157's premise when touching it

### Related

- [bnf-grammar-single-owner](bnf-grammar-single-owner.md) — same "one owner"
move for the grammar itself
- [667-js-tokenizer-handler-literals](../../../js/todo/667-js-tokenizer-handler-literals.md)
— the `js/tokenizer`-local half of this
Loading
Loading