hoodscript 1.2.0__tar.gz → 1.3.1__tar.gz

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 (146) hide show
  1. {hoodscript-1.2.0 → hoodscript-1.3.1}/CHANGELOG.md +104 -0
  2. {hoodscript-1.2.0 → hoodscript-1.3.1}/CONTRIBUTING.md +19 -0
  3. {hoodscript-1.2.0 → hoodscript-1.3.1}/PKG-INFO +25 -1
  4. {hoodscript-1.2.0 → hoodscript-1.3.1}/README.md +19 -0
  5. hoodscript-1.3.1/docs/adr/0004-no-regional-dialect-overlays.md +45 -0
  6. hoodscript-1.3.1/docs/grammar.ebnf +59 -0
  7. hoodscript-1.3.1/examples/solutions/beatlab/stage1.hs +10 -0
  8. hoodscript-1.3.1/examples/solutions/beatlab/stage2.hs +15 -0
  9. hoodscript-1.3.1/examples/solutions/beatlab/stage3.hs +11 -0
  10. hoodscript-1.3.1/examples/solutions/beatlab/stage4.hs +14 -0
  11. hoodscript-1.3.1/examples/solutions/beatlab/stage5.hs +13 -0
  12. hoodscript-1.3.1/examples/solutions/cookout/stage1.hs +9 -0
  13. hoodscript-1.3.1/examples/solutions/cookout/stage2.hs +8 -0
  14. hoodscript-1.3.1/examples/solutions/cookout/stage3.hs +5 -0
  15. hoodscript-1.3.1/examples/solutions/cookout/stage4.hs +26 -0
  16. hoodscript-1.3.1/examples/solutions/cookout/stage5.hs +13 -0
  17. hoodscript-1.3.1/examples/solutions/dropday/stage1.hs +13 -0
  18. hoodscript-1.3.1/examples/solutions/dropday/stage2.hs +14 -0
  19. hoodscript-1.3.1/examples/solutions/dropday/stage3.hs +12 -0
  20. hoodscript-1.3.1/examples/solutions/dropday/stage4.hs +11 -0
  21. hoodscript-1.3.1/examples/solutions/dropday/stage5.hs +16 -0
  22. hoodscript-1.3.1/examples/solutions/level1.hs +9 -0
  23. hoodscript-1.3.1/examples/solutions/level2.hs +9 -0
  24. hoodscript-1.3.1/examples/solutions/level3.hs +14 -0
  25. hoodscript-1.3.1/examples/solutions/level4.hs +18 -0
  26. hoodscript-1.3.1/examples/solutions/level5.hs +38 -0
  27. hoodscript-1.3.1/examples/solutions/level6.hs +14 -0
  28. hoodscript-1.3.1/examples/solutions/level7.hs +37 -0
  29. hoodscript-1.3.1/examples/solutions/level8.hs +41 -0
  30. {hoodscript-1.2.0 → hoodscript-1.3.1}/pyproject.toml +11 -1
  31. hoodscript-1.3.1/scripts/gen_ebnf.py +33 -0
  32. hoodscript-1.3.1/scripts/hooks/pre-push +11 -0
  33. hoodscript-1.3.1/scripts/install-hooks.sh +7 -0
  34. hoodscript-1.3.1/src/hoodarcade/__init__.py +9 -0
  35. hoodscript-1.3.1/src/hoodarcade/cli.py +357 -0
  36. hoodscript-1.3.1/src/hoodarcade/core/runtime.py +148 -0
  37. hoodscript-1.3.1/src/hoodarcade/core/ui.py +232 -0
  38. hoodscript-1.3.1/src/hoodarcade/games/beatlab.py +393 -0
  39. hoodscript-1.3.1/src/hoodarcade/games/cookout.py +446 -0
  40. hoodscript-1.3.1/src/hoodarcade/games/dropday.py +415 -0
  41. hoodscript-1.3.1/src/hoodfarm/__init__.py +3 -0
  42. hoodscript-1.3.1/src/hoodfarm/cli.py +262 -0
  43. hoodscript-1.3.1/src/hoodfarm/drone.py +158 -0
  44. hoodscript-1.3.1/src/hoodfarm/grid.py +216 -0
  45. hoodscript-1.3.1/src/hoodfarm/levels.py +402 -0
  46. hoodscript-1.3.1/src/hoodfarm/renderer.py +249 -0
  47. hoodscript-1.3.1/src/hoodfarm/runner.py +224 -0
  48. hoodscript-1.3.1/src/hoodlib/__init__.py +9 -0
  49. hoodscript-1.3.1/src/hoodlib/disk.py +177 -0
  50. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/_version.py +2 -2
  51. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/cli.py +41 -4
  52. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/errors/translator.py +1 -1
  53. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/kernel/kernel.py +4 -1
  54. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/patterns.py +21 -11
  55. hoodscript-1.3.1/src/hoodscript/zen.py +60 -0
  56. hoodscript-1.3.1/src/vibe.py +8 -0
  57. hoodscript-1.3.1/tests/test_arcade.py +224 -0
  58. hoodscript-1.3.1/tests/test_arcade_gates.py +72 -0
  59. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_cli.py +14 -0
  60. hoodscript-1.3.1/tests/test_fuzz_ebnf.py +178 -0
  61. hoodscript-1.3.1/tests/test_game_engine.py +249 -0
  62. hoodscript-1.3.1/tests/test_grammar_ebnf.py +19 -0
  63. hoodscript-1.3.1/tests/test_hoodlib_disk.py +101 -0
  64. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_kernel.py +14 -0
  65. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_patterns.py +13 -0
  66. hoodscript-1.3.1/tests/test_wheel_contents.py +34 -0
  67. {hoodscript-1.2.0 → hoodscript-1.3.1}/.gitignore +0 -0
  68. {hoodscript-1.2.0 → hoodscript-1.3.1}/CODE_OF_CONDUCT.md +0 -0
  69. {hoodscript-1.2.0 → hoodscript-1.3.1}/LICENSE +0 -0
  70. {hoodscript-1.2.0 → hoodscript-1.3.1}/SECURITY.md +0 -0
  71. {hoodscript-1.2.0 → hoodscript-1.3.1}/SPRINT_PLAN.md +0 -0
  72. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/adr/0001-token-pipeline-over-parser.md +0 -0
  73. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/adr/0002-keyword-reconciliation.md +0 -0
  74. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/adr/0003-inclusive-ranges.md +0 -0
  75. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/adr/README.md +0 -0
  76. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_COMPLETE_MASTER_SYSTEM.md +0 -0
  77. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_FULL_ENGINEERING_SUITE.md +0 -0
  78. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/archive/HOODSCRIPT_UNSPRED_FEATURES_ROADMAP.md +0 -0
  79. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/curriculum.md +0 -0
  80. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/keywords.md +0 -0
  81. {hoodscript-1.2.0 → hoodscript-1.3.1}/docs/onboarding.md +0 -0
  82. {hoodscript-1.2.0 → hoodscript-1.3.1}/examples/quickstart.ipynb +0 -0
  83. {hoodscript-1.2.0 → hoodscript-1.3.1}/out.txt +0 -0
  84. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/app.js +0 -0
  85. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/bundle.js +0 -0
  86. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/examples.js +0 -0
  87. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/hoodscript-latest-py3-none-any.whl +0 -0
  88. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/index.html +0 -0
  89. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/style.css +0 -0
  90. {hoodscript-1.2.0 → hoodscript-1.3.1}/playground/worker.js +0 -0
  91. {hoodscript-1.2.0 → hoodscript-1.3.1}/scripts/build_playground.py +0 -0
  92. {hoodscript-1.2.0 → hoodscript-1.3.1}/scripts/gen_keywords.py +0 -0
  93. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/__init__.py +0 -0
  94. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/__main__.py +0 -0
  95. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/cache.py +0 -0
  96. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/constants.py +0 -0
  97. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/debugger.py +0 -0
  98. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/errors/__init__.py +0 -0
  99. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/errors/catalog.py +0 -0
  100. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/errors/renderer.py +0 -0
  101. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/formatter.py +0 -0
  102. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/importer.py +0 -0
  103. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/kernel/__init__.py +0 -0
  104. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/kernel/__main__.py +0 -0
  105. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/kernel/installer.py +0 -0
  106. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/kernel/magics.py +0 -0
  107. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/linter.py +0 -0
  108. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/lsp.py +0 -0
  109. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/migrator.py +0 -0
  110. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/repl.py +0 -0
  111. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/sandbox.py +0 -0
  112. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/stubs.py +0 -0
  113. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/traceback_handler.py +0 -0
  114. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/transpiler.py +0 -0
  115. {hoodscript-1.2.0 → hoodscript-1.3.1}/src/hoodscript/tutor.py +0 -0
  116. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/conftest.py +0 -0
  117. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/deep_nesting.hs +0 -0
  118. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/fork_bomb.hs +0 -0
  119. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/huge_literal.hs +0 -0
  120. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/import_escapes.hs +0 -0
  121. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/infinite_recursion.hs +0 -0
  122. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/open_file.hs +0 -0
  123. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/pathological_indent.hs +0 -0
  124. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/runaway_output.hs +0 -0
  125. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/corpus/runaway_time.hs +0 -0
  126. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/test_corpus.py +0 -0
  127. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/test_exec_sites.py +0 -0
  128. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/test_fuzz.py +0 -0
  129. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/security/test_sandbox.py +0 -0
  130. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_conformance.py +0 -0
  131. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_debugger.py +0 -0
  132. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_docs.py +0 -0
  133. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_errors.py +0 -0
  134. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_fmt.py +0 -0
  135. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_grammar_canonical.py +0 -0
  136. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_importer.py +0 -0
  137. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_kernel_zmq.py +0 -0
  138. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_keywords_doc.py +0 -0
  139. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_learn.py +0 -0
  140. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_lint.py +0 -0
  141. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_lsp_features.py +0 -0
  142. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_playground.py +0 -0
  143. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_release.py +0 -0
  144. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_repl.py +0 -0
  145. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_transpiler.py +0 -0
  146. {hoodscript-1.2.0 → hoodscript-1.3.1}/tests/test_vscode_extension.py +0 -0
@@ -3,6 +3,110 @@
3
3
  All notable changes to HoodScript. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
4
  Versioning: the keyword set is the public API; changing it is at least a minor bump.
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [1.3.1] — 2026-09-12
9
+
10
+ Patch release: what the fresh-machine check of 1.3.0 found, plus two audit
11
+ leftovers. No keyword or API changes.
12
+
13
+ ### Fixed
14
+
15
+ - Fresh-machine check of v1.3.0 (`pip install "hoodscript[all]"` in a clean
16
+ venv, `docs/onboarding.md` §2–§11 verbatim): everything passed except
17
+ `hoodfarm test` and `hoodarcade test`, which reported "No Sol" for every
18
+ level and **exited 0** — the reference solutions were not in the wheel and a
19
+ missing solution counted as a pass. The solutions now ship
20
+ (`hoodscript/data/solutions`, found by both CLIs after the repo checkout), a
21
+ missing one is a red `MISSING` row and a non-zero exit, and
22
+ `tests/test_wheel_contents.py` builds the wheel and asserts the runtime
23
+ data (curriculum, `vibe.py`, solutions) is inside it.
24
+
25
+ - `hoodscript --sandbox run f.hs` now works (the flags may come before or after
26
+ `run`; `run` is recognised as the first positional, not only as `argv[0]`).
27
+ - Jupyter: `%hood_tb` is honoured by the error card — the Python traceback
28
+ section opens by default and the plain-text fallback carries it too.
29
+ - MANUAL §16 documents that `hoodscript fmt` writes `ain't nobody x` back as
30
+ `ain't x` (same Python, one canonical spelling).
31
+ - `cli.py` had the `arcade` dispatch block twice; the copy is gone.
32
+
33
+ ## [1.3.0] — 2026-09-12
34
+
35
+ Three features that were on `main` but not on PyPI: the two games and the
36
+ ecosystem sprint. Keyword set unchanged (v1.1); minor bump for the new
37
+ packages and CLI commands.
38
+
39
+ ### Added
40
+
41
+ - `hoodarcade levels [game]`: the stage table for one game (`list` stays the
42
+ all-games form). Added while aligning a stale "Arcade + Ecosystem" prompt
43
+ with the release — the reconciliation is `PROMPTS.md` §3.5.
44
+
45
+ - **The Drone Was Replaced** (`hoodfarm`, `pip install "hoodscript[farm]"`, also
46
+ `hoodscript farm …`): a terminal grid-automation game in the style of *The
47
+ Farmer Was Replaced*. Program a drone (`bot.move/harvest/plant/water/till/
48
+ inspect_ground/measure_water`, `bot.bag/energy/ticks_used`) in HoodScript
49
+ across 8 levels (single-tile harvest → loops → 2D traversal → conditional
50
+ weeding → irrigation → inventory quotas → companion planting → an
51
+ energy-budgeted supply chain). Rich TUI with emoji tiles, telemetry,
52
+ inventory ledger and log console (ASCII fallback); `hoodfarm play <level>`
53
+ hot-reloads `solution.hs` on save; `hoodfarm run --benchmark` and
54
+ `hoodfarm test` run headless with proper exit codes. Deterministic ticks.
55
+ Reference solutions for all 8 levels in `examples/solutions/`.
56
+ - QA on acceptance (external batch): it shipped with a **private game lexicon**
57
+ — `cook`/`serve`/`run_thru`/`Facts` (cut under Rule Zero) and `dip`
58
+ remapped to `break` — so 7 of 8 solutions did not parse under `hoodscript`
59
+ and the game taught a different language. Removed; every starter template
60
+ and solution translated to v1.1 and verified to win through the real
61
+ transpiler. Crashes now go through the JPE engine with the player's line and
62
+ the game state ("drone at (2, 4), energy 0, tick 50"). Cut builtin aliases
63
+ injected as globals (`span`, `lineup`, `ledger`, `count_up`) removed.
64
+ Tests guard all of it (no cut word as a code token anywhere in the game).
65
+
66
+ - **HoodArcade** (`hoodarcade`, `pip install "hoodscript[arcade]"`, also
67
+ `hoodscript arcade …`): three terminal games, five stages each, with Rich
68
+ viewports (grill rack + Spades table, 16-step 808 matrix, SNKRS drop feed),
69
+ hot-reload on save, and progress in `~/.hoodarcade/progress.json`:
70
+ *Big Mama's Cookout* (grill timing, the potato-salad exception test, aux-cord
71
+ sorting, a Spades rule engine, an autonomous Spades partner), *BeatLab 808*
72
+ (four-on-the-floor, trap-hat triplets by modulo, sample chopping, 808 glide
73
+ interpolation, a full arrangement), *Drop Day* (stock monitor, legit-check
74
+ filter, exponential backoff with `tryna`/`catch`, multi-cart, a circular
75
+ trade-graph search). Reference solutions for all 15 stages.
76
+ - QA on acceptance (external batch): same defect as the farm — a **private
77
+ lexicon** (`cook`/`serve`/`run_thru`/`Facts`, `dip`→`break`) meant no stage
78
+ solution ran under `hoodscript`; removed, everything translated to v1.1.
79
+ **Every "starter" template was the finished solution** — a player opened a
80
+ stage already solved; all 15 rewritten as real scaffolds (API shown,
81
+ algorithm left to the player; tests assert scaffolds fail and solutions
82
+ pass). Crashes in player code, including ones the stage checkers catch, now
83
+ surface as JPE reports with the failing line; a checker fed the wrong
84
+ return type fails the stage in plain English instead of raising.
85
+
86
+ - **HPEP 20 — The Zen of HoodScript.** `hoodscript zen` (add `--notes` to see
87
+ which file each line points at) and `import vibe` from any program, like
88
+ `import this`. Nineteen lines, each a decision this repo actually made.
89
+ - **`docs/grammar.ebnf`** — the formal grammar as its own artifact, generated
90
+ from `GRAMMAR.md` by `scripts/gen_ebnf.py`; a test fails if they drift and
91
+ another checks every keyword has a production (that found `ask` missing).
92
+ - **Grammar-directed fuzzing** (`tests/test_fuzz_ebnf.py`, Hypothesis): generates
93
+ valid programs from the grammar and asserts they transpile, compile, keep
94
+ their line count, contain no leaked sentinels or keywords, and that
95
+ `py2hood(transpile(p))` transpiles back to the same Python. Found and fixed
96
+ two pattern-layer bugs on its first run: `x = ask "name? "` was a syntax
97
+ error (R12 only fired at statement start; now anywhere), and `holla (x)` was
98
+ not reversible (whitespace now decides: `holla(x)` is a call, `holla (x)` is
99
+ the statement form). `HYPOTHESIS_PROFILE=thorough` runs 2,000 examples per
100
+ property.
101
+ - **`hoodlib.disk`** — atomic, thread-safe JSON/CSV read/write and backups,
102
+ standard library only (`from hoodlib import disk`). Errors are the Python
103
+ exceptions the crash reporter already explains.
104
+ - **ADR-0004: no regional dialect overlays.** A proposal for Bay Area / Atlanta
105
+ / DMV / MLE token overlays was declined under Rule Zero; a cited, single-
106
+ variety Tier B extension remains possible via the sourcing procedure. Two
107
+ companion proposals (`hoodpkg`/`hood.lock`, and a `hoodlib` with AES-GCM and
108
+ an async HTTP client) were dropped as duplicating Python and its packaging.
109
+
6
110
  ## [1.2.0] — 2026-09-11
