Skip to content

feat(stdlib): Deno scripting surface — walk/args/exit/iso/regex/stderr (step #242)#445

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/deno-scripting-externs-step3
May 30, 2026
Merged

feat(stdlib): Deno scripting surface — walk/args/exit/iso/regex/stderr (step #242)#445
hyperpolymath merged 1 commit into
mainfrom
feat/deno-scripting-externs-step3

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Adds six externs to stdlib/Deno.affine to unblock the TS→AffineScript estate migration:

  • walkRecursive(root) -> [String] — depth-first file walk
  • args() -> [String]Deno.args
  • exit(code) -> IntDeno.exit (never returns)
  • dateNowIso() -> Stringnew Date().toISOString()
  • consoleError(s) -> Int — stderr; returns 0
  • regexMatch(s, pat) -> Boolnew RegExp(pat).test(s)

These were surfaced by surveying the campaign #239 step 2 tail-batch-1 candidates: two pure-logic Deno CLI scripts (panic-attack docs/campaigns/2026-05-26/01-triage.ts and standards/scripts/check-ts-allowlist.ts) become portable using only the existing Deno.affine surface plus these six additions.

Lowering

  • walkRecursive, regexMatch get tiny prelude shims (__as_walkRecursive recurses through Deno.readDirSync; __as_regexMatch wraps RegExp.test).
  • args, exit, dateNowIso, consoleError lower directly to host expressions.

Test plan

  • dune build — green
  • New fixture tests/codegen-deno/deno_scripting.{affine,harness.mjs} — in-memory FS for walk, captured stderr, intercepted exit. New harness passes.
  • All 12 codegen-deno harnesses pass
  • dune runtest — 346 tests green

Refs

Notes

This is the first-cut for step 3. Other gaps not addressed here (HTTP server, fetch, crypto.subtle, Deno.Command, recursive-stat with isFile/isDirectory flags) remain on the step-3 docket — they block heavier ports (mock-echidna, civic-connect services, dotfiles health host) which are out of scope for tail-batch-1.

🤖 Generated with Claude Code

…r (step #242)

Adds six externs to stdlib/Deno.affine to unblock the TS→AffineScript
estate migration (campaign hyperpolymath/standards#239), step 3
(standards#242 — STDLIB FILL):

- walkRecursive(root) -> [String]   (depth-first file walk)
- args() -> [String]                (Deno.args)
- exit(code) -> Int                 (Deno.exit; never returns)
- dateNowIso() -> String            (new Date().toISOString())
- consoleError(s) -> Int            (stderr; returns 0)
- regexMatch(s, pat) -> Bool        (new RegExp(pat).test(s))

These were surfaced as the minimum-viable set by a survey of tail-batch-1
candidates (standards#241): two pure-logic Deno CLI scripts
(panic-attack docs/campaigns/2026-05-26/01-triage.ts and
standards/scripts/check-ts-allowlist.ts) become portable using only the
existing Deno.affine surface plus these six additions. Other tail-batch
candidates remain blocked on fetch / HTTP-server / crypto plumbing
which is out of scope here.

Lowering:
- walkRecursive, regexMatch get tiny prelude shims (__as_walkRecursive
  recurses through Deno.readDirSync; __as_regexMatch wraps RegExp.test).
- args, exit, dateNowIso, consoleError lower direct to host expressions
  (Deno.args / Deno.exit(c) / new Date().toISOString() / (console.error(s), 0)).

Test: tests/codegen-deno/deno_scripting.{affine,harness.mjs} stubs the
filesystem (in-memory fs for walk), captures stderr writes, and
intercepts Deno.exit so the harness completes. All 12 codegen-deno
harnesses pass; full dune runtest = 346 green.

Refs hyperpolymath/standards#242
Refs hyperpolymath/standards#239
@hyperpolymath hyperpolymath enabled auto-merge (squash) May 30, 2026 12:40
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 82 issues detected

Severity Count
🔴 Critical 4
🟠 High 10
🟡 Medium 68

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action perpolymath/standards/.github/workflows/governance-reusable.yml@main\n needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action ons/checkout@v6\n    needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action land/setup-deno@v2\n    needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in affine-vscode-publish.yml",
    "type": "unknown",
    "file": "affine-vscode-publish.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "unknown",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "unknown",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in ci.yml",
    "type": "unknown",
    "file": "ci.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath merged commit e4f8fbf into main May 30, 2026
26 of 27 checks passed
@hyperpolymath hyperpolymath deleted the feat/deno-scripting-externs-step3 branch May 30, 2026 12:43
hyperpolymath added a commit to hyperpolymath/standards that referenced this pull request May 30, 2026
…Refs #239) (#283)

## Summary

AffineScript port of `scripts/check-ts-allowlist.ts` — the meta-tool
that enforces the no-new-TypeScript estate policy. Self-referential:
this script is itself one of the `.ts` files its policy applies to.
Symbolic landing.

Filed under the estate TS→AffineScript migration campaign:
- Umbrella: #239
- Step: #241 (STEP 2 — tail-batch-1)
- Unblocked by: hyperpolymath/affinescript#445 (Deno scripting externs —
walk/args/exit/iso/regex/stderr)

## Pattern

Adapted from phronesis#19 seed: adds `.affine` alongside the live `.ts`;
CI keeps invoking the `.ts`. The follow-up PR can wire
`.github/workflows/governance-reusable.yml` to the compiled `.deno.js`
directly (no `.mjs` wrapper needed — `main()` now calls `Deno.exit()`
itself, matching the TS original).

## Behaviour

Faithful mirror of the TS:

- Built-in directory + filename allowlist (`DIR_NAMES_ALLOWED`,
`*.d.ts`, `mod.ts`, `lsp*.ts`, `*-lsp.ts`, `*.bench.ts`, `*_bench.ts`,
segments containing `vscode` or starting with `deno-`)
- **Layer 2** — `.claude/CLAUDE.md` TypeScript-exemption table parser
(heading regex matches `TypeScript|JavaScript|TS|JS|.tsx?`
`[Ee]xemption`, multi-table aware)
- **Layer 2.5** — `.governance-allowlist` plain-text glob list
- Glob→regex translation (`*` → `.*`, `?` → `.`, regex-escape chars)
- Exempt fallback: regex match, then literal `bare` equality, then
trailing-slash prefix match
- Lex-sort of the violation list (manual `str_lt` via `char_to_int` —
AffineScript has no `<` on String)
- Same exit code (0 success / 1 violation via `Deno.exit`) and same
output text

## Drive-by

SPDX header normalised to `MPL-2.0` (per estate language-policy
2026-05-25; `PMPL-1.0-or-later` on the live `.ts` is the legacy form
pending its own cleanup).

## Test plan

- [x] `affinescript compile scripts/check-ts-allowlist.affine -o
/tmp/check-ts-allowlist.deno.js --deno-esm` → exit 0, clean compile
(oracle per campaign prompt)
- [ ] Build-chain integration (workflow switch from `.ts` to compiled
`.deno.js`) — deferred to follow-up PR per seed pattern

## Notes

Branch was started by a parallel Claude session (479L draft); this PR
added `str_lt` byte-wise lex compare (TS used `<` on String which
AffineScript doesn't overload), SPDX header drive-by, and the
exit()-direct refactor so no `.mjs` wrapper is needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hyperpolymath added a commit to hyperpolymath/standards that referenced this pull request May 30, 2026
…gn STEP 2 seed) (#284)

## Summary

Adds `scripts/check-ts-allowlist.affine` as the AffineScript port of the
existing `scripts/check-ts-allowlist.ts`, filed under the estate
TS->AffineScript migration campaign (#239
umbrella, STEP 2 — tail-batch-1 per standards#241).

**Self-referential**: the script that enforces the no-new-TypeScript
policy is itself one of the TS files that policy applies to. Landing the
AffineScript version is a symbolic milestone for the meta-tool.

**Pattern**: phronesis#19 seed style. Add `.affine` alongside the live
`.ts`. No workflow change in this PR; CI keeps invoking the `.ts` via
`deno run --allow-read
.standards-checkout/scripts/check-ts-allowlist.ts`. Workflow cutover
(compile `.affine` to `.deno.js`, wire wrapper, retire `.ts`) is a
follow-up issue.

## Behaviour equivalence

The regression suite at `scripts/tests/check-ts-allowlist-test.sh`
exercises 13 cases (builtin allowlist classes + Layer-2 CLAUDE.md
exemptions + Layer-2.5 governance-allowlist + dotted-dir skip +
multi-heading-table parsing).

- **All 13 PASS** against the AffineScript-emitted `.deno.js` (verified
locally with a sibling harness using the same fixtures)
- **All 13 PASS** against the original `.ts` (no regression)

Both implementations are behaviour-equivalent on the substring
assertions the suite makes. Cosmetic difference: the `.affine` port uses
ASCII `[FAIL]` / `[OK]` sentinels instead of the original emoji (see
"Seam findings" below).

## Stdlib surface used

All externs already shipped in `stdlib/Deno.affine` via
affinescript#445:

- `walkRecursive` (recursive file enumeration)
- `regexMatch` (JS RegExp.test wrapper)
- `readTextFile` (synchronous file read; throws on missing — wrapped in
`try`/`catch`)
- `args`, `exit`, `consoleError`

Plus AffineScript builtins: `string_get`, `string_sub`, `string_find`,
`char_to_int`, `int_to_string`, `len`.

## AffineScript seam findings surfaced by this port

(Each would be a separate affinescript-repo PR — out of scope for this
per-standards-repo PR per the campaign's ownership gate.)

- **String less-than lex-compare**: not built-in. Implemented inline via
byte-wise `str_lt` (calls `char_to_int(string_get(...))`). Bare `a < b`
on `String` produces `TypeMismatch (String, Int)`.
- **`break`/`continue` in `while`**: `BREAK`/`CONTINUE` tokens are
reserved in `lib/parser.mly` but no production rule uses them yet.
Refactored two natural occurrences (`s_trim` inner loops +
`strip_leading_dot_slash`) to combined-guard / sentinel-boolean forms.
- **Non-ASCII string literals**: lower to octal escape sequences (e.g.
`"\\226\\157\\140"`) in `--deno-esm` output. Strict-mode ESM rejects
octal escapes with `SyntaxError: Octal escape sequences are not allowed
in strict mode.` Worked around with ASCII `[FAIL]` / `[OK]` sentinels.
- **Stale installed binary**: the `affinescript` at `~/.local/bin/`
predated PR #445 and silently emitted bare `walkRecursive(".")` calls
(no `__as_walkRecursive` shim in the prelude). A trunk rebuild surfaces
the new shims correctly.

## Sequencing follow-ups (NOT part of this PR)

- [ ] Compile-time wiring: build `.affine` -> `.deno.js` in CI; commit
`.deno.js` as a generated artefact OR add a precompile step.
- [ ] Workflow cutover: update
`.github/workflows/governance-reusable.yml` to invoke the `.deno.js`
(with the existing `--allow-read` scope).
- [ ] Retire `.ts`: delete `scripts/check-ts-allowlist.ts` and update
`docs/EXEMPTION-MECHANISMS.adoc` references.
- [ ] Upstream affinescript fixes for the 3 seam findings above (file as
separate affinescript-repo issues).

## Test plan

- [x] `affinescript check` type-checks the `.affine`
- [x] `affinescript compile --deno-esm` emits `.deno.js` with no
octal-escape errors
- [x] 13/13 regression tests pass against the AffineScript-emitted
`.deno.js`
- [x] 13/13 regression tests pass against the original `.ts` (no
regression)
- [x] GPG-signed commits

## Refs

- Refs #239 (campaign umbrella)
- Refs #241 (STEP 2 — TAIL BATCH 1)
- Pattern: hyperpolymath/phronesis#19 (STEP 1 seed)
- Unblocked by: hyperpolymath/affinescript#445 (STEP 3 first-cut)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant