Skip to content

Implement $fy migration for shell and competitor scripts (JS + Rust) - #133

Merged
konard merged 15 commits into
mainfrom
issue-1-7de9505b
Oct 24, 2026
Merged

konard merged 15 commits into
mainfrom
issue-1-7de9505b

Conversation

@konard

@konard konard commented Sep 9, 2025 •

Copy link
Copy Markdown
Member

$fy now converts shell scripts and competitor JavaScript into executable command-stream modules in both JavaScript and Rust. Shell translation automatically loads command-stream through use-m; competitor translation uses the matching command-stream compatibility entry points.

Fixes #1

Resulting behavior

npm install use-m
npx command-stream fy deploy.sh deploy.mjs
node deploy.mjs staging
command-stream fy old-zx.mjs migrated.mjs
printf 'import { execa } from "execa";' | command-stream fy --from execa
  • The default shell rule retains the complete source and selected interpreter, passes arguments/stdin through, and preserves exit status. Generated modules use await use('command-stream'); --no-use-m supports an already installed library. File translation retains the source basename for $0.
  • JavaScript ASTs and Rust JavaScript syntax trees identify static imports, re-exports, literal dynamic imports and CommonJS calls for zx, Execa, ShellJS, cross-spawn and Bun's shell API. Rules select corresponding command-stream/* entry points while preserving surrounding source. .cjs/--commonjs produces an executable ES module using createRequire, preserving top-level this and exports identity.
  • Both front ends formalize into actual meta-language LinkNetwork syntax/token links, then apply LinkQuery and TranslationRuleSet substitutions. The structural shell compiler remains available with --structural; current main's root fy() helper and compatibility APIs remain intact.
  • Shared shell, structural, competitor and CommonJS fixtures enforce byte-for-byte JavaScript/Rust output parity. Diagnostics cover unsupported competitor subpaths and locally bound require; comments, ordinary strings, regexes and property calls are preserved. Output files cannot overwrite existing files.
  • Merged current main, including the lightweight core and Rust 1.10.1 release. $fy is registered in the shared core without loading the compiler eagerly; cold ESM/CommonJS entry points remain within the 100-module budget. Updated CLI/API documentation and executable examples, and retained minor-release changeset/changelog triggers for both packages.

Review requirements

The July review and October follow-up are addressed by the formalize/substitute implementation, competitor migrations, default use-m output and resolved merge. Representation/evaluation separation and composable rules were informed by relative-meta-logic and formal-ai; neither adds a runtime dependency.

Rechecked published meta-language packages: npm latest remains 0.46.0; Rust is now 1.0.0. Rust's public dynamic-arity insertion replaces the old chunk-node workaround. Remaining evaluator differences are documented with executable probes in the integration findings and reported in the upstream recheck. Translation works independently of further upstream releases. Rust meta-language 1.0.0 still pins tree-sitter to 0.27.0 exactly; the existing freshness exception policy accepts the documented open upstream blocker.

The latest main release run also exposed changeset merging before remote synchronization. Merging now happens inside the version script after synchronization. A real Git regression with two pending changesets and a concurrent Rust release reproduces the original dirty-tree rebase failure and passes with the corrected order. The downloaded main failure log records the exact error at ci-logs/main-javascript-40081061680.log:51286.

Reproduction and validation

fixtures/fy/competitors.js previously had no source migration path; fixtures/fy/sample.sh did not produce the requested use-m module. Their expected generated modules now have shared JavaScript/Rust regression tests. fixtures/fy/commonjs.cjs also reproduces the formerly incorrect top-level this binding; its generated module must preserve the original exports identity. Runtime tests execute all five competitor migrations in Node and Bun, compare shell functions/arguments/stdin/status with the original interpreter, and exercise the actual use-m loader and both JS CLI entry points. Native Rust tests cover CLI/stdin, virtual $fy, output safety and identical fixtures.

Local validation:

  • Full JavaScript suite after the core merge: 2,871 passed, 10 existing skips. The final 216 workflow/release regressions also pass, including a concurrent-release Git reproduction; the 54 focused core/translator tests and five Node core tests pass. Migration runtime tests cover CommonJS binding in Node and Bun.
  • Node process/entry-point regression suite after the core merge: 124 passed.
  • Full Rust suite including doctests after the Rust 1.10.1 merge: 958 passed, 7 existing ignores; final migration/CLI integration suites passed.
  • ESLint, Prettier, duplication checks, TypeScript declarations, frozen Bun install, Rust formatting and Clippy passed.
  • Cross-runtime parity: all 37 features pass in JS and Rust, with Node/Bun observations matching; all 39 generated documentation files are current.
  • Rust documentation and final verified crate packaging pass (230 files, 494.5 KiB compressed for Rust 1.10.1).

All 49 PR checks pass on f5c5ee813f369bdd94ff3fbec2263607171702ac, including Linux/macOS/Windows test matrices, Node 22/24/26 checks, release build and verified packaging, benchmarks, parity, dependency freshness and security. Failed-run logs were downloaded and checked against commit timestamps and SHAs: the benchmark locked-build failure (ci-logs/benchmarks-40078904274.log:639) reported cannot update the lock file ... because --locked was passed and required refreshing its dependency graph; generated docs (ci-logs/parity-40078904294.log:805) reported Generated documentation is out of date and required the updated virtual-command catalog; freshness (ci-logs/dependencies-40079434519.log:756) reported pending lockfile updates and required updating the benchmark's compatible smallvec version. Execa and fancy-regex were updated to their latest releases, and the resolved Execa type blocker was removed. CodeQL's shebang backtracking finding is fixed with a line parser, a bounded scaling probe and a regression test; GitHub now marks the alert fixed.

See the translator guide for options, supported entry points and runtime requirements. JavaScript input means standard JavaScript (TypeScript should be compiled first); Bun migration targets its shell API. Keep migrated files beside their inputs when relative imports depend on that location.

Adding CLAUDE.md with task information for AI processing.
This file will be removed when the task is complete.

Issue: #1
@konard konard self-assigned this Sep 9, 2025
konard and others added 2 commits September 9, 2025 23:22
- Add $fy virtual command that converts shell scripts to command-stream mjs format
- Support stdin input for single commands and file input for full scripts
- Handle shell operators: &&, ||, ; with proper JavaScript equivalents
- Convert pipelines, sequences, variables, exports, and comments
- Preserve script structure while translating to command-stream syntax
- Add comprehensive test suite with 14 test cases
- Include example scripts and debug utilities

Features:
- Single command conversion: echo "ls -la" | $fy
- File conversion: $fy script.sh [output.mjs]
- Shell operator translation (&&, ||, ;)
- Pipeline preservation
- Comment and structure preservation
- Variable and export handling
- Error handling for missing files

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

Co-Authored-By: Claude <noreply@anthropic.com>
@konard konard changed the title [WIP] tool (sh to mjs translator) tool (sh to mjs translator) - Issue #1 Sep 9, 2025
@konard
konard marked this pull request as ready for review September 9, 2025 20:30
@konard

konard commented Jul 25, 2026 •

Copy link
Copy Markdown
Member Author

We should use https://github.com/link-foundation/meta-language, and may be logic from https://github.com/link-foundation/relative-meta-logic, in order to actually implement rule based translation, where we first formalize into meta language, and after it use substitution rules on top of meta language to actually finish the translation.

Any missing features from https://github.com/link-foundation/meta-language must be reported, so we can fix that first, before continuing this pull request.

Also may be you will learn something from github.com/link-assistant/formal-ai related to that specific task.

So we use all the best practices possible for both JavaScript and Rust.

@konard
konard marked this pull request as draft July 25, 2026 19:11
@konard

konard commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

🤖 AI Work Session Started

Starting automated work session at 2026-07-25T19:11:32.340Z

The PR has been converted to draft mode while work is in progress.

This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback.

konard added 2 commits July 25, 2026 19:14
The first draft of $fy translated shell to JavaScript with ad-hoc string
munging. This replaces it with the two-stage pipeline requested in review:

1. `formalizeShell` parses the script and formalizes it into a
   link-foundation/meta-language `LinkNetwork` of typed syntax links
   (`LinkType.Syntax`, language `Shell`).
2. `buildRuleSet` declares Shell -> JavaScript `TranslationRule`s and a
   small engine substitutes them over the network, rendering each node in
   the sub-language its parent asks for (statement, command, value,
   expression).

Missing meta-language features found while doing this are reported in
docs/meta-language-gaps.md so they can be fixed upstream.

Also fixes two genuine bugs found on the way:

- `parseSimpleCommand` treated reserved words as block terminators in any
  position, so `echo done | tr a-z A-Z` lost its argument. Reserved words
  are only reserved in command position.
- `getStdinData` returned the stdio *mode* string as stdin *data*, so
  every virtual command saw the literal text `inherit` on stdin.

