Skip to content

feat(parser): trailing-comma in fn params and expr lists (Refs gitbot-fleet#148) - #370

Merged
hyperpolymath merged 3 commits into
mainfrom
claude/parser-trailing-comma-148
May 26, 2026
Merged

feat(parser): trailing-comma in fn params and expr lists (Refs gitbot-fleet#148)#370
hyperpolymath merged 3 commits into
mainfrom
claude/parser-trailing-comma-148

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

Adds optional-trailing-comma support to comma-separated parameter and expression lists in the AffineScript grammar — required by the sustainabot hand-port (gitbot-fleet#148) and conventional in every Rust-like language in the estate.

Two new right-recursive rules in `parser.mly`:

  • `param_list_trailing_comma` — replaces `separated_list(COMMA, param)` at four call sites (`extern_fn_decl`, `fn_decl`, `effect_op_decl`, `fn_sig`).
  • `expr_list_trailing_comma` — replaces `separated_list(COMMA, expr)` at two call sites (`expr_postfix` for fn-application args, `expr_primary` for array literals).

Why right-recursive rather than `separated_list(...) COMMA?`

The hand-rolled form lets the trailing COMMA be absorbed inside the recursion. After each item, the LR(1) choice on COMMA is unambiguous: shift into the recursive tail, whose body may be empty when the closing token follows. The mixfix `separated_list(...) COMMA?` form introduces an LR conflict on the final COMMA which Menhir would have to break by precedence, and we'd rather not pay that cost for a syntactic convenience.

Conflict-cost

Conflict-neutral. Parser builds with 21 shift/reduce + 1 reduce/reduce, identical to the pre-patch baseline (verified by inspecting `_build/default/lib/parser.conflicts`).

Test plan

  • `dune build` green
  • Conflict count unchanged: 21 S/R + 1 R/R
  • CI green
  • Smoke: `fn f(a: Int, b: Int,) -> Int { a + b }` parses
  • Smoke: `let xs = [1, 2, 3,];` parses
  • Smoke: `f(a, b,)` parses as a call

Companion PRs (gitbot-fleet#148 spine)

This is PR 1 of 5 in the parser+lexer cleanup spine driven by the sustainabot hand-port. Each PR is independent and can be reviewed/merged in any order:

  1. this PR — trailing-comma in fn params + expr lists
  2. fn-type with effect arrow in type position
  3. builtin/lowercase qualified paths + TOTAL field name
  4. lexer `_`-prefix idents
  5. (hypatia) Levenshtein perf fix

🤖 Generated with Claude Code

…-fleet#148)

Adds `param_list_trailing_comma` and `expr_list_trailing_comma` —
right-recursive comma-separated lists that accept an optional trailing
COMMA before the closing delimiter. Rewires six call sites:

  * `extern_fn_decl` (line 197)
  * `fn_decl` (line 229)
  * `effect_op_decl` (line 596)
  * `fn_sig` (line 662)
  * `expr_postfix` LPAREN…RPAREN (line 799)
  * `expr_primary` LBRACKET…RBRACKET (line 852)

Hand-rolled right-recursive form (rather than `separated_list(COMMA, X)
COMMA?`) so the trailing COMMA is absorbed inside the recursion: after
each item, the LR(1) choice on COMMA is unambiguous (shift into the
recursive tail, whose body may be empty when the closing token follows).

Conflict-neutral: parser builds with 21 shift/reduce + 1 reduce/reduce,
identical to the pre-patch baseline.

Required by sustainabot hand-port (gitbot-fleet#148): patterns such as
`json::encode_object([("k", v),])` and `f(..., last_arg,)` are
Rust-likes-conventional and used throughout the hand-ported sources.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 41 issues detected

Severity Count
🔴 Critical 15
🟠 High 15
🟡 Medium 11

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Action actions/checkout@v6 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action denoland/setup-deno@v2 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/discover.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/packages/affine-js/types.d.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath enabled auto-merge (squash) May 26, 2026 08:26
hyperpolymath added a commit to hyperpolymath/gitbot-fleet that referenced this pull request May 26, 2026
…Refs #148)

Two hand-port corrections after re-validating the ReScript→AffineScript
files against the live `affinescript check`:

1. **OCaml-style float operators** `/.`, `*.`, `+.`, `-.` → AffineScript
   `/`, `*`, `+`, `-`. AffineScript uses unified arithmetic operators
   for both Int and Float (see `examples/lessons/01_hello.affine`:
   `subtotal * 0.08`), so the OCaml separation was a hand-port artefact.
   Affected sites:
   - `GitHubApp.affine`: `Time::now_millis() / 1000.0`,
     `now_seconds - 60.0`, `now_seconds + 600.0`,
     `Time::now_millis() + 60000.0`
   - `Oikos.affine`: `60.0 * 60.0 * 1000.0`, `now - a.createdAt`
   - `Report.affine`: `r.confidence * 100.0`

2. **`handle` is a contextual keyword** (HANDLE token, used in
   `handle body { handlers }` effect expressions) and parses as a token,
   not an ident, so it cannot be a function name. Renamed `Router.affine`
   `pub fn handle(...)` → `pub fn dispatch(...)`. The function dispatches
   a request to its matching route handler, so the rename is also a more
   accurate name. The doc-comment is updated to match.

Result: with these edits + the four parser PRs gated on this issue
(hyperpolymath/affinescript#370, #371, #372, #373, #376), all 13
hand-ported `.affine` files reach Resolution (no parse errors) under
`affinescript check`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request May 26, 2026
…tch workflow regex (#374)

## Summary

The `governance / Language / package anti-pattern policy` job in the
reusable
[governance-reusable.yml](https://github.com/hyperpolymath/standards/blob/main/.github/workflows/governance-reusable.yml)
scans `.claude/CLAUDE.md` for a section heading matching the regex
`TypeScript [Ee]xemptions` to find the exemption table.

This repo's heading was `### TypeScript / JavaScript Exemptions
(Approved)`. The `/ JavaScript ` between the two words prevents the
regex from matching.

The parser then falls through to the next mention of the phrase ("the
TypeScript exemptions above") in the **Runtime Exemptions** section's
prose, and parses the **Runtime Exemptions** table — picking up 2 JS
entries (`packages/affinescript-cli/mod.js`,
`editors/vscode/test/**/*.js`) instead of the 3 TypeScript entries
(including `affinescript-deno-test/*.ts`).

Result:
`affinescript-deno-test/{cli,example/smoke_driver,lib/{compile,discover,runner}}.ts`
were reported as forbidden TypeScript files on every PR — blocking the
check estate-wide for weeks.

## Fix

Rename `### TypeScript / JavaScript Exemptions (Approved)` → `###
TypeScript Exemptions (Approved)`.

JavaScript exemptions already live in their own `### Runtime Exemptions
(Approved)` section immediately below, so the original heading's `/
JavaScript` was misleading anyway.

## Verification

Simulated the workflow's Python parser locally on the post-fix
`.claude/CLAUDE.md`:

```
Parsed 3 exemption(s)
✅ No TypeScript files outside allowlist (3 per-repo exemption(s) parsed).
```

(Pre-fix: parsed 2 exemptions — the wrong table — and 5
`affinescript-deno-test/*.ts` files were reported bad.)

## Test plan

- [x] Local Python simulation of the workflow parser
- [ ] CI: `governance / Language / package anti-pattern policy` green on
this PR
- [ ] Downstream: re-run CI on #370, #371, #372, #373 — same check
should turn green after merge

Refs gitbot-fleet#148 (unblocks affinescript #370/#371/#372/#373 et al)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hyperpolymath added a commit to hyperpolymath/gitbot-fleet that referenced this pull request May 26, 2026
…Refs #148) (#206)

## Summary

Two hand-port corrections after re-validating sustainabot's
ReScript→AffineScript files against the live `affinescript check`:

1. **OCaml-style float operators** `/.`, `*.`, `+.`, `-.` → AffineScript
`/`, `*`, `+`, `-`. AffineScript uses unified arithmetic operators for
Int and Float (see `examples/lessons/01_hello.affine`: `subtotal *
0.08`), so the OCaml separation was a hand-port artefact. Affected:
`GitHubApp.affine`, `Oikos.affine`, `Report.affine`.

2. **`handle` is a contextual keyword** in AffineScript (HANDLE token,
used in `handle body { handlers }` effect expressions), so it cannot be
a function name. Renamed `Router.affine`'s `pub fn handle(...)` → `pub
fn dispatch(...)`. The function dispatches a request to its matching
route handler — the rename is also more accurate.

## Gate

This PR's parse-time fix is fully effective only once the four parser
PRs gated on the same issue have merged:

- hyperpolymath/affinescript#370 (trailing-comma in fn params + expr
lists + effect-annotated lambda)
- hyperpolymath/affinescript#371 (fn-type with effect arrow in type
position)
- hyperpolymath/affinescript#372 (builtin/lowercase qualified paths +
TOTAL field name)
- hyperpolymath/affinescript#373 (underscore-prefix idents
`_key`/`_unused`)
- hyperpolymath/affinescript#376 (record-update spread at start)

## Test plan

- [x] After landing the 5 affinescript PRs + this PR: `affinescript
check` on all 13 sustainabot `.affine` files reaches at least Resolution
(no parse errors). 12 hit `Resolve.UndefinedModule` (stdlib not loaded
by the check), 1 (`tea/Sub.affine`) hit it from the start.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit to hyperpolymath/gitbot-fleet that referenced this pull request May 26, 2026
…on (#209)

## Summary

Adds the human-readable session report and its machine-readable
companion for the 2026-05-26 sustainabot ReScript→AffineScript hand-port
validation work (Refs #148).

- `docs/archive/SESSION-2026-05-26-sustainabot-148-validation.md` —
human-readable session record. Goal, approach (parser-fix vs
hand-port-rewrite decision rule), the 5 affinescript parser PRs + 1
gitbot-fleet hand-port PR, validation-oracle output, gotchas surfaced
(HANDLE-as-fn-name, no-OCaml-float-ops), and out-of-scope follow-ups
(#208).
- `docs/archive/README.md` — table updated with the new entry.
- `.machine_readable/SESSION-2026-05-26-148.a2ml` — structured
companion. PR list with branch names + scopes, before/after validation
counts, CI-status notes (incl. baseline noise), captured gotcha rules,
next-gate identification, downstream work that unblocks after #148.

The work itself ships in the PR queue (no source-file changes here).
Issue #148 closure is owner-merge gated per the `ISSUE-CLOSURE` rule.

## Cross-refs

- Refs #148, #206, #208
- Refs hyperpolymath/affinescript#370, #371, #372, #373, #376

## Test plan
- [x] Markdown renders correctly on GitHub preview
- [x] A2ML file follows existing structure in
`.machine_readable/STATE.a2ml`
- [x] `docs/archive/README.md` table updated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 41 issues detected

Severity Count
🔴 Critical 15
🟠 High 15
🟡 Medium 11

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Action actions/checkout@v6 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action denoland/setup-deno@v2 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/discover.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/packages/affine-js/types.d.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 41 issues detected

Severity Count
🔴 Critical 15
🟠 High 15
🟡 Medium 11

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Action actions/checkout@v6 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action denoland/setup-deno@v2 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/discover.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/packages/affine-js/types.d.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

hyperpolymath added a commit to hyperpolymath/hypatia that referenced this pull request May 26, 2026
…aml float ops (#332)

## Summary

Adds 5 patterns under language `"affine"`/`"affinescript"` to catch
parse-blocking hand-port leaks that surface when `.res` / `.ml` / `.re`
code is migrated to AffineScript. Both classes were discovered manually
during the sustainabot ReScript→AffineScript migration
(gitbot-fleet#148) and resolved via 5 parser PRs + a hand-port-rewrite
PR — but the underlying *signal* ("this code won't parse") only emerged
after `affinescript check`. Capturing them as Hypatia rules surfaces the
signal at PR-review time, before the iteration loop.

## The two pitfalls

1. **`handle` as a fn name.** AffineScript's `handle` is a reserved
keyword (HANDLE token), used by the effect-handler expression form
`handle body { handler_arms }`. So `pub fn handle(...)` is a parse
error. Severity: `:high`. Recommended rename: `dispatch`,
`handle_request`, `handle_event`.

2. **OCaml-style float operators.** AffineScript uses unified `+ - * /`
for both Int and Float. The OCaml form `+. -. *. /.` is never accepted.
Four separate patterns (one per operator) — each requires an
operand-character preceding the operator-and-dot, so `.0`-suffixed
numeric literals (e.g. `1.0 + x`) don't false-positive. Severity:
`:high` (parse blocker).

## Integration

- New `@affine_hand_port_patterns` module attribute in
`lib/rules/code_safety.ex` (immediately after `@rescript_patterns`).
- Dispatch registered for both `"affine"` and `"affinescript"` language
keys (mirrors the `javascript`/`typescript` alias precedent at lines
384-385).
- File-extension fallback in `lib/rules/rules.ex` after the existing
JS/TS web-security fallback: any `.affine` file gets the hand-port scan
even when the caller passes `language = nil` or an unrelated upstream
default.

## Test plan

- [x] Regex spot-check via `elixir -e 'Regex.scan(...)'`: `pub fn
handle(` matches; `let x = a /. 1.0` matches; `let x = a / 1.0` does NOT
match (true negative). All three confirmed.
- [x] `elixirc` of both modified files succeeds (only pre-existing
module-resolution warnings unrelated to this patch).
- [ ] `mix compile` blocked by a Phoenix-dep / Elixir-1.15 mismatch in
the sandbox env; not in scope for this PR.

## Cross-refs

- Refs hyperpolymath/gitbot-fleet#148
- Refs hyperpolymath/affinescript#370, #371, #372, #373, #376
- Refs hyperpolymath/gitbot-fleet#206

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath
hyperpolymath merged commit d15b664 into main May 26, 2026
20 of 21 checks passed
@hyperpolymath
hyperpolymath deleted the claude/parser-trailing-comma-148 branch May 26, 2026 09:27
hyperpolymath added a commit that referenced this pull request May 26, 2026
…-fleet#148) (#371)

## Summary

Adds \`fn(A, B) -{E}-> C\` as a type-position production in
\`type_expr_primary\`, mirroring the prefix-row arrow that
\`type_expr_arrow\` and \`return_type\` already accept.

For multi-arg \`fn\`, the row attaches to the **innermost** arrow — that
is where the call actually performs the effect, and it matches the
single-arg case where \`A -{E}-> R\` puts the row on the lone arrow.

Lowering:

\`\`\`
fn(A, B) -{E}-> R   ≡   A -> (B -{E}-> R)
\`\`\`

## Why

Required by sustainabot hand-port (gitbot-fleet#148) — \`Router.affine\`
declares \`fn(Http::Request) -{IO}-> Http::Response\` and similar
shapes; the prefix-row form is the natural ergonomic for "a function
that performs effect E".

## Conflict-cost

Zero. The \`FN LPAREN ... RPAREN\` prefix already disambiguates against
every other type-position production, so adding the \`MINUS LBRACE eff
RBRACE ARROW\` continuation here introduces no new lookahead conflict
beyond the existing \`type_expr_arrow\` row-arrow rule it mirrors.
Parser builds with **21 S/R + 1 R/R**, identical to the pre-patch
baseline.

## Test plan

- [x] \`dune build\` green
- [x] Conflict count unchanged: 21 S/R + 1 R/R
- [ ] CI green
- [ ] Smoke: \`fn handle: fn(Http::Request) -{IO}-> Http::Response;\`
parses

## Companion PRs (gitbot-fleet#148 spine)

1. trailing-comma in fn params + expr lists (#370)
2. **this PR** — fn-type with effect arrow in type position
3. builtin/lowercase qualified paths + TOTAL field name
4. lexer \`_\`-prefix idents
5. (hypatia) Levenshtein perf fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)
hyperpolymath added a commit that referenced this pull request May 27, 2026
…efs gitbot-fleet#148) (#372)

## Summary

Three closely-related \`expr_primary\` / \`field_name\` extensions, all
driven by sustainabot hand-port shapes (gitbot-fleet#148):

### 1. Builtin-type-qualified value path

\`Int::to_string(n)\`, \`String::len(s)\`, \`Float::to_string(f)\`, etc.
The built-in type names are reserved keyword tokens
(NAT/INT_T/BOOL/FLOAT_T/STRING_T/CHAR_T), not UPPER_IDENT, so the
existing \`upper_ident COLONCOLON lower_ident\` production never fired
for them. Six new productions, one per builtin keyword, producing the
canonical \`ExprField (ExprVar TypeName, fname)\` shape that [Resolve]
already expects.

### 2. Lowercase-module-qualified value path

\`json::encode_string(s)\`, \`string::join(xs, sep)\`, etc. The stdlib
already defines lowercase modules (\`module json;\`, \`module string;\`)
and \`use json::{...}\` parses, but the expression-position qualified
path only covered uppercase modules. The new \`lower_ident COLONCOLON
lower_ident\` rule mirrors the upper-ident form.

LR(1)-safe: the only competing reduction for \`lower_ident\` at this
position is \`name = lower_ident { ExprVar ... }\`, and on a COLONCOLON
lookahead no rule starting from \`expr_primary COLONCOLON\` exists, so
the parser shifts unambiguously.

### 3. \`total\` as a record-field name

Added to \`field_name\` alongside the existing \`handle\`
contextual-keyword exception. Safe by the same reasoning HANDLE is safe:
as a function-decl modifier TOTAL appears between \`visibility?\` and
\`FN\`, never after DOT/before COLON in a record body; as a field name
it appears between COLON / DOT and the field value. Surrounding token
disambiguates. Used by sustainabot's \`HealthIndex.total\`,
\`Recommendation.total\`, etc.

## Conflict-cost

Zero. Parser builds with **21 S/R + 1 R/R**, identical to the pre-patch
baseline.

## Test plan

- [x] \`dune build\` green
- [x] Conflict count unchanged: 21 S/R + 1 R/R
- [ ] CI green
- [ ] Smoke: \`Int::to_string(42)\` parses
- [ ] Smoke: \`json::encode_string(\"x\")\` parses
- [ ] Smoke: \`r.total\` parses as field access; \`#{ total: 99 }\`
parses as record literal

## Companion PRs (gitbot-fleet#148 spine)

1. trailing-comma in fn params + expr lists (#370)
2. fn-type with effect arrow in type position (#371)
3. **this PR** — builtin/lowercase qualified paths + TOTAL field name
4. lexer \`_\`-prefix idents
5. (hypatia) Levenshtein perf fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit to hyperpolymath/gitbot-fleet that referenced this pull request May 27, 2026
…ep + Hypatia) (#211)

## Summary

Adds the second human + machine readable session record for 2026-05-26.
Turn 2 follows the gitbot-fleet#148 sustainabot validation (turn 1,
captured in `SESSION-2026-05-26-sustainabot-148-validation.md`).

The user's four sequenced asks for turn 2:
1. Set automerge on the seven #148 PRs (7/7 enabled; 4 cleared
in-session; 2 still queued at session-write time)
2. Resolve CI/CD baseline noise **foundationally at
root/source/upstream**
3. Pass new lessons to the Hypatia ruleset if not already captured
4. Document everything for humans and machines
5. Search the estate for SafeDOMExample and resolve estate-wide

## Outcome

10 PRs merged this turn + 4 outstanding (2 BLOCKED awaiting review, 2
still queued in baseline-CI). 5 dialect-distinct SafeDOMExample variants
resolved across 50 stale copies in 4 repos (1,267 `.res` siblings
deferred to affinescript#57 Phase 2).

## Files

- `docs/archive/SESSION-2026-05-26-cicd-foundational-fixes.md` —
Human-readable; full per-phase narrative with root-cause traces.
- `docs/archive/README.md` — Table updated with the new entry.
- `.machine_readable/SESSION-2026-05-26-cicd.a2ml` — Machine-readable
companion; per-phase PR rosters, agent-memory audit, captured lessons.

## Cross-refs

- Refs gitbot-fleet#148, #208, #210
- Refs hyperpolymath/affinescript#370, #371, #372, #373, #376, #381
- Refs hyperpolymath/standards#185, #188
- Refs hyperpolymath/hypatia#332
- Refs metadatastician/burble#92
- Refs hyperpolymath/claude-gecko-browser-extension#30

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant