hoodscript 1.3.1__tar.gz → 1.4.0__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.
- hoodscript-1.4.0/.claude/skills/align-prompt/SKILL.md +32 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/CHANGELOG.md +100 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/CONTRIBUTING.md +7 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/PKG-INFO +6 -2
- {hoodscript-1.3.1 → hoodscript-1.4.0}/README.md +5 -1
- {hoodscript-1.3.1 → hoodscript-1.4.0}/SECURITY.md +3 -2
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/adr/0002-keyword-reconciliation.md +11 -5
- hoodscript-1.4.0/docs/hoodlib.md +105 -0
- hoodscript-1.4.0/scripts/align_prompt.py +288 -0
- hoodscript-1.4.0/scripts/coraal_hits.py +123 -0
- hoodscript-1.4.0/scripts/hooks/pre-commit +13 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodlib/__init__.py +2 -2
- hoodscript-1.4.0/src/hoodlib/chart.py +167 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/_version.py +2 -2
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/cli.py +18 -4
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/linter.py +96 -0
- hoodscript-1.4.0/src/hoodscript/migrate.py +146 -0
- hoodscript-1.4.0/src/hoodscript/replay.py +325 -0
- hoodscript-1.4.0/src/hoodscript/sourcing.py +410 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/test_exec_sites.py +1 -1
- hoodscript-1.4.0/tests/test_align_prompt.py +108 -0
- hoodscript-1.4.0/tests/test_chart.py +91 -0
- hoodscript-1.4.0/tests/test_coraal_hits.py +74 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_docs.py +1 -1
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_fmt.py +1 -1
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_lint.py +54 -0
- hoodscript-1.4.0/tests/test_migrate_sourcing.py +235 -0
- hoodscript-1.4.0/tests/test_replay.py +125 -0
- hoodscript-1.4.0/tests/test_rule_zero.py +112 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/.gitignore +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/CODE_OF_CONDUCT.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/LICENSE +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/SPRINT_PLAN.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/adr/0001-token-pipeline-over-parser.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/adr/0003-inclusive-ranges.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/adr/0004-no-regional-dialect-overlays.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/adr/README.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/archive/HOODSCRIPT_COMPLETE_MASTER_SYSTEM.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/archive/HOODSCRIPT_FULL_ENGINEERING_SUITE.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/archive/HOODSCRIPT_UNSPRED_FEATURES_ROADMAP.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/curriculum.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/grammar.ebnf +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/keywords.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/docs/onboarding.md +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/quickstart.ipynb +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/beatlab/stage1.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/beatlab/stage2.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/beatlab/stage3.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/beatlab/stage4.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/beatlab/stage5.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/cookout/stage1.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/cookout/stage2.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/cookout/stage3.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/cookout/stage4.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/cookout/stage5.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/dropday/stage1.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/dropday/stage2.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/dropday/stage3.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/dropday/stage4.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/dropday/stage5.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level1.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level2.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level3.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level4.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level5.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level6.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level7.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/examples/solutions/level8.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/out.txt +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/app.js +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/bundle.js +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/examples.js +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/hoodscript-latest-py3-none-any.whl +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/index.html +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/style.css +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/playground/worker.js +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/pyproject.toml +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/scripts/build_playground.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/scripts/gen_ebnf.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/scripts/gen_keywords.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/scripts/hooks/pre-push +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/scripts/install-hooks.sh +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/__init__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/cli.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/core/runtime.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/core/ui.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/games/beatlab.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/games/cookout.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodarcade/games/dropday.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/__init__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/cli.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/drone.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/grid.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/levels.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/renderer.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodfarm/runner.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodlib/disk.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/__init__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/__main__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/cache.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/constants.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/debugger.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/errors/__init__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/errors/catalog.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/errors/renderer.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/errors/translator.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/formatter.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/importer.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/kernel/__init__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/kernel/__main__.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/kernel/installer.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/kernel/kernel.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/kernel/magics.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/lsp.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/migrator.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/patterns.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/repl.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/sandbox.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/stubs.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/traceback_handler.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/transpiler.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/tutor.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/hoodscript/zen.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/src/vibe.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/conftest.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/deep_nesting.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/fork_bomb.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/huge_literal.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/import_escapes.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/infinite_recursion.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/open_file.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/pathological_indent.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/runaway_output.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/corpus/runaway_time.hs +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/test_corpus.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/test_fuzz.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/security/test_sandbox.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_arcade.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_arcade_gates.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_cli.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_conformance.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_debugger.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_errors.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_fuzz_ebnf.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_game_engine.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_grammar_canonical.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_grammar_ebnf.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_hoodlib_disk.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_importer.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_kernel.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_kernel_zmq.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_keywords_doc.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_learn.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_lsp_features.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_patterns.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_playground.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_release.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_repl.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_transpiler.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_vscode_extension.py +0 -0
- {hoodscript-1.3.1 → hoodscript-1.4.0}/tests/test_wheel_contents.py +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: align-prompt
|
|
3
|
+
description: Align a pasted generation prompt (from Gemini or anywhere) with the current HoodScript release before implementing it. Use whenever the user pastes a prompt that names files, keywords, or features to build.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# align-prompt
|
|
7
|
+
|
|
8
|
+
The user pastes a generation prompt. Do not execute it as written. Do this:
|
|
9
|
+
|
|
10
|
+
1. Save the pasted prompt verbatim to `$SCRATCHPAD/prompt-in.md`.
|
|
11
|
+
2. Run the mechanical half:
|
|
12
|
+
```
|
|
13
|
+
python scripts/align_prompt.py $SCRATCHPAD/prompt-in.md -o $SCRATCHPAD/prompt-aligned.md --report $SCRATCHPAD/prompt-report.md
|
|
14
|
+
```
|
|
15
|
+
It rewrites the v1.0.0 lexicon to v1.1, swaps stale phrasing/paths/commands,
|
|
16
|
+
prefixes lines that ask for declined/dropped work with `[DECLINED …]` /
|
|
17
|
+
`[DROPPED …]` / `[NEVER …]`, and prepends the PROMPTS.md §0 brief.
|
|
18
|
+
3. Do the judgment half — diff the aligned scope against the tree:
|
|
19
|
+
- `git ls-files src tests examples docs`, CHANGELOG `[Unreleased]` and the
|
|
20
|
+
latest release section, `CLAUDE.md` "Current status".
|
|
21
|
+
- For each item in the aligned prompt, classify: **shipped** (on `main`,
|
|
22
|
+
cite the path), **declined/dropped** (cite ADR or CHANGELOG), **gap**
|
|
23
|
+
(not on disk — the only thing to implement), or **conflict** (asks for
|
|
24
|
+
something the standing brief forbids — say so, don't build it).
|
|
25
|
+
4. Show the user a short table (item → status → evidence) and the report's
|
|
26
|
+
flagged lines. Then implement only the **gap** rows, one sprint, under the
|
|
27
|
+
gate (`ruff check && pytest`), on the current branch rules (agents.md).
|
|
28
|
+
5. If the aligned prompt is worth keeping, record the reconciliation as a
|
|
29
|
+
PROMPTS.md §3.x section like §3.5 — mapping table + the short correct prompt.
|
|
30
|
+
|
|
31
|
+
Never: reintroduce `cook`/`serve`/`Facts`/`holler`/`libcst`; add a keyword;
|
|
32
|
+
build a dialect overlay (ADR-0004); create a tag.
|
|
@@ -5,6 +5,106 @@ Versioning: the keyword set is the public API; changing it is at least a minor b
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.4.0] — 2026-09-12
|
|
9
|
+
|
|
10
|
+
New library module, new CLI command, three new lint rules, and the prompt
|
|
11
|
+
aligner. Keyword set unchanged (v1.1).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Sourcing audit sprint** (PROMPTS.md §2.9, worked record §3.7). Every
|
|
16
|
+
keyword, pattern form, and the `*Trip` suffix re-run through
|
|
17
|
+
`lexical-sourcing.md` §5 with the sources open rather than remembered:
|
|
18
|
+
the nine Yale GDP pages fetched and quoted; Green's Dictionary of Slang
|
|
19
|
+
read entry by entry (variety labels and first-quotation years now in the
|
|
20
|
+
table); CORAAL grepped for the first time — 231 interviews, 1.39 M tokens,
|
|
21
|
+
via a mirror because the Oregon host refused connections; the Urban
|
|
22
|
+
Dictionary liability screen run on all Tier B words. Results and
|
|
23
|
+
corrections: `hoodscript/docs/linguistics.md` "Sourcing audit —
|
|
24
|
+
2026-09-12". Decisions for the owner: GRAMMAR.md "Sourcing audit
|
|
25
|
+
2026-09-12 — DRAFT" (`cap`/`no cap` liability + 2018 dating; `trip` now
|
|
26
|
+
two-source; `dip` meets the corpus clause; `tryna` re-ranked; Rickford →
|
|
27
|
+
Labov on the `ain't` row). No keyword changed.
|
|
28
|
+
- **`hoodscript migrate <file.hs>`** — rewrites v1.0.0 code to the v1.1
|
|
29
|
+
lexicon from the CHANGELOG migration table: two line-preserving token
|
|
30
|
+
passes (v1.0.0 map → Python → `py2hood`), so `cook`→`bet`, `serve`→`dip`,
|
|
31
|
+
`holler(x)`→`holla x`, `Facts`→`no cap`, `GhostFileTrip`→`MissingFileTrip`,
|
|
32
|
+
the 21 builtin aliases → their Python names. Auto-detects v1.0.0 by its
|
|
33
|
+
exclusive tokens (`dip` meant `break` there — a v1.1 file is left alone
|
|
34
|
+
with a note; `--from 1.0` forces). `-w` writes with a `.bak`; `--diff`.
|
|
35
|
+
Refuses to write if the line count changed. `align_prompt.py` fixed
|
|
36
|
+
prompts; this fixes code.
|
|
37
|
+
- **`hoodscript sourcing <word>`** — Rule Zero as one command
|
|
38
|
+
(`src/hoodscript/sourcing.py`): standing (keyword / cut / candidate, with
|
|
39
|
+
the `linguistics.md` rows when run from a checkout), Green's Dictionary of
|
|
40
|
+
Slang senses with variety labels and first-quotation years, the Urban
|
|
41
|
+
Dictionary liability screen by rank, CORAAL hits + KWIC with `--coraal`,
|
|
42
|
+
and the checklist of steps no script can do. `--offline`, `--json`. The
|
|
43
|
+
cut list now lives in `sourcing.CUT` (one source for the command and
|
|
44
|
+
`tests/test_rule_zero.py`); `scripts/coraal_hits.py` imports the corpus
|
|
45
|
+
code from here.
|
|
46
|
+
- `scripts/coraal_hits.py` — step 3 of the sourcing procedure as a tool:
|
|
47
|
+
counts every keyword in official CORAAL text files or a JSONL mirror,
|
|
48
|
+
`--kwic` prints the lines so senses can be read, not guessed.
|
|
49
|
+
- `tests/test_rule_zero.py` — the release package's constitutional test
|
|
50
|
+
(the prototype's `test_one_to_one_law.py` read the prototype's table and
|
|
51
|
+
never guarded `constants.py`): cut words stay cut (the full v1.0 + v1.1 +
|
|
52
|
+
alias cut list), tiers are disjoint, Tier B ≤ 9, every Tier A/B keyword
|
|
53
|
+
has a row in `linguistics.md`, Urban Dictionary never appears in a
|
|
54
|
+
citation row, the docs mention every keyword, and the screen matches red
|
|
55
|
+
flags on word boundaries.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- `hoodscript/scripts/screen_keyword.py` flagged `something` for `meth` and
|
|
60
|
+
`essex` for `sex` (substring matching); now word-boundary regexes. The UD
|
|
61
|
+
API has returned no vote counts since 2024, so the script reports UD's
|
|
62
|
+
own rank instead of a meaningless `score +0`.
|
|
63
|
+
- `hoodscript/docs/lexical-sourcing.md` §5 step 7 sent contributors to the
|
|
64
|
+
frozen prototype's `KEYWORD_TO_AST`; now `src/hoodscript/constants.py` +
|
|
65
|
+
`tests/test_conformance.py` + `docs/keywords.md`. Green's Dictionary of
|
|
66
|
+
Slang added to the Tier 3 table as the first dictionary to check.
|
|
67
|
+
- `docs/adr/0002` listed `tryna` and `chill` as Tier C (they are A and B);
|
|
68
|
+
CLAUDE.md/agents.md said 16 exception names (17); GRAMMAR.md's `*Trip`
|
|
69
|
+
list lacked `BadImportTrip`.
|
|
70
|
+
|
|
71
|
+
- **Creative-DX sprint** (from a pasted "Horizon & Creative DX" prompt, aligned
|
|
72
|
+
via `scripts/align_prompt.py`; reconciliation in PROMPTS.md §3.6). Built:
|
|
73
|
+
- **`hoodlib.chart`** — Braille/block sparklines, bar charts, histograms,
|
|
74
|
+
`show_bag()` for game inventories. Standard library only; every function
|
|
75
|
+
returns a string; empty/non-numeric input is a plain `BadValueTrip` /
|
|
76
|
+
`WrongKindTrip`. Documented with runnable examples in `docs/hoodlib.md`
|
|
77
|
+
(now part of the docs-are-tests set, together with `hoodlib.disk`).
|
|
78
|
+
- **`hoodscript replay <file.hs>`** — run a program under `sys.settrace`,
|
|
79
|
+
then step through it backwards: `f`/`b`, `j N`, `l N` (first step at a
|
|
80
|
+
line), `v` (locals), `w` (caller chain). Snapshots are deep copies;
|
|
81
|
+
uncopyable values are stored as `repr`; `--max-steps` caps memory;
|
|
82
|
+
`--dump` prints the trace for pipes. A crash is reported by the JPE
|
|
83
|
+
engine and the prompt opens *at* the crash so `b` steps back from it.
|
|
84
|
+
The 1:1 line law is what makes this cheap: trace lines are `.hs` lines.
|
|
85
|
+
- **Lint HL004–HL006** — exact `ast` rules, no tool needed: a secret-looking
|
|
86
|
+
name assigned a real-looking string literal (placeholders and
|
|
87
|
+
`os.environ` reads are ignored); nesting deeper than four blocks inside
|
|
88
|
+
a function (one finding per chain); a function that is one big `if`
|
|
89
|
+
(guard-clause opportunity). Plain-English what/why/fix like HL001–HL003.
|
|
90
|
+
Declined from the same prompt, with reasons: a `pass_to` pipe operator
|
|
91
|
+
(a new keyword without a citation, and `g(f(x))` rewriting is not one
|
|
92
|
+
construct per construct — Rule Zero and the 1:1 law), voice-to-code
|
|
93
|
+
(`hoodscript mic`: a speech stack the zero-dep core forbids, and a spoken
|
|
94
|
+
dialect map — ADR-0004 in audio). Dropped: BeatMode audio cues (not a
|
|
95
|
+
language feature, untestable in CI), a `hoodlib.tui` DSL over Rich (Rich is
|
|
96
|
+
the DSL), `hoodlib.collections` query wrappers (comprehensions, `sorted`,
|
|
97
|
+
`itertools.groupby`, `Counter`), and a 0–10 "Vibe Score" (not actionable;
|
|
98
|
+
its real rules became HL004–HL006).
|
|
99
|
+
|
|
100
|
+
- `scripts/align_prompt.py` + `/align-prompt` skill: rewrites a stale
|
|
101
|
+
generation prompt to the current release (v1.1 lexicon, line law wording,
|
|
102
|
+
current paths/commands), marks lines that ask for declined or dropped work
|
|
103
|
+
(`[DECLINED — ADR-0004]`, `[DROPPED …]`, `[NEVER — libcst]`), prepends the
|
|
104
|
+
PROMPTS.md §0 brief, and prints a change report. Prose is left alone —
|
|
105
|
+
English words that are also v1.0.0 tokens (`say`, `cut`, `word`, `mine`…)
|
|
106
|
+
are rewritten only in code context. Idempotent. PROMPTS.md §3.5.
|
|
107
|
+
|
|
8
108
|
## [1.3.1] — 2026-09-12
|
|
9
109
|
|
|
10
110
|
Patch release: what the fresh-machine check of 1.3.0 found, plus two audit
|
|
@@ -126,6 +126,13 @@ and the end-to-end audit in [`PROMPTS.md`](PROMPTS.md) §2.8, and only then
|
|
|
126
126
|
merges. Two batches arrived on 2026-09-11 claiming completion with defects
|
|
127
127
|
their own tests could not see; that is why.
|
|
128
128
|
|
|
129
|
+
Concretely, for an agent (decided 2026-09-12): it works on a `gemini/<topic>`
|
|
130
|
+
branch — never on `main`, never in a checkout another agent is using — pushes
|
|
131
|
+
it, opens a PR with `.github/PULL_REQUEST_TEMPLATE.md` filled in, and stops.
|
|
132
|
+
The `scripts/hooks/pre-commit` hook refuses commits on `main` unless
|
|
133
|
+
`git config hoodscript.owner` matches `user.email` (the owner sets it once;
|
|
134
|
+
`scripts/install-hooks.sh` installs the hooks).
|
|
135
|
+
|
|
129
136
|
## Working with an AI agent
|
|
130
137
|
|
|
131
138
|
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.
|
|
3
|
+
Version: 1.4.0
|
|
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
|
|
@@ -110,7 +110,10 @@ hoodscript -c hello.hs # print the Python it becomes
|
|
|
110
110
|
hoodscript repl --mirror # interactive REPL (hood> prompt; chill to exit; --mirror shows the Python)
|
|
111
111
|
hoodscript playground # launch in-browser Pyodide WebAssembly playground
|
|
112
112
|
hoodscript kernel install # Jupyter kernel (pip install "hoodscript[jupyter]"); or %load_ext hoodscript
|
|
113
|
-
hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]")
|
|
113
|
+
hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]"); lint has HL001–HL006
|
|
114
|
+
hoodscript replay <file.hs> # run it, then step through it backwards (b/f/j/l/v/w); --dump prints all
|
|
115
|
+
hoodscript migrate <file.hs> # rewrite v1.0.0 code (cook/serve/holler/Facts…) to the v1.1 lexicon; -w writes, --diff shows
|
|
116
|
+
hoodscript sourcing <word> # Rule Zero check: standing, Green's Dictionary of Slang, UD liability screen, --coraal PATH
|
|
114
117
|
hoodscript learn [topic] # interactive tutor driving docs/curriculum.md
|
|
115
118
|
hoodscript # a short tour of all commands
|
|
116
119
|
hoodscript zen # the Zen of HoodScript (HPEP 20); or `import vibe`
|
|
@@ -196,6 +199,7 @@ hoodarcade play cookout 1 # edit the solution file; the stage re-runs on s
|
|
|
196
199
|
- [`docs/onboarding.md`](docs/onboarding.md) — from zero to a running program in ten minutes
|
|
197
200
|
- [`docs/curriculum.md`](docs/curriculum.md) — ten lessons, each with a runnable file
|
|
198
201
|
- [`MANUAL.md`](MANUAL.md) — the full manual
|
|
202
|
+
- [`docs/hoodlib.md`](docs/hoodlib.md) — the small standard library: `chart` (sparklines, bars, histograms), `disk` (atomic files)
|
|
199
203
|
- [`agents.md`](agents.md) — architecture and subsystem walkthrough
|
|
200
204
|
- [`CLAUDE.md`](CLAUDE.md) — the standing brief for anyone (human or agent) changing the code
|
|
201
205
|
- [`SPRINT_PLAN.md`](SPRINT_PLAN.md) — what's next · [`CHANGELOG.md`](CHANGELOG.md) · [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
@@ -60,7 +60,10 @@ hoodscript -c hello.hs # print the Python it becomes
|
|
|
60
60
|
hoodscript repl --mirror # interactive REPL (hood> prompt; chill to exit; --mirror shows the Python)
|
|
61
61
|
hoodscript playground # launch in-browser Pyodide WebAssembly playground
|
|
62
62
|
hoodscript kernel install # Jupyter kernel (pip install "hoodscript[jupyter]"); or %load_ext hoodscript
|
|
63
|
-
hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]")
|
|
63
|
+
hoodscript fmt|lint <file.hs> # formatter / linter (pip install "hoodscript[tools]"); lint has HL001–HL006
|
|
64
|
+
hoodscript replay <file.hs> # run it, then step through it backwards (b/f/j/l/v/w); --dump prints all
|
|
65
|
+
hoodscript migrate <file.hs> # rewrite v1.0.0 code (cook/serve/holler/Facts…) to the v1.1 lexicon; -w writes, --diff shows
|
|
66
|
+
hoodscript sourcing <word> # Rule Zero check: standing, Green's Dictionary of Slang, UD liability screen, --coraal PATH
|
|
64
67
|
hoodscript learn [topic] # interactive tutor driving docs/curriculum.md
|
|
65
68
|
hoodscript # a short tour of all commands
|
|
66
69
|
hoodscript zen # the Zen of HoodScript (HPEP 20); or `import vibe`
|
|
@@ -146,6 +149,7 @@ hoodarcade play cookout 1 # edit the solution file; the stage re-runs on s
|
|
|
146
149
|
- [`docs/onboarding.md`](docs/onboarding.md) — from zero to a running program in ten minutes
|
|
147
150
|
- [`docs/curriculum.md`](docs/curriculum.md) — ten lessons, each with a runnable file
|
|
148
151
|
- [`MANUAL.md`](MANUAL.md) — the full manual
|
|
152
|
+
- [`docs/hoodlib.md`](docs/hoodlib.md) — the small standard library: `chart` (sparklines, bars, histograms), `disk` (atomic files)
|
|
149
153
|
- [`agents.md`](agents.md) — architecture and subsystem walkthrough
|
|
150
154
|
- [`CLAUDE.md`](CLAUDE.md) — the standing brief for anyone (human or agent) changing the code
|
|
151
155
|
- [`SPRINT_PLAN.md`](SPRINT_PLAN.md) — what's next · [`CHANGELOG.md`](CHANGELOG.md) · [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
@@ -50,7 +50,7 @@ To protect host environments against runaway workloads, the sandbox enforces thr
|
|
|
50
50
|
|
|
51
51
|
## 4. Code Execution Audit
|
|
52
52
|
|
|
53
|
-
`exec`/`eval` appear in exactly
|
|
53
|
+
`exec`/`eval` appear in exactly seven places, all on code objects produced by
|
|
54
54
|
`compile()` from the transpiler's output of the user's own source — never on
|
|
55
55
|
text from anywhere else:
|
|
56
56
|
1. `src/hoodscript/cli.py` — running a file
|
|
@@ -59,7 +59,8 @@ text from anywhere else:
|
|
|
59
59
|
4. `src/hoodscript/tutor.py` — running a curriculum lesson
|
|
60
60
|
5. `src/hoodscript/kernel/kernel.py` — executing notebook cells in the HoodScript kernel
|
|
61
61
|
6. `src/hoodscript/kernel/magics.py` — executing `%%hood` cells in IPython / Google Colab
|
|
62
|
-
`
|
|
62
|
+
7. `src/hoodscript/replay.py` — running a file under the `sys.settrace` recorder (`hoodscript replay`)
|
|
63
|
+
`tests/security/test_exec_sites.py` fails if an eighth appears.
|
|
63
64
|
|
|
64
65
|
### Why this is safe and equivalent to `compile()`:
|
|
65
66
|
- `transpile()` performs strictly lexical substitution over Python's `tokenize.tokenize` stream and deterministic character-preserving pattern transforms (Rules R1–R12).
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# ADR-0002: Linguistic Sourcing & Keyword Reconciliation
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
|
-
Accepted (Sprint 1–2)
|
|
4
|
+
Accepted (Sprint 1–2). Tier lists corrected 2026-09-12 to match GRAMMAR.md v1.1
|
|
5
|
+
(`tryna` is Tier A, `chill` Tier B); re-audited with sources open — see
|
|
6
|
+
`hoodscript/docs/linguistics.md` "Sourcing audit — 2026-09-12".
|
|
5
7
|
|
|
6
8
|
## Context
|
|
7
9
|
The Sprint 0 prototype inherited a set of keywords (`cook`, `serve`, `crew`, `Facts`,
|
|
@@ -18,10 +20,14 @@ A rigorous audit against published AAVE lexicography revealed that:
|
|
|
18
20
|
We adopted the 7-step Lexical Sourcing Policy (`hoodscript/docs/lexical-sourcing.md`)
|
|
19
21
|
and reconciled the keywords to authentic, documented AAVE grammar and lexicon:
|
|
20
22
|
* **Tier A Grammatical Features:** Habitual *be* (`be ... in`), intensive continuative
|
|
21
|
-
*steady* (`steady:`), immediate future *finna*, completive *done*,
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
* **Tier
|
|
23
|
+
*steady* (`steady:`), immediate future *finna*, completive *done*, remote past *BIN*,
|
|
24
|
+
negation *ain't* / *ain't nobody*, existential *it's*, and *tryna* — each with a Yale
|
|
25
|
+
Grammatical Diversity Project page.
|
|
26
|
+
* **Tier B Lexical Keywords:** `bet` (agreement/commitment), `dip` (leave/return),
|
|
27
|
+
`fam` (class/family), `no cap` (true), `cap` (false), `holla` (print), `chill` (pass),
|
|
28
|
+
and the `*Trip` exception suffix — each with dictionary attestation of AAVE use.
|
|
29
|
+
* **Tier C Plain English, kept deliberately:** `catch`, `regardless`, `throw`, `skip`, `ask`,
|
|
30
|
+
`else if` — labelled as plain English, never claimed as AAVE.
|
|
25
31
|
* **Python Pass-through:** Where no cited AAVE word existed with verifiable pedigree
|
|
26
32
|
prior to 2005, Python's standard keywords (`if`, `else`, `while`, `import`, `with`)
|
|
27
33
|
pass through unchanged rather than inventing artificial slang.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# hoodlib — the small standard library
|
|
2
|
+
|
|
3
|
+
`hoodlib` ships with HoodScript and holds only what Python's own library
|
|
4
|
+
doesn't give you in one call. Everything in it is standard-library Python,
|
|
5
|
+
importable from `.hs` or `.py`, with no dependencies. Errors are the plain
|
|
6
|
+
Python exceptions the crash reporter already explains.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
from hoodlib import chart, disk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `hoodlib.chart` — plots that fit in a terminal
|
|
13
|
+
|
|
14
|
+
Every function returns a string, so you decide whether to `holla` it, log it,
|
|
15
|
+
or test it.
|
|
16
|
+
|
|
17
|
+
### Sparklines
|
|
18
|
+
|
|
19
|
+
Two points per character on a four-dot Braille scale (80 columns show 160
|
|
20
|
+
points), or one ▁▂▃ block per point with `style="blocks"`. The minimum still
|
|
21
|
+
shows one dot, so every point is visible.
|
|
22
|
+
|
|
23
|
+
```hood
|
|
24
|
+
from hoodlib import chart
|
|
25
|
+
temps = [61, 64, 70, 75, 73, 68, 62]
|
|
26
|
+
holla chart.sparkline(temps, style="blocks")
|
|
27
|
+
holla chart.sparkline(temps)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
▁▃▅█▇▅▁
|
|
32
|
+
⣠⣾⣷⡀
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`width=N` caps the output at N characters by resampling, never by cutting.
|
|
36
|
+
|
|
37
|
+
### Bar charts
|
|
38
|
+
|
|
39
|
+
```hood
|
|
40
|
+
from hoodlib import chart
|
|
41
|
+
holla chart.bar_chart({"corn": 12, "beans": 6, "hay": 0}, title="Bag", width=32)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Bag
|
|
46
|
+
────────────────────────────────
|
|
47
|
+
corn │█████████████████████ 12
|
|
48
|
+
beans │██████████ 6
|
|
49
|
+
hay │ 0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Bars scale to the largest value. `sort=True` orders them, `color=True` adds
|
|
53
|
+
plain ANSI colour, and negative values draw an empty bar with the number
|
|
54
|
+
rather than pretending to be zero. `chart.show_bag(bot.bag)` is the same
|
|
55
|
+
thing for a game inventory, and an empty bag is a line saying so.
|
|
56
|
+
|
|
57
|
+
### Histograms
|
|
58
|
+
|
|
59
|
+
```hood
|
|
60
|
+
from hoodlib import chart
|
|
61
|
+
scores = [55, 62, 68, 71, 74, 75, 78, 81, 85, 93]
|
|
62
|
+
holla chart.histogram(scores, bins=4, width=32)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
55–64.50 │██████████ 2
|
|
67
|
+
64.50–74 │██████████ 2
|
|
68
|
+
74–83.50 │███████████████████ 4
|
|
69
|
+
83.50–93 │██████████ 2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### What goes wrong, in plain English
|
|
73
|
+
|
|
74
|
+
```hood
|
|
75
|
+
# expect-crash
|
|
76
|
+
from hoodlib import chart
|
|
77
|
+
holla chart.sparkline([])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The report is a `BadValueTrip` saying `sparkline() got an empty series;
|
|
81
|
+
there is nothing to plot`. A non-number names the item: `item 1 is 'x'
|
|
82
|
+
(str)`.
|
|
83
|
+
|
|
84
|
+
## `hoodlib.disk` — files that can't half-write
|
|
85
|
+
|
|
86
|
+
Atomic writes (temp file + fsync + rename), per-path locks so two threads
|
|
87
|
+
can't tear a file, and timestamped backups.
|
|
88
|
+
|
|
89
|
+
```hood
|
|
90
|
+
from hoodlib import disk
|
|
91
|
+
disk.write_json("scores.json", {"ali": 3, "bo": 5})
|
|
92
|
+
holla disk.read_json("scores.json")
|
|
93
|
+
disk.append_csv("log.csv", ["ali", 3])
|
|
94
|
+
holla disk.read_csv("log.csv", header=False)
|
|
95
|
+
holla disk.read_json("missing.json", default={})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
{'ali': 3, 'bo': 5}
|
|
100
|
+
[['ali', '3']]
|
|
101
|
+
{}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`disk.backup(path, keep=5)` copies `path` to `path.<timestamp>.bak` and
|
|
105
|
+
prunes older copies. A missing file with no `default` is a `MissingFileTrip`.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ruff: noqa: E501 (rule tables read better on one line each)
|
|
3
|
+
"""Align a stale generation prompt with the current release.
|
|
4
|
+
|
|
5
|
+
Prompts written against the v1.0.0 package (the `cook`/`serve` lexicon, "AST
|
|
6
|
+
isomorphism", `libcst`, `hoodscript vibe`, ...) keep arriving. Executing one as
|
|
7
|
+
written regresses the language; aligning one by hand is the procedure in
|
|
8
|
+
PROMPTS.md §3.5. This script does the mechanical half of that procedure and
|
|
9
|
+
flags the half that needs a decision:
|
|
10
|
+
|
|
11
|
+
1. lexicon v1.0.0 words -> v1.1 words (the CHANGELOG migration table);
|
|
12
|
+
common English words (`say`, `word`, `cut`, ...) only inside
|
|
13
|
+
backticks / code fences / `word (python)` glosses, so prose
|
|
14
|
+
is not mangled
|
|
15
|
+
2. phrasing "AST isomorphism" -> the line law, "Friendly Coach" -> the
|
|
16
|
+
JPE engine, stale paths and commands -> current ones
|
|
17
|
+
3. decisions lines that ask for something the owner declined or dropped
|
|
18
|
+
(dialect overlays -> ADR-0004; hoodpkg, hoodlib.vault/net,
|
|
19
|
+
libcst -> CHANGELOG) are kept but marked [DECLINED ...] /
|
|
20
|
+
[DROPPED ...] so the agent does not do them and the reader
|
|
21
|
+
sees why
|
|
22
|
+
4. framing the PROMPTS.md §0 standing brief goes on top; a footer tells
|
|
23
|
+
the agent to diff the scope against the tree before writing
|
|
24
|
+
|
|
25
|
+
What it cannot do: know what is already on disk. That is the "diff against
|
|
26
|
+
disk" step of §3.5 and stays a judgment call.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
python scripts/align_prompt.py prompt.md # aligned prompt on stdout, report on stderr
|
|
30
|
+
python scripts/align_prompt.py - < prompt.md
|
|
31
|
+
python scripts/align_prompt.py prompt.md -o aligned.md --report report.md
|
|
32
|
+
python scripts/align_prompt.py prompt.md --check # exit 1 if anything would change
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import re
|
|
39
|
+
import sys
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------------------------------- 1. lexicon
|
|
46
|
+
# Source of truth: CHANGELOG.md "keyword migration table" (v1.0.0 -> v1.1) and
|
|
47
|
+
# src/hoodscript/constants.py. Unambiguous tokens are rewritten anywhere;
|
|
48
|
+
# AMBIGUOUS ones (real English words) only in code context.
|
|
49
|
+
|
|
50
|
+
LEXICON: dict[str, str] = {
|
|
51
|
+
# keywords
|
|
52
|
+
"cook": "bet", "serve": "dip", "crew": "fam", "holler": "holla", "Facts": "no cap", "Cap": "cap",
|
|
53
|
+
"Ghost": "None", "mine": "self", "say": "if", "or_say": "else if", "otherwise": "else",
|
|
54
|
+
"run_thru": "be", "up_in": "in", "keep_goin_if": "while", "keep_pushin": "skip", "attempt": "tryna",
|
|
55
|
+
"aint": "ain't", "on_the_side": "finna", "hold_up": "done", "crash_out": "throw", "catch_heat": "catch",
|
|
56
|
+
"stand_on_it": "assert", "rock_with": "with", "cut": "del", "whole_city": "global",
|
|
57
|
+
"outside_scope": "nonlocal", "inspect": "match", "scenario": "case", "bring_in": "import", "outta": "from",
|
|
58
|
+
"quick_cook": "lambda", "pass_it": "yield", "and_also": "and", "or_else": "or", "straight_is": "is",
|
|
59
|
+
"whatever": "_",
|
|
60
|
+
# builtins
|
|
61
|
+
"check_mic": "assert", "count_up": "len", "span": "range", "what_kind": "type", "built_like": "isinstance",
|
|
62
|
+
"word": "str", "whole_num": "int", "point_num": "float", "lineup": "list", "ledger": "dict", "circle": "set",
|
|
63
|
+
"locked_lineup": "tuple", "unlock_file": "open", "tally": "sum", "index_run": "enumerate", "pair_up": "zip",
|
|
64
|
+
"every_single": "all", "any_at_all": "any", "weed_out": "filter", "apply_all": "map", "ancestor": "super",
|
|
65
|
+
# exceptions whose roots were cut
|
|
66
|
+
"GhostFileTrip": "MissingFileTrip", "StandOnItTrip": "AssertTrip",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# English words that are also v1.0.0 tokens: rewrite only in code context.
|
|
70
|
+
AMBIGUOUS = frozenset({
|
|
71
|
+
"say", "otherwise", "mine", "attempt", "cut", "inspect", "scenario", "word", "circle", "span",
|
|
72
|
+
"tally", "ledger", "lineup", "whatever", "serve", "crew", "cook", "ancestor",
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------------------- 2. phrasing
|
|
76
|
+
# (pattern, replacement, note). Applied in order, case-insensitive where flagged.
|
|
77
|
+
PHRASES: list[tuple[str, str, str]] = [
|
|
78
|
+
(r"(?:a )?strict 1:1 AST isomorphism to CPython(?: 3\.1\d(?:[–-]3\.1\d)?)?",
|
|
79
|
+
"the 1:1 line law (token substitution; line N of the .py is line N of the .hs; no AST rewriting) on Python 3.10–3.13",
|
|
80
|
+
"AST isomorphism -> line law"),
|
|
81
|
+
# v1.0.0 `dip` meant break; v1.1 `dip` is return (CHANGELOG). Rewrite the glossed form before the lexicon pass.
|
|
82
|
+
(r"\bdip \(break\)", "break (break)", "dip (break) -> break: v1.1 dip is return"),
|
|
83
|
+
(r"`dip`\s*(?:->|→|=>)\s*`break`", "`break` (Python's word; v1.1 `dip` is `return`)", "dip -> break remap removed"),
|
|
84
|
+
(r"1:1 AST (?:isomorphism|parity|mapping)", "1:1 line mapping (token substitution, no AST rewriting)", "AST isomorphism -> line law"),
|
|
85
|
+
(r"100% CPython PEG grammar parity", "CPython compiles the output unchanged (the 1:1 line law)", "grammar parity -> line law"),
|
|
86
|
+
(r"lexical AST rewriting", "token-stream rewriting (`tokenize` -> NAME substitution -> `untokenize`)", "AST rewriting -> token rewriting"),
|
|
87
|
+
(r"with AST validation", "via `hoodscript.transpile` -> `compile` -> `exec`", "AST validation -> transpile/compile"),
|
|
88
|
+
(r"Friendly Coach(?: error hook(?: integrations)?)?", "the JPE error engine (`hoodscript.errors`: `diagnose` -> `render_plain`; register the source in `linecache` first)",
|
|
89
|
+
"Friendly Coach -> JPE engine"),
|
|
90
|
+
(r"`?hoodscript vibe`?(?![-\w])", "`hoodscript zen`", "hoodscript vibe -> hoodscript zen"),
|
|
91
|
+
(r"`?bring_in vibe`?", "`import vibe`", "bring_in vibe -> import vibe"),
|
|
92
|
+
(r"examples/arcade_solutions/?", "examples/solutions/<game>/", "arcade_solutions path"),
|
|
93
|
+
(r"tests/test_hoodlib\.py", "tests/test_hoodlib_disk.py", "test_hoodlib path"),
|
|
94
|
+
(r"`hoodarcade levels <game>`", "`hoodarcade list` / `hoodarcade levels [game]`", "levels command"),
|
|
95
|
+
(r"`--watch` live solution reloading", "hot-reload on save (the default of `play`; `--once` disables it)", "--watch is the default"),
|
|
96
|
+
(r"`hood` CLI entry point|`hood` command", "`hoodscript` CLI (there is no `hood` entry point in the release package)", "hood -> hoodscript"),
|
|
97
|
+
(r"Python 3\.11\+", "Python 3.10–3.13", "Python floor is 3.10"),
|
|
98
|
+
(r"prompt-toolkit, pygments, libcst, jedi, pygls", "zero-dependency core; extras `repl lsp tools pretty farm arcade jupyter all dev`", "dependency list"),
|
|
99
|
+
(r"beyond `rich`, `cryptography`, and `prompt-toolkit`", "beyond what pyproject's extras already declare (`rich` for the games; the core has none)", "deps: no cryptography"),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# --------------------------------------------------------------------------- 3. decisions
|
|
103
|
+
# (pattern, marker). A line matching a pattern is prefixed with the marker.
|
|
104
|
+
DECISIONS: list[tuple[str, str]] = [
|
|
105
|
+
(r"dialect|Bay Area|Atlanta \(|DMV \(|UK Drill|\bMLE\b|kill_moe|on_mama|WastemanTrip",
|
|
106
|
+
"[DECLINED — ADR-0004 (docs/adr/0004-no-regional-dialect-overlays.md): no dialect overlays under Rule Zero]"),
|
|
107
|
+
(r"hoodpkg|hood\.toml|hood\.lock|\bpkg\.py\b|hood (?:new|add|install|lock|publish)\b",
|
|
108
|
+
"[DROPPED — duplicates pyproject/pip; .hs files are already importable modules (CHANGELOG 1.3.0)]"),
|
|
109
|
+
(r"vault\.py|hoodlib\.vault|AES-GCM|PBKDF2|payload signing",
|
|
110
|
+
"[DROPPED — hoodlib ships only what Python lacks; `cryptography` is one import away (CHANGELOG 1.3.0)]"),
|
|
111
|
+
(r"\bnet\.py\b|hoodlib\.net|async(?:hronous)? HTTP client|connection pooling",
|
|
112
|
+
"[DROPPED — same; `aiohttp`/`httpx` are one import away (CHANGELOG 1.3.0)]"),
|
|
113
|
+
(r"libcst", "[NEVER — libcst was cut with the v1.0.0 stack; the transpiler is tokenize-based]"),
|
|
114
|
+
(r"^\s*<lexicon_taxonomy>",
|
|
115
|
+
"[NOTE — the keyword set is `src/hoodscript/constants.py` + GRAMMAR.md R1–R12; this table was rewritten to v1.1 and is informational only]"),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
FOOTER = """
|
|
119
|
+
---
|
|
120
|
+
ALIGNMENT FOOTER (added by scripts/align_prompt.py — keep it)
|
|
121
|
+
|
|
122
|
+
Before writing a line: diff this scope against the tree. `git ls-files src tests
|
|
123
|
+
examples docs` and the CHANGELOG say what already exists; PROMPTS.md §3.5 is the
|
|
124
|
+
worked example of this procedure. Items marked [DECLINED]/[DROPPED]/[NEVER]
|
|
125
|
+
above are out of scope by owner decision — do not re-propose them inside the
|
|
126
|
+
batch. Work on a `gemini/<topic>` branch and open a PR (agents.md). No tags.
|
|
127
|
+
Gate: `ruff check && pytest`. Docs are tests. Complete files.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
_WORD = r"(?<![\w'])({})(?![\w'])"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class Report:
|
|
135
|
+
lexicon: dict[str, int] = field(default_factory=dict)
|
|
136
|
+
phrases: dict[str, int] = field(default_factory=dict)
|
|
137
|
+
flagged: list[tuple[int, str]] = field(default_factory=list)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def changed(self) -> bool:
|
|
141
|
+
return bool(self.lexicon or self.phrases or self.flagged)
|
|
142
|
+
|
|
143
|
+
def render(self) -> str:
|
|
144
|
+
out = ["# align_prompt report", ""]
|
|
145
|
+
if self.lexicon:
|
|
146
|
+
out.append("## Lexicon rewrites (v1.0.0 -> v1.1)")
|
|
147
|
+
for k, n in sorted(self.lexicon.items(), key=lambda kv: -kv[1]):
|
|
148
|
+
out.append(f"- `{k}` -> `{LEXICON[k]}` ×{n}")
|
|
149
|
+
out.append("")
|
|
150
|
+
if self.phrases:
|
|
151
|
+
out.append("## Phrasing / paths / commands")
|
|
152
|
+
for k, n in self.phrases.items():
|
|
153
|
+
out.append(f"- {k} ×{n}")
|
|
154
|
+
out.append("")
|
|
155
|
+
if self.flagged:
|
|
156
|
+
out.append("## Lines flagged by owner decision (kept, marked, out of scope)")
|
|
157
|
+
for ln, marker in self.flagged:
|
|
158
|
+
out.append(f"- line {ln}: {marker.split(' — ')[0].strip('[')}")
|
|
159
|
+
out.append("")
|
|
160
|
+
if not self.changed:
|
|
161
|
+
out.append("Nothing to change: the prompt is already aligned.")
|
|
162
|
+
out.append("Still to do by hand: diff the scope against the tree (PROMPTS.md §3.5).")
|
|
163
|
+
return "\n".join(out) + "\n"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def standing_brief() -> str:
|
|
167
|
+
"""PROMPTS.md §0, the block that goes at the top of every prompt."""
|
|
168
|
+
text = (ROOT / "PROMPTS.md").read_text(encoding="utf-8")
|
|
169
|
+
m = re.search(r"## 0\. The standing brief.*?\n```\n(.*?)\n```", text, re.S)
|
|
170
|
+
return m.group(1) if m else ""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _code_spans(text: str) -> list[tuple[int, int]]:
|
|
174
|
+
"""Character ranges that count as code: fenced blocks, backtick spans, and
|
|
175
|
+
`word (python)` glosses like the lexicon tables these prompts carry."""
|
|
176
|
+
spans = []
|
|
177
|
+
for m in re.finditer(r"```.*?```", text, re.S):
|
|
178
|
+
spans.append(m.span())
|
|
179
|
+
for m in re.finditer(r"`[^`\n]+`", text):
|
|
180
|
+
spans.append(m.span())
|
|
181
|
+
for m in re.finditer(r"\b\w+ \((?:def|return|yield|lambda|class|self|if|elif|else|for|in|while|break|continue|pass|import|from|as|try|except|finally|raise|assert|with|async|await|del|global|nonlocal|match|case|True|False|None|_|and|or|not|is|print|len|range|type|str|int|float|list|dict|set|tuple|open|sum|enumerate|zip|all|any|filter|map|isinstance|super|input)\)", text):
|
|
182
|
+
spans.append(m.span())
|
|
183
|
+
return spans
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _in_code(spans: list[tuple[int, int]], pos: int) -> bool:
|
|
187
|
+
return any(a <= pos < b for a, b in spans)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def rewrite_lexicon(text: str, report: Report) -> str:
|
|
191
|
+
spans = _code_spans(text)
|
|
192
|
+
# longest first so `locked_lineup` wins over `lineup`, `or_say` over `say`
|
|
193
|
+
words = sorted(LEXICON, key=len, reverse=True)
|
|
194
|
+
pattern = re.compile(_WORD.format("|".join(re.escape(w) for w in words)))
|
|
195
|
+
|
|
196
|
+
def sub(m: re.Match) -> str:
|
|
197
|
+
w = m.group(1)
|
|
198
|
+
if w in AMBIGUOUS and not _in_code(spans, m.start()):
|
|
199
|
+
return w
|
|
200
|
+
report.lexicon[w] = report.lexicon.get(w, 0) + 1
|
|
201
|
+
return LEXICON[w]
|
|
202
|
+
|
|
203
|
+
return pattern.sub(sub, text)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def rewrite_phrases(text: str, report: Report) -> str:
|
|
207
|
+
for pat, repl, note in PHRASES:
|
|
208
|
+
text, n = re.subn(pat, repl, text)
|
|
209
|
+
if n:
|
|
210
|
+
report.phrases[note] = report.phrases.get(note, 0) + n
|
|
211
|
+
return text
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def collapse_glosses(text: str, report: Report) -> str:
|
|
215
|
+
"""After the lexicon pass a table row like `bring_in (import)` reads
|
|
216
|
+
`import (import)`; say what that means."""
|
|
217
|
+
text, n = re.subn(r"\b(\w+) \(\1\)", r"\1 (Python's word)", text)
|
|
218
|
+
if n:
|
|
219
|
+
report.phrases["X (X) gloss -> Python's word"] = n
|
|
220
|
+
return text
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def mark_decisions(text: str, report: Report) -> str:
|
|
224
|
+
out = []
|
|
225
|
+
for i, line in enumerate(text.split("\n"), 1):
|
|
226
|
+
for pat, marker in DECISIONS:
|
|
227
|
+
if re.search(pat, line, re.I) and not line.lstrip().startswith("["):
|
|
228
|
+
indent = line[: len(line) - len(line.lstrip())]
|
|
229
|
+
line = f"{indent}{marker} {line.lstrip()}"
|
|
230
|
+
report.flagged.append((i, marker))
|
|
231
|
+
break
|
|
232
|
+
out.append(line)
|
|
233
|
+
return "\n".join(out)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
HEAD_MARK = "<!-- PROMPTS.md §0 standing brief, prepended by scripts/align_prompt.py -->"
|
|
237
|
+
HEAD_END = "\n\n---\n\n"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def align(text: str, *, brief: bool = True) -> tuple[str, Report]:
|
|
241
|
+
"""Idempotent: a brief this script prepended and a footer it appended are
|
|
242
|
+
recognised and left alone (the brief names the cut words on purpose)."""
|
|
243
|
+
report = Report()
|
|
244
|
+
head = ""
|
|
245
|
+
if text.startswith(HEAD_MARK) and HEAD_END in text:
|
|
246
|
+
head, text = text.split(HEAD_END, 1)
|
|
247
|
+
head += HEAD_END
|
|
248
|
+
if FOOTER.strip() in text:
|
|
249
|
+
text = text.replace(FOOTER.strip(), "").rstrip("\n") + "\n"
|
|
250
|
+
|
|
251
|
+
body = rewrite_phrases(text, report) # phrase rules first: `dip (break)` must go before the lexicon pass
|
|
252
|
+
body = rewrite_lexicon(body, report)
|
|
253
|
+
body = collapse_glosses(body, report)
|
|
254
|
+
body = mark_decisions(body, report)
|
|
255
|
+
if not head and brief and "You are working on HoodScript" not in text:
|
|
256
|
+
b = standing_brief()
|
|
257
|
+
head = f"{HEAD_MARK}\n{b}{HEAD_END}" if b else ""
|
|
258
|
+
return head + body.rstrip("\n") + "\n" + FOOTER, report
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def main(argv: list[str] | None = None) -> int:
|
|
262
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
263
|
+
ap.add_argument("prompt", help="prompt file, or - for stdin")
|
|
264
|
+
ap.add_argument("-o", "--out", help="write the aligned prompt here (default: stdout)")
|
|
265
|
+
ap.add_argument("--report", help="write the change report here (default: stderr)")
|
|
266
|
+
ap.add_argument("--no-brief", action="store_true", help="do not prepend the PROMPTS.md §0 brief")
|
|
267
|
+
ap.add_argument("--check", action="store_true", help="exit 1 if the prompt would change (no output)")
|
|
268
|
+
args = ap.parse_args(argv)
|
|
269
|
+
|
|
270
|
+
text = sys.stdin.read() if args.prompt == "-" else Path(args.prompt).read_text(encoding="utf-8")
|
|
271
|
+
aligned, report = align(text, brief=not args.no_brief)
|
|
272
|
+
|
|
273
|
+
if args.check:
|
|
274
|
+
sys.stderr.write(report.render())
|
|
275
|
+
return 1 if report.changed else 0
|
|
276
|
+
if args.out:
|
|
277
|
+
Path(args.out).write_text(aligned, encoding="utf-8")
|
|
278
|
+
else:
|
|
279
|
+
sys.stdout.write(aligned)
|
|
280
|
+
if args.report:
|
|
281
|
+
Path(args.report).write_text(report.render(), encoding="utf-8")
|
|
282
|
+
else:
|
|
283
|
+
sys.stderr.write(report.render())
|
|
284
|
+
return 0
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
sys.exit(main())
|