experiments/fy-pipeline-repro.sh reproduces the parser bug and
examples/fy-translate.mjs demonstrates the whole pipeline.
The reviewer asked for the best practices of both languages, so the
rule-based translator now exists in Rust as well: rust/src/fy mirrors
js/src/fy stage for stage (formalize into a meta-language links network,
then substitute through a TranslationRuleSet).

fixtures/fy/ holds one shell script and the module both implementations
must produce from it; rust/tests/fy.rs and js/tests/fy-tool.test.mjs
assert against those bytes, so the two cannot drift apart.

Also translate ${1:-default}: the brace expander only accepted names, so
positional and special parameters with a default fell through to the
untranslated \${...} escape hatch.

Reports two further meta-language findings: the Rust crate has no public
dynamic-arity link insertion (worked around by chunking), and
with_reference_capture exists in Rust 0.54 but not in JS 0.46.
@konard

konard commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

We should use meta-language, and maybe logic from relative-meta-logic, in order to actually implement rule based translation ... Any missing features from meta-language must be reported ... So we use all the best practices possible for both JavaScript and Rust.

Done. $fy is no longer a string rewriter; it is a two-stage rule-based translation, implemented twice — once in JavaScript, once in Rust — against meta-language's own types.

1. Formalize → substitute

  1. Formalize (js/src/fy/shell-formalizer.mjs, rust/src/fy/formalizer.rs): the shell source becomes a LinkNetwork of LinkType.Syntax nodes over LinkType.Token leaves, with a LanguageProfile declaring the supported surface (validateNetwork rejects anything outside it).
  2. Substitute (js/src/fy/translation-rules.mjs + rule-engine.mjs, rust/src/fy/rules.rs + engine.rs): a TranslationRuleSet of ~40 TranslationRule/TranslationTemplate pairs, one per construct, selected per link by LinkQuery. The rule table holds all the shell and JavaScript knowledge; the engine holds none.

2. Missing meta-language features — reported

Full write-up with reproductions in js/docs/meta-language-gaps.md, filed upstream as link-foundation/meta-language#183. Summary:

# Gap
1 TranslationRuleSet.render returns the template verbatim — placeholders are never substituted
2 No recursion into nested nodes
3 Only the first matching rule is ever applied (per network, not per link)
4 No reference-capture API on TranslationRule in JS (Rust 0.54 already has with_reference_capture)
5 No variadic placeholder (`{*children
6 No conditional template segments ({?name}…{/name})
7 No target sub-language / rendering context
8 No indentation-aware substitution
9 No builtin Shell profile; parse() emits no syntax nodes
10 Rust only: insert_syntax_node/insert_link are const-generic and insert_dynamic_link is pub(crate), so a run-time parser cannot insert a node of run-time arity

Gaps 1–3 block rule-based translation entirely, so rule-engine.mjs / engine.rs implement 1–8 over meta-language's own types and are written to collapse into a single upstream call once these land. Gap 10 is worked around by chunking wide nodes and flattening them again in the engine.

Also confirmed on the Rust side by reading the crate: TranslationRuleSet::render is pub(crate), applies only the first matching rule, and does not recurse.

3. JavaScript / Rust parity

fixtures/fy/sample.sh is a script exercising every supported construct; fixtures/fy/sample.mjs is the module both implementations produce from it, byte for byte. rust/tests/fy.rs and js/tests/fy-tool.test.mjs each assert against those files, so the two cannot drift apart silently.

cargo test is green (161 lib + all integration tests, 38 of them new fy tests); bun test tests/fy-tool.test.mjs is 30/30. The JS suite also executes a translated script and compares its output to sh running the original.

@konard konard changed the title tool (sh to mjs translator) - Issue #1 Implement $fy: rule-based shell to mjs translation (JS + Rust) Jul 25, 2026
konard added 2 commits July 25, 2026 20:39
The Node.js compatibility job imports js/src/$.mjs without installing
dependencies, and the $fy command pulled meta-language in statically, so
the module no longer loaded on Node:

  Module failed to load: Cannot find package 'meta-language' imported
  from js/src/fy/shell-formalizer.mjs

Importing the translator inside the command handler keeps meta-language
out of the library's import graph until $fy actually runs.
The Windows job failed on both new tests. A script checked out with CRLF
endings reached the lexer with the \r attached to each word, which broke
the parse ('rule `for` has no JavaScript:command template'), so both
translators now normalize line endings first. The golden fixture must
also arrive with LF for a byte-for-byte comparison to mean anything,
which .gitattributes now enforces.

The run-the-translation test passed a URL pathname as a module
specifier; on Windows that is '/D:/...', which is not importable, so it
passes the file URL instead.
@konard
konard marked this pull request as ready for review July 25, 2026 20:51
@konard

konard commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost: $29.524047

📊 Context and tokens usage:

Claude Opus 5: (7 sub-sessions)

  1. 112.5K / 1M (11%) input tokens, 41.6K / 128K (33%) output tokens
  2. 117.0K / 1M (12%) input tokens, 40.3K / 128K (31%) output tokens
  3. 116.9K / 1M (12%) input tokens, 23.6K / 128K (18%) output tokens
  4. 115.1K / 1M (12%) input tokens, 29.2K / 128K (23%) output tokens
  5. 116.3K / 1M (12%) input tokens, 33.9K / 128K (26%) output tokens
  6. 116.9K / 1M (12%) input tokens, 26.0K / 128K (20%) output tokens
  7. 37.0K / 1M (4%) input tokens, 867 / 128K (1%) output tokens

Total: (16.2K new + 593.9K cache writes + 34.2M cache reads) input tokens, 256.4K output tokens, $29.524047 cost

🤖 Models used:

  • Tool: Anthropic Claude Code
  • Requested: opus (claude-opus-5)
  • Thinking level: off (disabled)
  • Model: Claude Opus 5 (claude-opus-5)

📎 Log file uploaded as Gist (10705KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard

konard commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

✅ Ready to merge

This pull request is now ready to be merged:

  • All CI checks have passed
  • No merge conflicts
  • No pending changes

Monitored by hive-mind with --auto-restart-until-mergeable flag

@konard

konard commented Oct 24, 2026 •

Copy link
Copy Markdown
Member Author

Double check we fully deliver all the requirements and also make sure we have translators from source code of our competitors to our source code, as well as automatic translation for shell scripts into mjs scripts that uses our library + use-m.

#133 (comment) - double check all from here also delivered.

We don't wait until latest version of meta-language will be fully done, yet we can report issues if we have something missing there.

And all conflicts must be resolved.

@konard
konard marked this pull request as draft October 24, 2026 08:07
@konard

konard commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

🤖 AI Work Session Started

Started at 2026-10-24T08:07:56.165Z. Please wait for it to finish before giving feedback.

Runtime: solve v2.39.6 · tool codex · model gpt-6.1-sol · task image konard/hive-mind-dind:2.39.6@sha256:2d5d8294e932465fc7f8065662e8bd9c1f2d25e00a20a6c25c388bcec6d2c00d

@konard konard changed the title Implement $fy: rule-based shell to mjs translation (JS + Rust) Implement $fy migration for shell and competitor scripts (JS + Rust) Oct 24, 2026
Comment thread js/src/fy/source-translator.mjs Fixed
@konard
konard marked this pull request as ready for review October 24, 2026 09:14
@konard
konard marked this pull request as draft October 24, 2026 09:15
@konard
konard marked this pull request as ready for review October 24, 2026 09:42
@konard

konard commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

Working session summary

PR #133 is updated and ready for review. Shell and competitor translators are implemented in JavaScript and Rust, and merge conflicts are resolved.

Passed: 2,871 JavaScript tests, 958 Rust tests, 124 Node regressions, and all 49 CI checks. The working tree is clean.


This summary was automatically extracted from the AI working session output.

@konard

konard commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost estimation:

  • Model: GPT-6.1 Sol
  • Provider: OpenAI
  • Public pricing estimate: $9.812416

📊 Context and tokens usage:

  • 566.4K input tokens across requests (cumulative, larger than the 272K context window, so not one request's context), 156.3K / 128K (122%) output tokens

Total: (566.4K + 26.0M cached) input tokens, 156.3K output tokens, $9.812416 cost

🤖 Models used:

  • Tool: OpenAI Codex
  • Requested: gpt-6.1-sol
  • Thinking level: xhigh (~31999 tokens)
  • Model: GPT-6.1 Sol (gpt-6.1-sol)

📎 Log file uploaded as Gist (5065KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard

konard commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

✅ Ready to merge

This pull request is now ready to be merged:

  • All CI checks have passed
  • No merge conflicts
  • No uncommitted changes

Note: Issue #1 has been closed. Reopen issue #1 so auto-merge can complete, or merge this pull request manually.

@konard
konard merged commit 1d63fb0 into main Oct 24, 2026
55 checks passed
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.

$fy tool (sh to mjs translator)

2 participants