7
111
 
8
112
  ### Added
@@ -64,6 +64,15 @@ Before every push:
64
64
  ruff check && pytest
65
65
  ```
66
66
 
67
+ Enforce it locally once per clone — the versioned pre-push hook runs exactly
68
+ this and refuses the push if it fails:
69
+
70
+ ```bash
71
+ ./scripts/install-hooks.sh
72
+ ```
73
+
74
+ (If you pipe `ruff check` into anything, you lose its exit code. Don't.)
75
+
67
76
  That is exactly what CI runs, on Ubuntu, macOS, and Windows × Python
68
77
  3.10–3.13, plus a CLI smoke test. Python's exception *messages* differ between
69
78
  versions (`was never closed` vs `EOF in multi-line statement`), so a catalog
@@ -107,6 +116,16 @@ with a gate. Commit at the gate with a message that says what changed and
107
116
  why; the changelog is written from those messages. Update `CLAUDE.md` when a
108
117
  decision changes — stale context is worse than none.
109
118
 
119
+ ## Landing changes
120
+
121
+ `main` is protected: the Ubuntu/macOS/Windows test legs and the build must be
122
+ green, force-pushes and deletions are refused, and pull requests need a
123
+ code-owner review (`.github/CODEOWNERS`). **Work you did not write yourself —
124
+ an agent's output included — lands as a pull request**, gets `/code-review`
125
+ and the end-to-end audit in [`PROMPTS.md`](PROMPTS.md) §2.8, and only then
126
+ merges. Two batches arrived on 2026-09-11 claiming completion with defects
127
+ their own tests could not see; that is why.
128
+
110
129
  ## Working with an AI agent
111
130
 
112
131
  Paste the standing brief from [`PROMPTS.md`](PROMPTS.md) §0 at the top of
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hoodscript
3
- Version: 1.2.0
3
+ Version: 1.3.1
4
4
  Summary: A production-grade Python 3 conlang based on AAVE with 1:1 AST parity.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -14,10 +14,13 @@ Requires-Dist: prompt-toolkit>=3.0.40; extra == 'all'
14
14
  Requires-Dist: pygls<2,>=1.3.1; extra == 'all'
15
15
  Requires-Dist: rich>=13.7; extra == 'all'
16
16
  Requires-Dist: ruff>=0.3.0; extra == 'all'
17
+ Provides-Extra: arcade
18
+ Requires-Dist: rich>=13.7; extra == 'arcade'
17
19
  Provides-Extra: dev
18
20
  Requires-Dist: build>=1.1.0; extra == 'dev'
19
21
  Requires-Dist: debugpy>=1.8.0; extra == 'dev'
20
22
  Requires-Dist: hatch-vcs>=0.4.0; extra == 'dev'
23
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
21
24
  Requires-Dist: ipykernel>=6.0.0; extra == 'dev'
22
25
  Requires-Dist: ipython>=8.0.0; extra == 'dev'
23
26
  Requires-Dist: jedi>=0.19.1; extra == 'dev'
@@ -28,6 +31,8 @@ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
28
31
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
29
32
  Requires-Dist: rich>=13.7; extra == 'dev'
30
33
  Requires-Dist: ruff>=0.3.0; extra == 'dev'
34
+ Provides-Extra: farm
35
+ Requires-Dist: rich>=13.7; extra == 'farm'
31
36
  Provides-Extra: jupyter
32
37
  Requires-Dist: ipykernel>=6.0.0; extra == 'jupyter'
33
38
  Requires-Dist: ipython>=8.0.0; extra == 'jupyter'
@@ -108,6 +113,7 @@ hoodscript kernel install # Jupyter kernel (pip install "hoodscript[jupyter]
108
113
  hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]")
109
114
  hoodscript learn [topic] # interactive tutor driving docs/curriculum.md
110
115
  hoodscript # a short tour of all commands
116
+ hoodscript zen # the Zen of HoodScript (HPEP 20); or `import vibe`
111
117
  hoodscript lsp # language server over stdio, for editors
112
118
  hoodscript make-stubs f.py # emit a .pyi-style stub with empty bodies
113
119
  ```
