Skip to content

Commit 57cfabf

Browse files
Polish: second cartridge, GUI smoke test, finish .adoc tidy (#39)
## Three small follow-ups, done in turn ### 1. Second example cartridge — proves the engine is language-agnostic `cartridges/reference/python-ruby.cartridge.json`: 6 Python→Ruby transitions spanning **cognate** (`def`, string interpolation), **false-friend** (empty-collection truthiness — `[]` is falsy in Python, truthy in Ruby), **alien-realization** (comprehension → block `map`), **novel** (Ruby `:symbol`), and **vanished** (explicit `self` parameter). Dogfoods the cartridge contract on a fresh pair. Cartridge invariants went **59 → 107 checks**, all green; the CLI/GUI now present 2 packs / 12 correspondences. ### 2. GUI smoke test `gui/server.js` now `export`s `handler` + `loadCartridges` and opens a listener **only under `import.meta.main`** — so `test/gui_smoke_test.js` drives the handler **in-process (no socket, `--allow-read` only)**: the static shell + `#index/#paneForms/#paneClassify`, the `/app.js` bundle, the `/api/cartridges` shape (every transition has a valid kind + from/to), and a clean 404. **38 checks, all passing.** Wired into `deno task test` (and `test:gui`) + a `just test-gui` recipe. Verified the real server still starts via a live probe. ### 3. Finished the `.adoc` migration `TOPOLOGY.md` → `TOPOLOGY.adoc` (and corrected its data-flow diagram, which still described the old JS→migration-report pipeline, to the real cartridge→engine→GUI/CLI flow) and `TEST-NEEDS.md` → `TEST-NEEDS.adoc` (coverage refreshed to include the GUI smoke test). Root docs are now AsciiDoc end-to-end, except the GitHub-required `CODE_OF_CONDUCT.md` / `SECURITY.md`. ### Verified `deno fmt --check`, `deno lint`, `deno task test` (cartridge invariants + GUI smoke), `deno task build` (2/2 cartridges valid), and a live GUI probe — all green. No dangling links. Licence files untouched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01A1BaAhqxUjkgVb1yg1sZap)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 68f492e commit 57cfabf

9 files changed

Lines changed: 350 additions & 100 deletions

File tree

Justfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,16 @@ gui:
7070

7171
# === TESTING ===
7272

73-
# Run all tests
73+
# Run all tests (cartridge invariants + GUI smoke test)
7474
test:
7575
@echo "🧪 Running tests..."
7676
deno task test
7777

78+
# Run just the GUI smoke test (server handler: shell, bundle, cartridge API)
79+
test-gui:
80+
@echo "🖥️ GUI smoke test..."
81+
@deno task test:gui
82+
7883
# === VALIDATION ===
7984

8085
# Run full validation

TEST-NEEDS.adoc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
= TEST-NEEDS — nextgen-languages-evangeliser
3+
4+
CRG Grade: C (achieved 2026-04-04).
5+
6+
== Current test state
7+
8+
[cols="2,1,3",options="header"]
9+
|===
10+
| Category | Count | Notes
11+
12+
| Cartridge invariant tests | 1 suite | `test/run_all.js` (Deno) — semantic invariants over every cartridge
13+
| GUI smoke test | 1 suite | `test/gui_smoke_test.js` (Deno) — server handler: shell, bundle, cartridge API
14+
| Cartridge schema validation | 1 | `scripts/validate-cartridges.js` (Deno + ajv, JSON-Schema 2020-12)
15+
| Idris2 ABI typecheck | 1 | `abi.ipkg` / `src/interface/Abi/*.idr` (CI: `idris2-abi.yml`)
16+
| Test framework | Deno + ajv + Idris2 | host-language unit tests were removed with the ReScript host
17+
|===
18+
19+
== What's covered
20+
21+
* Every correspondence is classified by one of the six CorrespondenceKinds
22+
* Well-formedness: Forms (language + surface), residue shapes, strata names/booleans
23+
* False-friend signature (surface holds ∧ intention diverges) — mirrors `Abi.Correspondence.isFalseFriendShape`
24+
* Residue direction for novel (absent-source) / vanished (absent-target)
25+
* The worked-examples reference cartridge demonstrates all six kinds
26+
* Cartridge JSON-Schema conformance (ajv)
27+
* GUI server handler: static shell, `/app.js` bundle, `/api/cartridges` shape, and a clean 404
28+
* Idris2 ABI typechecks (no `believe_me` / `assert_total`)
29+
30+
== Still missing (for CRG B+)
31+
32+
* Headless-DOM rendering test of `gui/app.js` (the smoke test covers the server / API, not the browser render)
33+
* Property-based generation over cartridge facts
34+
* Performance benchmarks
35+
* End-to-end "walk a correspondence" flow test
36+
37+
== Run tests
38+
39+
[source,bash]
40+
----
41+
just test # cartridge invariants + GUI smoke test
42+
just test-gui # GUI smoke test only
43+
deno task test # same as `just test`
44+
deno task validate # cartridge schema validation
45+
----

TEST-NEEDS.md

Lines changed: 0 additions & 37 deletions
This file was deleted.

TOPOLOGY.adoc

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= TOPOLOGY — nextgen-languages-evangeliser
4+
5+
== Purpose
6+
7+
Cross-language *comprehension and transfer* engine — Duolingo / Rosetta Stone for
8+
programming languages. It *classifies* correspondences between a known language and
9+
a target so learning transfers (it does not translate, lint, or build IDEs).
10+
Flagship / future host: AffineScript. Legacy target: ReScript (host removed). Runs
11+
on Deno; the browser multi-pane workspace is the primary surface.
12+
13+
== Module map
14+
15+
[source]
16+
----
17+
nextgen-languages-evangeliser/
18+
├── bin/evangeliser.js # Offline CLI (Deno; reads cartridges)
19+
├── src/
20+
│ ├── cartridges.js # Shared cartridge loader (Deno glue)
21+
│ └── interface/ # Engine spine: Idris2 ABI + Zig FFI + AffineScript host binding
22+
├── cartridges/ # Correspondence facts: schema + reference pack
23+
├── gui/ # Browser multi-pane workspace (primary surface)
24+
├── scripts/ # Deno utilities (cartridge validator, GUI launcher)
25+
├── test/ # Cartridge invariant tests + GUI smoke test
26+
├── config.ncl # Nickel configuration
27+
├── docs/ # Documentation (AsciiDoc)
28+
├── Justfile # Task runner recipes
29+
└── deno.json # Deno module config
30+
----
31+
32+
== Data flow
33+
34+
[source]
35+
----
36+
cartridges/**/*.cartridge.json (authored correspondence facts)
37+
38+
39+
shared loader (src/cartridges.js)
40+
41+
├─► gui/server.js ──► browser workspace (index · forms · classification)
42+
├─► bin/evangeliser.js ──► CLI (--kind / --find / --json)
43+
└─► test + scripts ──► invariants + schema validation
44+
45+
46+
(downstream, planned) octads ──► VeriSimDB ──► PanLL panels
47+
----
48+
49+
The classification vocabulary (six CorrespondenceKinds, strata, residue shapes) is
50+
specified in the Idris2 ABI (`src/interface/Abi/`) and mirrored by the cartridge
51+
schema. The engine *classifies* correspondences; it does not transform source.

TOPOLOGY.md

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"$schema": "../correspondence-cartridge.schema.json",
3+
"spdx": "MPL-2.0",
4+
"name": "python-ruby",
5+
"version": "0.1.0",
6+
"description": "Second reference cartridge: Python -> Ruby. Dogfoods the cartridge contract on a fresh language pair to show the engine is language-agnostic. Covers cognate / false-friend / alien-realization / novel / vanished from a Python learner's standpoint.",
7+
"languages": ["python", "ruby"],
8+
"transitions": [
9+
{
10+
"concept": "function-definition",
11+
"from": { "language": "python", "surface": "def greet(name):\n ..." },
12+
"to": { "language": "ruby", "surface": "def greet(name)\n ...\nend" },
13+
"kind": "cognate",
14+
"residue": {
15+
"shape": "none",
16+
"note": "Same keyword (`def`) and intent; Ruby closes the body with `end` where Python uses indentation."
17+
},
18+
"strata": [
19+
{ "stratum": "surface", "holds": true },
20+
{ "stratum": "intention", "holds": true }
21+
],
22+
"narrative": {
23+
"celebrate": "You already define functions with `def` — that word is the same in Ruby.",
24+
"better": "Your `def` intuition transfers directly.",
25+
"example": "def greet(name): ... <-> def greet(name) ... end"
26+
}
27+
},
28+
{
29+
"concept": "string-interpolation",
30+
"from": { "language": "python", "surface": "f\"Hi {name}\"" },
31+
"to": { "language": "ruby", "surface": "\"Hi #{name}\"" },
32+
"kind": "cognate",
33+
"residue": {
34+
"shape": "none",
35+
"note": "Same intent (interpolate a value into a string); Ruby uses `#{}` and needs no `f` prefix."
36+
},
37+
"strata": [
38+
{ "stratum": "surface", "holds": true },
39+
{ "stratum": "intention", "holds": true }
40+
],
41+
"narrative": {
42+
"celebrate": "f-strings are second nature to you — interpolation carries straight over.",
43+
"better": "Drop the `f` and write `#{}` instead of `{}`.",
44+
"example": "f\"Hi {name}\" <-> \"Hi #{name}\""
45+
}
46+
},
47+
{
48+
"concept": "empty-collection-truthiness",
49+
"from": { "language": "python", "surface": "if items: # empty list is falsy" },
50+
"to": { "language": "ruby", "surface": "if items # empty array is TRUTHY" },
51+
"kind": "false-friend",
52+
"residue": {
53+
"shape": "lossy",
54+
"note": "In Ruby only `nil` and `false` are falsy; `[]`, `\"\"`, and `0` are all truthy. The Python habit of testing a collection for emptiness via truthiness silently breaks."
55+
},
56+
"strata": [
57+
{ "stratum": "surface", "holds": true },
58+
{ "stratum": "intention", "holds": false }
59+
],
60+
"narrative": {
61+
"celebrate": "You've leaned on truthiness to check for empty/zero for years — that's a real, useful instinct.",
62+
"safety": "In Ruby that instinct misfires: `if items` is true even when empty. Test emptiness explicitly.",
63+
"example": "Python: if items: -> Ruby: if items.any? (or: unless items.empty?)"
64+
}
65+
},
66+
{
67+
"concept": "map-over-collection",
68+
"from": { "language": "python", "surface": "[f(x) for x in xs]" },
69+
"to": { "language": "ruby", "surface": "xs.map { |x| f(x) }" },
70+
"kind": "alien-realization",
71+
"residue": {
72+
"shape": "lossy",
73+
"note": "Same intention (transform each element) realised through different machinery: a comprehension expression vs a method taking a block."
74+
},
75+
"strata": [
76+
{ "stratum": "intention", "holds": true },
77+
{ "stratum": "structure", "holds": false }
78+
],
79+
"narrative": {
80+
"celebrate": "You think in comprehensions — that mapping intent is exactly right.",
81+
"better": "Ruby spells it as a block passed to `map`; once it clicks, blocks are everywhere.",
82+
"example": "[f(x) for x in xs] -> xs.map { |x| f(x) }"
83+
}
84+
},
85+
{
86+
"concept": "symbol",
87+
"from": { "language": "python", "surface": "(no analog)" },
88+
"to": { "language": "ruby", "surface": ":status" },
89+
"kind": "novel",
90+
"residue": {
91+
"shape": "absent-source",
92+
"note": "Python has no symbol type. The nearest habits — interned strings, enum members, dict keys — are not the same thing; learn the symbol fresh."
93+
},
94+
"strata": [
95+
{ "stratum": "intention", "holds": false }
96+
],
97+
"narrative": {
98+
"celebrate": "Coming from Python you have great instincts for dict keys and enums.",
99+
"safety": "A Ruby symbol (`:status`) is its own immutable, interned identifier — not a string. Learn it as a new thing rather than mapping it onto strings.",
100+
"example": "{ status: :active } # :active is a symbol, not \"active\""
101+
}
102+
},
103+
{
104+
"concept": "explicit-self-parameter",
105+
"from": { "language": "python", "surface": "def m(self):\n ..." },
106+
"to": { "language": "ruby", "surface": "def m\n ...\nend" },
107+
"kind": "vanished",
108+
"residue": {
109+
"shape": "absent-target",
110+
"note": "Ruby has no explicit `self` parameter; the receiver is implicit. The Python habit of writing `self` first in every method has nowhere to go — re-route it."
111+
},
112+
"strata": [
113+
{ "stratum": "intention", "holds": false }
114+
],
115+
"narrative": {
116+
"celebrate": "Writing `self` first is muscle memory from Python — totally reasonable.",
117+
"safety": "In Ruby that parameter vanishes: methods take only their real arguments, and `self` is implicit (use it bare when you need the receiver).",
118+
"example": "Python: def m(self): ... -> Ruby: def m ... end"
119+
}
120+
}
121+
]
122+
}

deno.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"gui": "deno run --allow-read --allow-net gui/server.js",
1111
"lint": "deno lint",
1212
"fmt": "deno fmt",
13-
"test": "deno run --allow-read test/run_all.js",
13+
"test": "deno run --allow-read test/run_all.js && deno run --allow-read test/gui_smoke_test.js",
14+
"test:gui": "deno run --allow-read test/gui_smoke_test.js",
1415
"pre-commit": "deno task lint && deno task fmt --check"
1516
},
1617
"fmt": {
@@ -20,7 +21,7 @@
2021
"semiColons": false,
2122
"singleQuote": false,
2223
"proseWrap": "preserve",
23-
"include": ["bin/", "src/cartridges.js", "test/run_all.js", "deno.json"]
24+
"include": ["bin/", "src/cartridges.js", "test/", "deno.json"]
2425
},
2526
"lint": {
2627
"rules": {

0 commit comments

Comments
 (0)