@@ -167,6 +173,24 @@ Everything else — `if`, `else`, `while`, `import`, `with`, `match`, `None`,
167
173
  `self`, every builtin — is Python's own word, and every Python keyword still
168
174
  works in a `.hs` file.
169
175
 
176
+ ## Play
177
+
178
+ ```bash
179
+ pip install "hoodscript[farm]"
180
+ hoodfarm levels # the 8 challenges
181
+ hoodfarm play 1 # edit solution.hs in your editor; the farm re-runs on every save
182
+ hoodfarm run my.hs -b # headless benchmark, exit 0 on pass
183
+ ```
184
+
185
+ *The Drone Was Replaced*: program a farming drone in HoodScript, one level at
186
+ a time. Same language, same crash reports — the game just adds a `bot`.
187
+
188
+ ```bash
189
+ pip install "hoodscript[arcade]"
190
+ hoodarcade # Big Mama's Cookout · BeatLab 808 · Drop Day
191
+ hoodarcade play cookout 1 # edit the solution file; the stage re-runs on save
192
+ ```
193
+
170
194
  ## Learn
171
195
 
172
196
  - [`docs/onboarding.md`](docs/onboarding.md) — from zero to a running program in ten minutes
@@ -63,6 +63,7 @@ hoodscript kernel install # Jupyter kernel (pip install "hoodscript[jupyter]
63
63
  hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]")
64
64
  hoodscript learn [topic] # interactive tutor driving docs/curriculum.md
65
65
  hoodscript # a short tour of all commands
66
+ hoodscript zen # the Zen of HoodScript (HPEP 20); or `import vibe`
66
67
  hoodscript lsp # language server over stdio, for editors
67
68
  hoodscript make-stubs f.py # emit a .pyi-style stub with empty bodies
68
69
  ```
@@ -122,6 +123,24 @@ Everything else — `if`, `else`, `while`, `import`, `with`, `match`, `None`,
122
123
  `self`, every builtin — is Python's own word, and every Python keyword still
123
124
  works in a `.hs` file.
124
125
 
126
+ ## Play
127
+
128
+ ```bash
129
+ pip install "hoodscript[farm]"
130
+ hoodfarm levels # the 8 challenges
131
+ hoodfarm play 1 # edit solution.hs in your editor; the farm re-runs on every save
132
+ hoodfarm run my.hs -b # headless benchmark, exit 0 on pass
133
+ ```
134
+
135
+ *The Drone Was Replaced*: program a farming drone in HoodScript, one level at
136
+ a time. Same language, same crash reports — the game just adds a `bot`.
137
+
138
+ ```bash
139
+ pip install "hoodscript[arcade]"
140
+ hoodarcade # Big Mama's Cookout · BeatLab 808 · Drop Day
141
+ hoodarcade play cookout 1 # edit the solution file; the stage re-runs on save
142
+ ```
143
+
125
144
  ## Learn
126
145
 
127
146
  - [`docs/onboarding.md`](docs/onboarding.md) — from zero to a running program in ten minutes
@@ -0,0 +1,45 @@
1
+ # ADR-0004: No Regional Dialect Overlays (Rule Zero Stands)
2
+
3
+ ## Status
4
+ Accepted (2026-09-12)
5
+
6
+ ## Context
7
+ A proposal asked for a "modular regional dialect engine": token overlays for
8
+ Bay Area, Atlanta, DMV, and UK Drill / MLE slang, selectable via `hood.toml` or
9
+ `--dialect`, resolving to standard HoodScript tokens before transpilation.
10
+
11
+ ## Decision
12
+ **Not adopted.** The keyword set is governed by Rule Zero and the sourcing
13
+ procedure in `hoodscript/docs/lexical-sourcing.md` §5. A dialect overlay is a
14
+ keyword table; every entry in it would need the same evidence as every other
15
+ keyword, and an owner-signed `GRAMMAR.md` revision. Four overlays of roughly
16
+ fifteen words each is four times the size of the entire v1.1 reconciliation.
17
+
18
+ Three further reasons:
19
+
20
+ 1. **It grows the wrong tier.** The language's thesis is Tier A — AAVE aspect
21
+ marking (`be`, `steady`, `BIN`, `done`) mapped onto execution semantics.
22
+ Overlays add only Tier B naming, which was deliberately capped small:
23
+ "every alias is a thing to learn, and 1:1 legibility suffers as aliases
24
+ grow." Regional slang is also the most perishable vocabulary there is,
25
+ which is exactly what Rule Zero excludes.
26
+ 2. **The mechanism is the known failure mode.** "Resolve dialect tokens to
27
+ standard tokens first" is the private-keyword-map pattern that three
28
+ external batches reintroduced in one day (2026-09-11/12), two of which
29
+ silently changed the meaning of `dip`. See CHANGELOG, QA sections.
30
+ 3. **A player who learns a dialect learns a language that runs nowhere else.**
31
+ The 1:1 promise is that what you write in HoodScript is HoodScript.
32
+
33
+ ## The door that stays open
34
+ The named varieties are real objects of sociolinguistic study. A *cited*
35
+ extension — one variety, each word through the seven steps, `UNVERIFIED`
36
+ where the literature is thin, signed by the owner — would be a legitimate
37
+ Tier B addition and can be proposed with `PROMPTS.md` §2.1. It is a sourcing
38
+ sprint first and a loader second, if ever.
39
+
40
+ ## Consequences
41
+ - No `--dialect` flag, no dialect section in any config, no overlay loader.
42
+ - `constants.py` remains the single keyword table; `patterns.py` the single
43
+ multi-token layer.
44
+ - Proposals that arrive with a dialect map are declined at review with a
45
+ pointer to this ADR.
@@ -0,0 +1,59 @@
1
+ (* HoodScript v1.1 — formal grammar (EBNF).
2
+ Descriptive: the implementation is a token rewrite (src/hoodscript/transpiler.py)
3
+ plus the pattern rules R1–R12 (src/hoodscript/patterns.py), not a parser.
4
+ This file is extracted verbatim from GRAMMAR.md; tests/test_grammar_ebnf.py
5
+ fails if the two drift. Edit GRAMMAR.md, then regenerate:
6
+ python scripts/gen_ebnf.py > docs/grammar.ebnf
7
+ *)
8
+
9
+ program = { statement } ;
10
+
11
+ statement = output_stmt | assignment | const_decl | func_def | class_def
12
+ | return_stmt | habitual_loop | continuative_loop | while_loop
13
+ | conditional | try_stmt | throw_stmt | import_stmt
14
+ | loop_control | pass_stmt | python_stmt | expr_stmt ;
15
+
16
+ output_stmt = "holla" [ expression { "," expression } | "(" [ args ] ")" ] ;
17
+ input_expr = "ask" ( expression | "(" [ args ] ")" ) ;
18
+ assignment = target "=" expression ;
19
+ const_decl = "BIN" IDENT "=" expression ;
20
+
21
+ func_def = [ "finna" ] "bet" IDENT "(" [ params ] ")" ":" block ;
22
+ return_stmt = ( "dip" | "return" ) [ expression ] ;
23
+ class_def = "fam" IDENT [ "(" IDENT { "," IDENT } ")" ] ":" block ;
24
+
25
+ habitual_loop = "be" target "in" expression ":" block ;
26
+ continuative_loop = "steady" ":" block ;
27
+ while_loop = "while" expression ":" block ;
28
+ loop_control = "skip" | "break" ;
29
+ pass_stmt = "chill" ;
30
+
31
+ conditional = "if" expression ":" block
32
+ { "else" "if" expression ":" block }
33
+ [ "else" ":" block ] ;
34
+
35
+ try_stmt = "tryna" ":" block { handler } [ "regardless" ":" block ] ;
36
+ handler = "catch" [ expression [ "as" IDENT ] ] ":" block ;
37
+ throw_stmt = "throw" [ expression ] ;
38
+
39
+ import_stmt = "import" dotted [ "as" IDENT ]
40
+ | "from" dotted "import" IDENT { "," IDENT } ;
41
+
42
+ python_stmt = (* any Python statement: with, match, del, global, nonlocal,
43
+ assert, yield, decorators, comprehensions — passed through *) ;
44
+
45
+ block = INDENT { statement } DEDENT ;
46
+
47
+ expression = or_expr ;
48
+ or_expr = and_expr { "or" and_expr } ;
49
+ and_expr = not_expr { "and" not_expr } ;
50
+ not_expr = [ "ain't" [ "nobody" ] | "not" ] comparison ;
51
+ comparison = term { comp_op term } ;
52
+ primary = NUMBER | STRING | IDENT
53
+ | "cap" | "no" "cap" | "None"
54
+ | "it's" expression
55
+ | "done" expression
56
+ | input_expr
57
+ | "(" expression ")"
58
+ | list_lit | dict_lit | range_lit | python_expr ;
59
+ range_lit = expression ".." expression ;
@@ -0,0 +1,10 @@
1
+ # Stage 1: Four on the Floor
2
+ # Instructions: Load 16-step patterns for 'Kick' and 'Snare'.
3
+
4
+ kicks = [0, 4, 8, 12]
5
+ snares = [4, 12]
6
+
7
+ sequencer.load_track("Kick", kicks)
8
+ sequencer.load_track("Snare", snares)
9
+ sequencer.play()
10
+ holla("Classic 4-on-the-floor sequence loaded!")
@@ -0,0 +1,15 @@
1
+ # Stage 2: Trap Hat Triplets
2
+ # Instructions: Generate hi-hat steps combining straight 8ths and a triplet roll.
3
+
4
+ hats = []
5
+ # Standard 8th notes on even steps 0 to 10
6
+ be step in range(12):
7
+ if step % 2 == 0:
8
+ hats.append(step)
9
+
10
+ # Triplet roll on turnaround steps 12 to 15
11
+ be step in range(12, 16):
12
+ hats.append(step)
13
+
14
+ sequencer.load_track("HiHat", hats)
15
+ holla("Trap hats rolling across matrix: ", hats)
@@ -0,0 +1,11 @@
1
+ # Stage 3: Soul Sample Chopper
2
+ # Instructions: Chop 16 consecutive 1000ms slices onto pads 1 to 16, reversing every 4th pad.
3
+
4
+ be i in range(1, 17):
5
+ start_ms = (i - 1) * 1000
6
+ end_ms = i * 1000
7
+ mpc.chop_sample(start_ms, end_ms, i)
8
+ if i % 4 == 0:
9
+ mpc.reverse_pad(i)
10
+
11
+ holla("Vinyl chopped and reversed into 16 quantized MPC pads!")
@@ -0,0 +1,14 @@
1
+ # Stage 4: 808 Slide Calculus
2
+ # Instructions: Define calculate_808_glide(start_note, end_note, steps) returning linear frequency curve.
3
+
4
+ bet calculate_808_glide(start_note, end_note, steps):
5
+ freqs = {"C1": 32.70, "D1": 36.71, "E1": 41.20, "F1": 43.65, "G1": 49.00, "A1": 55.00}
6
+ f_start = freqs[start_note]
7
+ f_end = freqs[end_note]
8
+
9
+ result = []
10
+ step_delta = (f_end - f_start) / (steps - 1)
11
+ be i in range(steps):
12
+ result.append(round(f_start + (i * step_delta), 2))
13
+
14
+ dip result
@@ -0,0 +1,13 @@
1
+ # Stage 5: The Metro Drop
2
+ # Instructions: Assemble full track arrangement, drop producer tag, and render final WAV.
3
+
4
+ bet produce_metro_drop(mpc, sequencer, mastering):
5
+ mpc.set_bpm(144)
6
+
7
+ sequencer.load_track("Kick", [0, 6, 8, 14])
8
+ sequencer.load_track("Snare", [4, 12])
9
+ sequencer.load_track("808", [0, 6, 8, 14])
10
+ sequencer.load_track("HiHat", [0, 2, 4, 6, 8, 10, 12, 13, 14, 15])
11
+
12
+ mastering.drop_tag("metro_tag.wav", 4)
13
+ mastering.render_wav("hit_single_master.wav")
@@ -0,0 +1,9 @@
1
+ # Stage 1: Grill Master Basics
2
+ # Instructions: Loop through all grill items, season with Lawry's, and flip twice so they are cooked!
3
+
4
+ items = grill.get_items()
5
+ be item in items:
6
+ grill.season(item, "Lawry's Seasoned Salt")
7
+ grill.flip(item)
8
+ grill.flip(item)
9
+ holla("Cooked to perfection: ", item)
@@ -0,0 +1,8 @@
1
+ # Stage 2: The Potato Salad Litmus Test
2
+ # Instructions: Validate ingredients. Crash out with BadValueTrip if raisins are present!
3
+
4
+ bet potato_salad_check(recipe):
5
+ if "raisins" in recipe:
6
+ throw BadValueTrip("Who put raisins in Big Mama's potato salad?!")
7
+ else:
8
+ dip no cap
@@ -0,0 +1,5 @@
1
+ # Stage 3: Aux Cord Rotation
2
+ # Instructions: Sort song list descending by vibe score using sorted and quick_cook.
3
+
4
+ bet curate_aux_rotation(songs):
5
+ dip sorted(songs, key=lambda s: s["vibe"], reverse=no cap)
@@ -0,0 +1,26 @@
1
+ # Stage 4: Spades Rule Engine
2
+ # Instructions: Determine winning card between card1 and card2 given led_suit.
3
+
4
+ bet spades_trick_winner(led_suit, card1, card2):
5
+ suit1 = card1[-1]
6
+ suit2 = card2[-1]
7
+
8
+ if suit1 == "♠" and suit2 != "♠":
9
+ dip card1
10
+ else if suit2 == "♠" and suit1 != "♠":
11
+ dip card2
12
+
13
+ ranks = {"2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10, "J": 11, "Q": 12, "K": 13, "A": 14}
14
+ rank1 = ranks[card1[:-1]]
15
+ rank2 = ranks[card2[:-1]]
16
+
17
+ if suit1 == suit2:
18
+ if rank1 > rank2:
19
+ dip card1
20
+ else:
21
+ dip card2
22
+
23
+ if suit1 == led_suit:
24
+ dip card1
25
+ else:
26
+ dip card2
@@ -0,0 +1,13 @@
1
+ # Stage 5: The Unbeatable Partner
2
+ # Instructions: Count books, bid a Boston (13), and play all winning cards in sequence!
3
+
4
+ bet autonomous_spades_partner(spades):
5
+ books = spades.count_books()
6
+ spades.bid(13)
7
+
8
+ hand = spades.get_hand()
9
+ be c in hand:
10
+ if spades.verify_renege(c) == cap:
11
+ spades.play_card(c)
12
+
13
+ dip 13
@@ -0,0 +1,13 @@
1
+ # Stage 1: Drop Monitor
2
+ # Instructions: Check stock. When available, solve captcha and checkout!
3
+
4
+ bet monitor_and_cop(bot, sku):
5
+ stock = bot.listen_drop(sku)
6
+ if stock > 0:
7
+ bot.solve_captcha("TOKEN_X77")
8
+ billing = {"card_num": "4111222233334444", "cvv": "123"}
9
+ bot.add_to_cart(sku, 10.5)
10
+ bot.checkout(billing)
11
+ dip no cap
12
+ else:
13
+ dip cap
@@ -0,0 +1,14 @@
1
+ # Stage 2: Legit Check AI
2
+ # Instructions: Inspect each shoe ledger and collect all authentic pairs.
3
+
4
+ bet filter_authentic_kicks(shoes):
5
+ legit_pairs = []
6
+ be s in shoes:
7
+ is_density_valid = s["stitch_density"] >= 8.5
8
+ is_uv_valid = s["uv_box_stamp"] == no cap
9
+ is_sku_valid = s["sku_label_matches"] == no cap
10
+
11
+ if is_density_valid and is_uv_valid and is_sku_valid:
12
+ legit_pairs.append(s)
13
+
14
+ dip legit_pairs
@@ -0,0 +1,12 @@
1
+ # Stage 3: Anti-Rate Limit Backoff
2
+ # Instructions: Query endpoint with retry loop catching RateLimitError with increasing delays.
3
+
4
+ bet fetch_with_backoff(endpoint, sku, max_retries):
5
+ delay = 0.01
6
+ be attempt_num in range(max_retries):
7
+ tryna:
8
+ dip endpoint.query_stock(sku)
9
+ catch:
10
+ delay = delay * 2
11
+
12
+ dip None
@@ -0,0 +1,11 @@
1
+ # Stage 4: Asynchronous Multi-Cart Engine
2
+ # Instructions: Add all target sizes to cart, solve captcha, and checkout.
3
+
4
+ bet multi_cart_cop(bot, sku, sizes):
5
+ be s in sizes:
6
+ bot.add_to_cart(sku, s)
7
+
8
+ bot.solve_captcha("CAPTCHA_FAST_PASS")
9
+ billing = {"card_num": "5500112233445566", "cvv": "999"}
10
+ bot.checkout(billing)
11
+ dip no cap
@@ -0,0 +1,16 @@
1
+ # Stage 5: The 3-Way Grail Trade
2
+ # Instructions: Trace the trade dependencies from starting item to target grail.
3
+
4
+ bet solve_grail_trade_cycle(traders, start_item, target_grail):
5
+ cycle = []
6
+ current_item = start_item
7
+
8
+ be _ in range(len(traders)):
9
+ be name in traders:
10
+ t = traders[name]
11
+ if t["has"] == current_item:
12
+ cycle.append(name)
13
+ current_item = t["wants"]
14
+ break
15
+
16
+ dip cycle
@@ -0,0 +1,9 @@
1
+ # Level 1: First Harvest
2
+ # Instructions: Step east across plots and harvest 3 hay.
3
+
4
+ bot.harvest()
5
+ bot.move("east")
6
+ bot.harvest()
7
+ bot.move("east")
8
+ bot.harvest()
9
+ holla("Level 1 complete!")
@@ -0,0 +1,9 @@
1
+ # Level 2: The Infinite Row
2
+ # Instructions: Use `be` and a range to harvest all 8 plots in a single loop.
3
+
4
+ be i in range(8):
5
+ bot.harvest()
6
+ if i < 7:
7
+ bot.move("east")
8
+
9
+ holla("All 8 hay harvested!")
@@ -0,0 +1,14 @@
1
+ # Level 3: Field of 9
2
+ # Instructions: Nested loops to traverse and harvest 3x3 grid.
3
+
4
+ be y in range(3):
5
+ be x in range(3):
6
+ bot.harvest()
7
+ if x < 2:
8
+ bot.move("east")
9
+ if y < 2:
10
+ bot.move("south")
11
+ bot.move("west")
12
+ bot.move("west")
13
+
14
+ holla("3x3 field cleared!")
@@ -0,0 +1,18 @@
1
+ # Level 4: Weed Control
2
+ # Instructions: Inspect soil. If weed, till it; otherwise harvest mature hay!
3
+
4
+ be y in range(4):
5
+ be x in range(4):
6
+ ground = bot.inspect_ground()
7
+ if ground["is_weed"]:
8
+ bot.till()
9
+ else:
10
+ bot.harvest()
11
+ if x < 3:
12
+ bot.move("east")
13
+ if y < 3:
14
+ bot.move("south")
15
+ be _ in range(3):
16
+ bot.move("west")
17
+
18
+ holla("Level 4 weed control completed!")
@@ -0,0 +1,38 @@
1
+ # Level 5: Smart Irrigation & Soil
2
+ # Instructions: Till, hydrate, and plant carrots, then harvest once mature.
3
+
4
+ bet prepare_and_plant():
5
+ bot.till()
6
+ bot.water(0.40)
7
+ bot.plant("carrot")
8
+
9
+ # Step 1: Prepare and seed the 4x4 field
10
+ be y in range(4):
11
+ be x in range(4):
12
+ prepare_and_plant()
13
+ if x < 3:
14
+ bot.move("east")
15
+ if y < 3:
16
+ bot.move("south")
17
+ be _ in range(3):
18
+ bot.move("west")
19
+
20
+ # Step 2: Return drone to top-left (0, 0)
21
+ be _ in range(3):
22
+ bot.move("west")
23
+ bot.move("north")
24
+
25
+ # Step 3: Harvest all carrots once mature
26
+ be y in range(4):
27
+ be x in range(4):
28
+ while cap == bot.inspect_ground()["is_mature"]:
29
+ bot.water(0.1)
30
+ bot.harvest()
31
+ if x < 3:
32
+ bot.move("east")
33
+ if y < 3:
34
+ bot.move("south")
35
+ be _ in range(3):
36
+ bot.move("west")
37
+
38
+ holla("All 16 carrots harvested!")