semantifold 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +13 -6
  2. package/build/index.d.ts +32 -3
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +99 -4
  5. package/build/src/acceptance.d.ts +22 -0
  6. package/build/src/acceptance.d.ts.map +1 -0
  7. package/build/src/acceptance.js +339 -0
  8. package/build/src/array.d.ts +7 -0
  9. package/build/src/array.d.ts.map +1 -0
  10. package/build/src/array.js +18 -0
  11. package/build/src/artifact-path.d.ts +13 -0
  12. package/build/src/artifact-path.d.ts.map +1 -0
  13. package/build/src/artifact-path.js +30 -0
  14. package/build/src/artifacts.d.ts +7 -0
  15. package/build/src/artifacts.d.ts.map +1 -0
  16. package/build/src/artifacts.js +389 -0
  17. package/build/src/backends/identifiers.d.ts.map +1 -1
  18. package/build/src/backends/identifiers.js +10 -2
  19. package/build/src/backends/index.d.ts.map +1 -1
  20. package/build/src/backends/index.js +19 -37
  21. package/build/src/backends/python.d.ts +8 -0
  22. package/build/src/backends/python.d.ts.map +1 -0
  23. package/build/src/backends/python.js +162 -0
  24. package/build/src/backends/scalars.d.ts.map +1 -1
  25. package/build/src/backends/scalars.js +2 -1
  26. package/build/src/backends/shared.d.ts.map +1 -1
  27. package/build/src/backends/shared.js +9 -6
  28. package/build/src/binary-mapping.d.ts +32 -0
  29. package/build/src/binary-mapping.d.ts.map +1 -0
  30. package/build/src/binary-mapping.js +285 -0
  31. package/build/src/diagnostic.d.ts +36 -4
  32. package/build/src/diagnostic.d.ts.map +1 -1
  33. package/build/src/diagnostic.js +42 -3
  34. package/build/src/frontends/index.d.ts.map +1 -1
  35. package/build/src/frontends/index.js +5 -23
  36. package/build/src/frontends/python.d.ts +14 -0
  37. package/build/src/frontends/python.d.ts.map +1 -0
  38. package/build/src/frontends/python.js +615 -0
  39. package/build/src/frontends/scalars.d.ts.map +1 -1
  40. package/build/src/frontends/scalars.js +6 -1
  41. package/build/src/language-registry.d.ts +113 -0
  42. package/build/src/language-registry.d.ts.map +1 -0
  43. package/build/src/language-registry.js +372 -0
  44. package/build/src/mapping.js +3 -2
  45. package/build/src/semantic/location.d.ts +7 -0
  46. package/build/src/semantic/location.d.ts.map +1 -1
  47. package/build/src/semantic/location.js +10 -1
  48. package/build/src/semantic/provenance.js +3 -3
  49. package/build/src/semantic/types.d.ts +399 -2
  50. package/build/src/semantic/types.d.ts.map +1 -1
  51. package/build/src/semantic/types.js +110 -2
  52. package/build/src/subprocess.d.ts +29 -0
  53. package/build/src/subprocess.d.ts.map +1 -0
  54. package/build/src/subprocess.js +207 -0
  55. package/build/src/timeout.d.ts +8 -0
  56. package/build/src/timeout.d.ts.map +1 -0
  57. package/build/src/timeout.js +11 -0
  58. package/build/src/toolchains.d.ts +83 -0
  59. package/build/src/toolchains.d.ts.map +1 -0
  60. package/build/src/toolchains.js +413 -0
  61. package/docs/architecture.md +7 -3
  62. package/docs/language-support.md +25 -4
  63. package/docs/parser-qualification.md +70 -0
  64. package/docs/source-maps.md +31 -4
  65. package/docs/testing.md +21 -2
  66. package/index.js +109 -4
  67. package/package.json +4 -2
  68. package/src/acceptance.js +359 -0
  69. package/src/array.js +19 -0
  70. package/src/artifact-path.js +34 -0
  71. package/src/artifacts.js +420 -0
  72. package/src/backends/identifiers.js +9 -1
  73. package/src/backends/index.js +16 -32
  74. package/src/backends/python.js +175 -0
  75. package/src/backends/scalars.js +1 -0
  76. package/src/backends/shared.js +8 -5
  77. package/src/binary-mapping.js +305 -0
  78. package/src/diagnostic.js +34 -2
  79. package/src/frontends/index.js +5 -20
  80. package/src/frontends/python.js +689 -0
  81. package/src/frontends/scalars.js +5 -0
  82. package/src/language-registry.js +403 -0
  83. package/src/mapping.js +2 -1
  84. package/src/semantic/location.js +10 -0
  85. package/src/semantic/provenance.js +2 -2
  86. package/src/semantic/types.js +124 -1
  87. package/src/subprocess.js +203 -0
  88. package/src/timeout.js +12 -0
  89. package/src/toolchains.js +444 -0
  90. package/tsconfig.json +1 -0
@@ -0,0 +1,70 @@
1
+ # Parser dependency qualification
2
+
3
+ Task 015 adds no parser dependency. It freezes the gate that every owning language task must complete before changing `package.json` or `package-lock.json`. Qualification is evidence for one exact candidate release; a package name or upstream reputation alone is not approval.
4
+
5
+ ## Required record
6
+
7
+ The owning task must add a checked-in table row containing all of the following before its manifest edit:
8
+
9
+ | Evidence field | Required value |
10
+ | --- | --- |
11
+ | npm distribution | Exact registry package name and version; no range |
12
+ | upstream identity | Repository URL, release tag, and full commit SHA |
13
+ | legal/supply chain | SPDX license, npm integrity from the lockfile, and registry provenance checked |
14
+ | runtime compatibility | Exact `tree-sitter` Node binding version, grammar ABI/language version, and clean Node 24 install/import result on canonical Linux |
15
+ | API/coordinates | Typed Node API availability; parser UTF-8 byte/point behavior; tested conversion to one-based UTF-16 Semantifold locations |
16
+ | parser behavior | Comment visibility plus explicit missing/error/recovery-node and malformed-input results |
17
+ | Task 001–004 corpus | Every required declaration, block, statement, expression, type, identifier, literal, and call node; complete named/unnamed child traversal |
18
+ | differential result | Official compiler command/version and acceptance comparison for community grammars; discrepancies enumerated |
19
+ | repository audit | Proof that no generated archive, Git URL/tarball dependency, postinstall download, runtime fetch, or vendored parser binary entered the repository |
20
+
21
+ The record includes the exact corpus commands and hashes/paths, the installed dependency tree, and the tool output needed to reproduce the result. Any failure blocks that language task. It does not permit a source-text fallback, ignored recovery node, unrecorded parser substitution, runtime download, or softened test.
22
+
23
+ ## Owning-task routes
24
+
25
+ All Tree-sitter languages use the official `tree-sitter` Node binding. The language task, not Task 015, selects and pins the exact npm versions and records the lockfile integrity after the gate passes.
26
+
27
+ | Owning task | Language | Required upstream route | Qualification state before owner starts |
28
+ | --- | --- | --- | --- |
29
+ | 016 | Python | official [`tree-sitter/tree-sitter-python`](https://github.com/tree-sitter/tree-sitter-python) grammar package | blocked until the exact release record above passes |
30
+ | 017 | C# | official [`tree-sitter/tree-sitter-c-sharp`](https://github.com/tree-sitter/tree-sitter-c-sharp) grammar package | blocked until the exact release record above passes |
31
+ | 018 | C | official [`tree-sitter/tree-sitter-c`](https://github.com/tree-sitter/tree-sitter-c) grammar package | blocked until the exact release record above passes |
32
+ | 019 | C++ | official [`tree-sitter/tree-sitter-cpp`](https://github.com/tree-sitter/tree-sitter-cpp) grammar package | blocked until the exact release record above passes |
33
+ | 020 | Rust | official [`tree-sitter/tree-sitter-rust`](https://github.com/tree-sitter/tree-sitter-rust) grammar package | blocked until the exact release record above passes |
34
+ | 022 | Swift | community [`alex-pinkus/tree-sitter-swift`](https://github.com/alex-pinkus/tree-sitter-swift) candidate plus differential `swiftc` checks | candidate only; blocked until exact release and differential record pass |
35
+ | 023 | Kotlin/JVM | community [`fwcd/tree-sitter-kotlin`](https://github.com/fwcd/tree-sitter-kotlin) candidate plus differential `kotlinc` checks | candidate only; blocked until exact release and differential record pass |
36
+ | 024 | Go | official [`tree-sitter/tree-sitter-go`](https://github.com/tree-sitter/tree-sitter-go) grammar package | blocked until the exact release record above passes |
37
+ | 029 | Dart | candidate selected and qualified by Task 029 | intentionally deferred |
38
+ | 031 | Zig | candidate selected and qualified by Task 031 | intentionally deferred |
39
+
40
+ Swift or Kotlin qualification failure leaves Task 022 or 023 blocked until its roadmap/source decision is explicitly revised. Parser archives produced by CI or release pages are not an alternative dependency route.
41
+
42
+ ## Task 016 Python qualification
43
+
44
+ Qualification passed on the canonical Node `v24.18.1`, npm `11.16.0`, Linux lane with Python `3.14.4`. Task 016 therefore uses exact registry dependencies `tree-sitter@0.25.1` and the official `tree-sitter-python@0.25.0`; neither dependency is a Git URL, tarball path, copied archive, or vendored repository artifact.
45
+
46
+ | Evidence | Qualified result |
47
+ | --- | --- |
48
+ | npm distribution | `tree-sitter@0.25.1`, integrity `sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==`; `tree-sitter-python@0.25.0`, integrity `sha512-eCmJx6zQa35GxaCtQD+wXHOhYqBxEL+bp71W/s3fcDMu06MrtzkVXR437dRrCrbrDbyLuUDJpAgycs7ncngLXw==` |
49
+ | upstream identity | Official [`tree-sitter/node-tree-sitter`](https://github.com/tree-sitter/node-tree-sitter) tag `v0.25.1`, commit `75a0eccfedc491e26843bd744bb6806f8a8bfff4`; official [`tree-sitter/tree-sitter-python`](https://github.com/tree-sitter/tree-sitter-python) annotated tag `v0.25.0` (`d326e4cad262cf681656e130960e49dfc04c03ea`), peeled commit/npm `gitHead` `293fdc02038ee2bf0e2e206711b69c90ac0d413f` |
50
+ | legal and registry provenance | Both packages declare MIT. `npm audit signatures` verified all four installed-package registry signatures and two attestations. The binding publishes npm SLSA provenance; the grammar release has a verified registry signature but did not advertise an npm attestation. The exact official tag/commit and registry integrity were therefore checked independently. |
51
+ | clean install/load | Credential-free `npm install --save-exact tree-sitter@0.25.1 tree-sitter-python@0.25.0` succeeded in an empty temporary package. ESM default imports loaded both CommonJS packages, `new Parser().setLanguage(Python)` succeeded, and `npm ls --all` resolved only `node-addon-api@8.9.2`, `node-gyp-build@4.8.4`, the exact binding, and the exact grammar. |
52
+ | ABI and typed API | The shipped grammar parser declares ABI `15`; the binding headers accept language ABI `13` through `15`. The binding ships `tree-sitter.d.ts`; the grammar ships `bindings/node/index.d.ts` with its language handle and node-type metadata. |
53
+ | lifecycle and packaged inputs | Each package has only `install: node-gyp-build`; there is no network-download lifecycle. Registry contents include Linux/macOS/Windows prebuilds plus binding/grammar C sources, grammar/node-type JSON, declarations, and licenses. No package archive, prebuild, generated parser, native binary, or `node_modules` content is checked into Semantifold. Cold installs may select the registry prebuild or compile the shipped sources, so the canonical image retains its native build prerequisites. |
54
+ | coordinates and recovery | The native grammar contract is byte-based, but `node-tree-sitter@0.25.1` feeds JavaScript strings to Tree-sitter as UTF-16LE and exposes normalized UTF-16 code-unit indexes (`src/parser.cc`; an astral prefix shifted bytes and code units differently). The adapter rejects lone surrogates and routes every binding boundary through the shared UTF-8-byte-to-UTF-16 converter for exact verification. Comments are visible named nodes. Malformed parameters, conditions, and incomplete expressions set `hasError` and expose `ERROR`; the adapter also checks `isMissing` exhaustively even though the malformed probes recovered with `ERROR`. |
55
+ | Tasks 001-004 tree corpus | Exhaustive `child(index)` traversal observed named and anonymous nodes/tokens for modules, definitions, exact annotations, parameters, blocks, initialized assignments, plain assignment, returns, calls/arguments, scalar literals, unary/binary/Boolean/comparison operators, comments, `if`/`elif`/`else`, and entry expression statements. Adapter specs cover rejected dynamic/recovery nodes and exact token spans. |
56
+ | official-runtime comparison | `python3 -m py_compile` accepted all five Python fixtures under deterministic UTF-8. Python is the official implementation and the grammar is official, so no community-grammar differential exception applies. |
57
+
58
+ Reproduce the install/supply-chain gate in an empty temporary directory with:
59
+
60
+ ```sh
61
+ npm init -y
62
+ env -u NODE_AUTH_TOKEN -u NPM_TOKEN -u npm_config__auth -u npm_config_token \
63
+ npm install --save-exact tree-sitter@0.25.1 tree-sitter-python@0.25.0
64
+ npm audit signatures
65
+ npm ls --all
66
+ npm pack tree-sitter@0.25.1 --dry-run --json
67
+ npm pack tree-sitter-python@0.25.0 --dry-run --json
68
+ ```
69
+
70
+ The accepted Python corpus is `spec/fixtures/{program.py,scalars/program.py,locals/program.py,operators/program.py,statements/program.py}` with SHA-256 values, in that order: `045ac897756dde15066878b56e40c84ee1aabdbd6fd32bddb9d0058ad512972a`, `b4acdda8c4c096ef7d76f83aec7dc148e049a0cd33583c55a79fd18ab1d6f6a6`, `123bb06c0833786168722ad7dfdcb47b95d37b502f6240fde4c12cd60f8e6715`, `35dce1aef96e5dc381baa1b2c39b58232d76b9e14350f0e81c4dd296da77df1e`, and `4907b25568006f65ce749f4c57b4b1eb9c7e6f655ad6be6e014d94c84885ada4`. Run `PYTHONPYCACHEPREFIX="$(mktemp -d)" python3 -m py_compile` over those paths so bytecode remains outside the checkout.
@@ -66,7 +66,7 @@ artifact.sourceMap // encoded Source Map v3 object
66
66
  artifact.sourceMapFilename
67
67
  ```
68
68
 
69
- Default filenames are `program.php`, `program.rb`, `program.js`, `program.ts`, and `Main.java`. Java artifacts may include directories but must retain the basename `Main.java`, matching the generated public class and making the returned filename directly compilable; any other basename is a located `UNSUPPORTED_CAPABILITY` diagnostic for the Java backend. `sources` may supply exact content for legacy or caller-assembled multi-source IR:
69
+ Default filenames are `program.php`, `program.rb`, `program.js`, `program.ts`, `Main.java`, and `program.py`. Defaults apply only when `filename` or `sourceMapFilename` is omitted (`undefined`); an explicitly supplied `null` remains invalid. Java artifacts may include directories but must retain the basename `Main.java`, matching the generated public class and making the returned filename directly compilable; any other basename is a located `UNSUPPORTED_CAPABILITY` diagnostic for the Java backend. Python artifacts likewise may include directories but retain the deterministic basename `program.py`; Python adds no package, environment, requirements, or dependency metadata. `sources` may supply exact content for legacy or caller-assembled multi-source IR:
70
70
 
71
71
  ```js
72
72
  generateArtifact({
@@ -79,7 +79,7 @@ generateArtifact({
79
79
  })
80
80
  ```
81
81
 
82
- This single-artifact envelope is intentionally reusable as an element in task 010's future ordered artifact set. Generation never writes files.
82
+ `generateArtifactSet()` wraps this exact result for original-five single-text targets and supplies one generated entry artifact, plus the referenced serialized mapping artifact when an external JavaScript-family directive is requested. `createGeneratedArtifactSet()` validates other single-module target shapes without introducing Task 010's source-project semantics. Its dense artifact array preserves caller/backend order and requires unique safe relative POSIX paths without file/directory prefix collisions, non-empty text or bytes, media types, one of `entry`/`source`/`manifest`/`support`/`mapping`/`resource`/`loader`, generated ownership, provenance, and exactly one entry. Binary storage remains private and every `content` read returns a detached `Uint8Array`, so mutations cannot alter the validated artifact or drift from byte provenance. Backends return no iterable or partial files: the complete set is validated before it becomes observable. Generation and set construction never write files.
83
83
 
84
84
  All outputs have deterministic LF newlines. Returned rich mappings are detached from caller-owned semantic locations and provenance before they are deeply frozen and internally indexed after trust-boundary validation. Freezing an artifact mapping therefore never freezes the mutable semantic module used to generate it. The rich map's `generated.content` is the exact returned code and its ordered, non-empty spans cover every UTF-16 code unit without gaps or overlaps:
85
85
 
@@ -89,9 +89,36 @@ All outputs have deterministic LF newlines. Returned rich mappings are detached
89
89
 
90
90
  Every span may carry a canonical `nodeId`, `symbolId`, semantic `role`, and Source Map `name`. Sources retain filenames, exact `sourcesContent` when available, and independent registry identity. Rich mappings are the serialization and interchange contract because v3 represents points rather than ranges and cannot retain semantic roles or the closed derived/synthetic model.
91
91
 
92
+ ## Binary and resource byte mappings
93
+
94
+ `SemantifoldByteMapping` v1 is separate from the text mapping schema:
95
+
96
+ ```js
97
+ const mapping = createByteMapping({
98
+ path: "program.wasm",
99
+ byteLength: bytes.byteLength,
100
+ ranges: [
101
+ {
102
+ generated: {start: 0, end: 8},
103
+ origin: {kind: "synthetic", reason: "WebAssembly header", relatedOrigins: []}
104
+ },
105
+ {
106
+ generated: {start: 8, end: bytes.byteLength},
107
+ nodeId: "node:4",
108
+ role: "encoded function body",
109
+ origin
110
+ }
111
+ ]
112
+ })
113
+ ```
114
+
115
+ The discriminator is `SemantifoldByteMapping`, version is `1`, and `coordinateSystem` is exactly `bytes`. The range array and every nested provenance-origin array are dense; ranges contain non-negative safe-integer half-open offsets, are non-empty, ordered, non-overlapping, and bounded by `generated.byteLength`. Source/derived origins retain ordinary UTF-16 original `SourceLocation` values; only the generated side is in bytes. Every source location has nondecreasing offsets and lexicographically ordered `(line, column)` endpoints; equal endpoints remain valid for zero-width origins. Synthetic regions require a non-empty reason. Optional `nodeId`, `symbolId`, and `role` fields are omitted only by `undefined`; any present value must be a non-empty string. These identities associate encoded regions without fabricating source positions. `parseByteMapping()` validates serialized input and `stringifyByteMapping()` emits canonical key-sorted JSON with one LF.
116
+
117
+ Text artifact provenance embeds the validated rich mapping and Source Map v3 projection. Its optional `sourceMapFilename` may be omitted, but when present must be a non-empty single-line string; optional Source Map arrays must likewise be omitted rather than set to `null`. Malformed values are rejected as `INVALID_ARTIFACT_SET`. Binary artifact provenance must embed a matching byte map whose path and byte length equal its artifact. Manifest, support, loader, and other source-free text/resources may instead carry an explicit artifact-level synthetic reason and related source origins. Optional related-origin identities follow the same `undefined`-only omission rule. Artifact-set construction detaches and deeply freezes every such related origin, including its location and start/end points.
118
+
92
119
  ## Source Map v3 and directives
93
120
 
94
- `artifact.sourceMap` exists for all five targets and is generated with `@jridgewell/gen-mapping`; lookup and import use `@jridgewell/trace-mapping`. Semantifold does not hand-code VLQ or tracing. The v3 map contains deterministic `file`, `sources`, `sourcesContent`, `names`, and `mappings` fields.
121
+ `artifact.sourceMap` exists for all six text targets and is generated with `@jridgewell/gen-mapping`; lookup, import, and artifact-set validation use `@jridgewell/trace-mapping`. Semantifold does not hand-code VLQ or tracing. Artifact-set validation requires dense `sources`, `names`, and optional `sourcesContent` arrays, decodes and canonically re-encodes the mapping payload, requires standard one-, four-, or five-field segments, rejects source or name indices outside their declared arrays, and requires the complete v3 value to equal the existing `toSourceMapV3()` projection of the accepted rich mapping. That projection comparison covers generated and original positions, source and name indexes, content, and the documented trailing unmapped directive span without a second encoder. The v3 map contains deterministic `file`, `sources`, `sourcesContent`, `names`, and `mappings` fields.
95
122
 
96
123
  Directives are opt-in and only valid for JavaScript and TypeScript:
97
124
 
@@ -100,7 +127,7 @@ generateArtifact({language: "javascript", module, mapDirective: "external"})
100
127
  generateArtifact({language: "typescript", module, mapDirective: "inline"})
101
128
  ```
102
129
 
103
- `external` appends `//# sourceMappingURL=<relative-map-url>`. `artifact.sourceMapFilename` remains the map artifact's literal logical filename, while the directive is relative to `artifact.filename`'s directory and URL-encodes each filesystem path segment without encoding structural slashes. Both logical paths resolve against one synthetic artifact root before relativization, so mixed leading-parent depths do not become false common segments; for example, generated `../../dist/out.js` and map `../maps/out.js.map` emit `../maps/out.js.map`. `dist/program.js` still advertises `dist/program.js.map` and emits `sourceMappingURL=program.js.map`; a literal `#` or `?` in the sidecar filename is emitted as `%23` or `%3F`. Names containing any ECMAScript line terminator—LF, CR, U+2028, or U+2029—are rejected before emission. `inline` appends a UTF-8 base64 data URL. The default is `none`. PHP, Ruby, and Java still receive sidecar map objects but never receive non-native map comments. Native Java debugger mapping such as JSR-45 would require class-file/toolchain integration and is not part of v1.
130
+ `external` appends `//# sourceMappingURL=<relative-map-url>`. `artifact.sourceMapFilename` remains the map artifact's literal logical filename, while the directive is relative to `artifact.filename`'s directory and URL-encodes each filesystem path segment without encoding structural slashes. Both logical paths resolve against one synthetic artifact root before relativization, so mixed leading-parent depths do not become false common segments; for example, generated `../../dist/out.js` and map `../maps/out.js.map` emit `../maps/out.js.map`. `dist/program.js` still advertises `dist/program.js.map` and emits `sourceMappingURL=program.js.map`; a literal `#` or `?` in the sidecar filename is emitted as `%23` or `%3F`. Names containing any ECMAScript line terminator—LF, CR, U+2028, or U+2029—are rejected before emission. `generateArtifactSet()` makes that reference materializable by adding the LF-terminated deterministic JSON serialization of `artifact.sourceMap` at the advertised safe path with role `mapping`; `generateArtifact()` retains its existing sidecar-object contract. `inline` appends a UTF-8 base64 data URL. The default is `none`. PHP, Ruby, and Java still receive sidecar map objects but never receive non-native map comments. Native Java debugger mapping such as JSR-45 would require class-file/toolchain integration and is not part of v1.
104
131
 
105
132
  The directive itself is an unmapped synthetic rich-map span. `artifact.sourceMap` is computed for the program before that self-referential comment is appended; this is required for a stable inline data URL and is the conventional v3 behavior. Use the rich mapping when the directive span itself matters. Rich registries may distinguish same-named sources by `sourceId`; Source Map v3 identifies sources by filename, so callers should use unique filenames when exporting such an assembled registry to v3.
106
133
 
package/docs/testing.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # Testing
2
2
 
3
- Specs use the released `@velocious/testing@0.0.0` framework and standalone `velocious-test` runner with one top-level `describe` per file. Direct value checks use the framework's `expect` API where it improves clarity; predicate-rich diagnostic assertions retain `node:assert/strict`. Frontend tests load real fixtures through Babel, php-parser, Prism, and Lezer and compare modeled meaning after removing source locations and the separately tested provenance index. They separately assert normalized JSDoc and TypeScript types and verify locations exist.
3
+ Specs use the released `@velocious/testing@0.0.0` framework and standalone `velocious-test` runner with one top-level `describe` per file. Direct value checks use the framework's `expect` API where it improves clarity; predicate-rich diagnostic assertions retain `node:assert/strict`. Frontend tests load real fixtures through Babel, php-parser, Prism, Lezer, and the qualified official Tree-sitter Python route and compare modeled meaning after removing source locations and the separately tested provenance index. They separately assert normalized annotations, complete named/anonymous child traversal, parser recovery rejection, and exact UTF-16 locations.
4
+
5
+ Task 015 has separate focused specs for immutable registry-derived discovery, role-specific diagnostics, safe transactional artifact sets, byte-coordinate provenance, exact tool discovery, and staged acceptance. Every caller-supplied ordered array in these contracts must be dense before element validation or iteration; each field independently retains its documented empty/non-empty rule. Error-path discovery uses controlled local fake executables. Positive runner coverage discovers and invokes real `php`, `ruby`, `node`, the lockfile-installed `tsc`, `javac`, and `java`; a missing or unsupported tool is a failure, never a skip. Runner assertions cover isolated directory cleanup, fixed `C.UTF-8` locale and UTC timezone, absence of undeclared ambient variables, exact argument arrays, and distinct invalid-input, missing, ambiguous, version, setup/materialization, launch, output-limit, nonzero-stage, signal, and timeout diagnostics. `ACCEPTANCE_LAUNCH_FAILURE` is restricted to explicit operating-system spawn errors. `ACCEPTANCE_OUTPUT_LIMIT` identifies a child that launched but exceeded the fixed 16 MiB capture limit; its diagnostic retains stage/tool/version and the deterministic limit evidence while omitting the oversized streams and child error object.
6
+
7
+ Task 016 extends the same fail-loud discovery and staged runner to configured Python 3. Native Python acceptance first invokes `python3 -m py_compile program.py`, then executes `python3 program.py` with deterministic `C.UTF-8`, `PYTHONUTF8=1`, and `PYTHONIOENCODING=utf-8`. Cross-language coverage generates and reparses Python from every original-five Tasks 001–004 fixture, then generates every original target from Python-derived IR, compiling where applicable and comparing exact runtime output. Missing Python is a test failure.
8
+
9
+ | Tool ID | Canonical command | Absolute-path override | Accepted canonical version |
10
+ | --- | --- | --- | --- |
11
+ | `php` | `php` | `SEMANTIFOLD_PHP` | PHP 8.x |
12
+ | `ruby` | `ruby` | `SEMANTIFOLD_RUBY` | Ruby 3.x |
13
+ | `node` | `node` | `SEMANTIFOLD_NODE` | Node 24.x |
14
+ | `tsc` | `tsc` | `SEMANTIFOLD_TSC` | TypeScript 7.x |
15
+ | `javac` | `javac` | `SEMANTIFOLD_JAVAC` | JDK 17–29 |
16
+ | `java` | `java` | `SEMANTIFOLD_JAVA` | JRE 17–29 |
17
+ | `python` | `python3` | `SEMANTIFOLD_PYTHON` | Python 3.x |
18
+
19
+ Canonical toolchain IDs must be non-empty primitive strings and are validated before property lookup or diagnostic formatting; malformed values normalize to `INVALID_TOOLCHAIN`, while an unknown valid string retains that string as its diagnostic identity. Discovery records the complete version output and first version line. Optional discovery and runner configuration defaults only when a field is `undefined`; explicit `null`, including in an environment entry, is invalid before lookup, setup, or launch. Explicit timeouts must be positive integers no greater than Node's exact timer ceiling of 2,147,483,647 milliseconds; larger values are rejected before lookup, setup, or launch rather than being clamped to one millisecond. Explicit environments must be ordinary or null-prototype records containing only enumerable own data properties with string or `undefined` values; inherited fields, accessors, symbol keys, collection/class instances, and other object shapes are rejected. Accepted environment values are synchronously detached before override lookup, setup, or launch. An override must be an executable absolute path resolving to a regular file; directories and other invalid file types report `TOOL_NOT_FOUND`. Canonical lookup examines the explicit PATH, accepts only executable regular files, resolves symlinks to exact paths, deduplicates aliases of the same executable, and rejects zero or multiple distinct matches. Version arguments and the accepted non-global/non-sticky version-policy expression are validated and detached before asynchronous executable lookup; the immutable discovery result retains the exact argument array that was executed, and caller mutation cannot change the pending version decision. TensorBuzz installs its pinned Node archive at `/usr/local/bin/node` and binds that exact executable through `SEMANTIFOLD_NODE`; a distinct host `/usr/bin/node` therefore cannot make the real-runtime lane ambiguous. This does not weaken canonical ambiguity detection when no override is configured. The runner inherits no ambient variables other than PATH unless the caller explicitly supplies them; locale and timezone are always normalized. Stage executable and version fields are copied and frozen before setup yields, embedded NUL is rejected before temporary-directory setup, and each completed stage returns a frozen copy of its executed argument array, so caller mutation cannot change execution or its audit record. On POSIX, version probes and acceptance stages spawn into one distinct owned process group: the configured deadline signals that exact group with `SIGTERM`, a fixed 250ms grace escalates the still-open group to `SIGKILL`, and a second fixed 250ms grace closes only the owned capture-pipe readers if an escaped descendant still retains their writers. The API then settles on the direct child's `close` event without claiming that an escaped process was terminated; captured output and timer-owned timeout classification remain unchanged. Platforms without this process-group contract fail with normalized unsupported launch/version diagnostics before spawning rather than claiming descendant cleanup. Timeout diagnostics derive from timer ownership rather than a subprocess signal, so spontaneous signals remain `TOOL_VERSION_FAILURE` or `ACCEPTANCE_SIGNAL` with their exact evidence. If a stage and isolated-directory cleanup both fail, the stage diagnostic remains primary with its exact stage, exit, signal, and bounded output fields; a bounded `ACCEPTANCE_CLEANUP_FAILURE` diagnostic is retained in its cause chain. Cleanup failure after successful stages remains `ACCEPTANCE_CLEANUP_FAILURE`.
4
20
 
5
21
  Backend tests start from one parsed semantic module, generate every target, parse each generated program back to equivalent modeled meaning, and work in isolated `mkdtemp` directories. They invoke real commands and assert exact `5\n` output:
6
22
 
@@ -9,10 +25,13 @@ Backend tests start from one parsed semantic module, generate every target, pars
9
25
  - `node program.js`
10
26
  - local lockfile-installed `tsc program.ts`, then `node program.js`
11
27
  - `javac Main.java`, then `java -cp … Main`
28
+ - `python3 -m py_compile program.py`, then `python3 program.py` with `PYTHONUTF8=1` and `PYTHONIOENCODING=utf-8`
12
29
 
13
30
  Cleanup runs in `finally`. A missing tool raises the process error and fails the spec; there are no skip branches or mocked compilers. Diagnostic specs cover unsupported syntax, missing JSDoc types, and backend capability rejection.
14
31
 
15
- Mapping specs assert all 25 frontend/backend combinations, exact/anchor/synthetic coverage without gaps, semantic-token roles, deterministic source/node/symbol IDs, exact `sourcesContent`, independently decoded v3 maps, rich/v3 composition, malformed caller metadata repair, legacy modules, caller-assembled multi-source IR, and deterministic JSON round trips. Transformation regressions cover same-kind function reorder, insertion, deletion, cloning, ID regeneration, source ownership, shared parameter, expression, and statement occurrences, and their distinct JSON-pointer node identities. UTF-16 cases cover BMP/astral text plus LF, canonical CRLF, and lone CR; escaped Babel identifiers cover declarations, references, and calls. Imported maps are checked against TraceMap on unmapped prefixes and lines; content-backed EOF anchors, including empty sources, round-trip as exact points, while mapped imports without source content are rejected before offset construction. Composition coverage includes derived intermediate provenance that resolves only to unmapped synthetic content, untraced intermediate EOF points whose registry source must remain available, and ordering where a later range remains traceable. Identity-index coverage checks direct, derived, and synthetic-related node/symbol associations for ordered deduplication. External directive coverage includes mixed parent-relative depths and resolution to the advertised map path. A bounded 50-to-200-function scaling test guards linear validation and indexed lookups; deterministic operation-count coverage guards Babel token indexing without wall-clock thresholds. A real `tsc` map is composed through generated TypeScript to original Ruby source. A separate mapped-artifact acceptance executes PHP, Ruby, Node, TypeScript through real `tsc`/Node, and Java through real `javac`/`java`; unavailable commands fail. SHA-256 characterization checks preserve every existing base/local generated byte across all source languages and targets.
32
+ Mapping specs assert all 36 frontend/backend combinations, exact/anchor/synthetic coverage without gaps, semantic-token roles, deterministic source/node/symbol IDs, exact `sourcesContent`, independently decoded v3 maps, rich/v3 composition, malformed caller metadata repair, legacy modules, caller-assembled multi-source IR, and deterministic JSON round trips. Transformation regressions cover same-kind function reorder, insertion, deletion, cloning, ID regeneration, source ownership, shared parameter, expression, and statement occurrences, and their distinct JSON-pointer node identities. UTF-16 cases cover BMP/astral text plus LF, canonical CRLF, and lone CR; escaped Babel identifiers cover declarations, references, and calls. Imported maps are checked against TraceMap on unmapped prefixes and lines; content-backed EOF anchors, including empty sources, round-trip as exact points, while mapped imports without source content are rejected before offset construction. Composition coverage includes derived intermediate provenance that resolves only to unmapped synthetic content, untraced intermediate EOF points whose registry source must remain available, and ordering where a later range remains traceable. Identity-index coverage checks direct, derived, and synthetic-related node/symbol associations for ordered deduplication. External directive coverage includes mixed parent-relative depths and resolution to the advertised map path. A bounded 50-to-200-function scaling test guards linear validation and indexed lookups; deterministic operation-count coverage guards Babel token indexing without wall-clock thresholds. A real `tsc` map is composed through generated TypeScript to original Ruby source. Mapped-artifact acceptance executes PHP, Ruby, Node, TypeScript through real `tsc`/Node, Java through real `javac`/`java`, and Python through its configured interpreter after `py_compile`; unavailable commands fail. SHA-256 characterization checks preserve every existing base/local generated byte across all original source languages and targets.
33
+
34
+ Task 016 adds mapped Python execution through the configured interpreter after `py_compile`; the original-five SHA-256 characterizations remain unchanged.
16
35
 
17
36
  Local-binding fixtures cover initialized mutable and immutable declarations, plain assignment, function/branch/entry sequencing, exact Ruby/PHP profile metadata, and source locations in all five frontends. Focused negatives cover every declaration/assignment exclusion, declaration-before-use, unresolved and duplicate bindings, callable-name and lexical no-shadowing, immutable parameters/locals, exact type preservation, and RHS-before-target validation. Ruby coverage rejects profile-like tag misspellings, places multibyte text before later function and local carrier comments, asserts their UTF-16 locations, and reparses generated Ruby with the same offset shift. Backend shape coverage applies missing, null, primitive, array, sparse, and unsupported-object values to recursive blocks, statement elements, declaration/assignment fields, and recursively nested call expressions, proving caller-supplied malformed IR receives the nearest located capability diagnostic before emitter dispatch. Backend runtime coverage regenerates and reparses the same binding/mutability semantics, then executes the result through all five real toolchains with exact `yes\n` output. Target-focused coverage rejects JavaScript/TypeScript `console` and Java `args`/`System` entry captures, JavaScript/TypeScript `console` and Java `System` function parameter/local print-receiver captures, and JavaScript/TypeScript `console` and Ruby `puts` function captures before emission. Real `tsc`, PHP, and Ruby coverage demonstrates their strict or reserved binding failures and requires located target-backend rejection for each proven declaration/assignment role. The PHP probe matrix separately covers automatic-global parameters, the narrower `GLOBALS`/`this` assignment restriction, legal function names, assignable non-`GLOBALS` superglobals, ordinary predefined variables, and exact-case near-collisions; other runtime coverage executes safe JavaScript behavior, Ruby `_10`, and TypeScript near-collisions.
18
37
 
package/index.js CHANGED
@@ -2,8 +2,18 @@
2
2
 
3
3
  import {parseSource} from "./src/frontends/index.js"
4
4
  import {generateArtifactSource, generateSource} from "./src/backends/index.js"
5
+ import {createGeneratedArtifactSet as constructArtifactSet} from "./src/artifacts.js"
6
+ import {SemantifoldDiagnostic} from "./src/diagnostic.js"
7
+ import {languageRegistry} from "./src/language-registry.js"
5
8
 
6
- export {SemantifoldDiagnostic} from "./src/diagnostic.js"
9
+ const artifactBackendRoles = new Set(["text", "binary", "application"])
10
+
11
+ export {SemantifoldDiagnostic}
12
+ export {languageCapabilities, supportedLanguages} from "./src/language-registry.js"
13
+ export {createGeneratedArtifactSet} from "./src/artifacts.js"
14
+ export {createByteMapping, parseByteMapping, stringifyByteMapping} from "./src/binary-mapping.js"
15
+ export {canonicalToolchains, discoverCanonicalToolchain, discoverToolchain} from "./src/toolchains.js"
16
+ export {runAcceptanceStages} from "./src/acceptance.js"
7
17
  export {getNodeProvenance, getSymbolProvenance} from "./src/semantic/provenance.js"
8
18
  export {primaryLocation} from "./src/semantic/provenance.js"
9
19
  export {
@@ -21,9 +31,6 @@ export {
21
31
  toSourceMapV3
22
32
  } from "./src/mapping.js"
23
33
 
24
- /** @type {readonly import("./src/semantic/types.js").SemanticLanguage[]} */
25
- export const supportedLanguages = Object.freeze(["php", "ruby", "javascript", "typescript", "java"])
26
-
27
34
  /**
28
35
  * Parses source into Semantifold's shared semantic representation.
29
36
  * @param {object} input - Parse request.
@@ -61,3 +68,101 @@ export function generate(input) {
61
68
  export function generateArtifact(input) {
62
69
  return generateArtifactSource(input)
63
70
  }
71
+
72
+ /**
73
+ * Generates a deterministic artifact set for one semantic module.
74
+ * @param {object} input - Artifact-set generation request.
75
+ * @param {string} input.language - Registered target language or platform ID.
76
+ * @param {import("./src/semantic/types.js").SemanticModule} input.module - Semantic module.
77
+ * @param {"text" | "binary" | "application"} [input.role] - Requested backend artifact role.
78
+ * @param {string} [input.filename] - Safe output path for a text target.
79
+ * @param {"none" | "external" | "inline"} [input.mapDirective] - JavaScript-family map directive.
80
+ * @param {string} [input.sourceMapFilename] - External map filename.
81
+ * @param {{filename: string, content: string, language?: import("./src/semantic/types.js").SemanticLanguage}[]} [input.sources] - Original sources.
82
+ * @returns {import("./src/semantic/types.js").GeneratedArtifactSet} Complete generated set.
83
+ */
84
+ export function generateArtifactSet(input) {
85
+ if (!input || typeof input != "object" || Array.isArray(input) || Object.getPrototypeOf(input) != Object.prototype) {
86
+ throw new SemantifoldDiagnostic({
87
+ code: "INVALID_ARTIFACT_SET",
88
+ language: "artifact",
89
+ message: "Artifact-set generation requires a request object."
90
+ })
91
+ }
92
+ const {filename, language, mapDirective, module, role = "text", sourceMapFilename, sources} = input
93
+
94
+ if (typeof language != "string" || language.length == 0) {
95
+ throw new SemantifoldDiagnostic({
96
+ code: "INVALID_ARTIFACT_SET",
97
+ language: "artifact",
98
+ message: "Artifact-set generation requires a non-empty string language or target ID."
99
+ })
100
+ }
101
+ if (typeof role != "string" || !artifactBackendRoles.has(role)) {
102
+ throw new SemantifoldDiagnostic({
103
+ code: "INVALID_ARTIFACT_SET",
104
+ language,
105
+ message: "Artifact backend role must be 'text', 'binary', or 'application'."
106
+ })
107
+ }
108
+
109
+ if (role == "text") {
110
+ const record = languageRegistry.record(language)
111
+
112
+ if (record.artifactMultiplicity == "multiple") {
113
+ const backend = languageRegistry.resolve(language, "textBackend", module?.location)
114
+
115
+ return constructArtifactSet(backend({filename, language, module, sources}))
116
+ }
117
+ const artifact = generateArtifactSource({
118
+ filename,
119
+ language: /** @type {import("./src/semantic/types.js").SemanticLanguage} */ (language),
120
+ mapDirective,
121
+ module,
122
+ sourceMapFilename,
123
+ sources
124
+ })
125
+
126
+ /** @type {import("./src/semantic/types.js").GeneratedSetArtifact[]} */
127
+ const artifacts = [{
128
+ content: artifact.code,
129
+ contentKind: "text",
130
+ mediaType: /** @type {string} */ (record.mediaType),
131
+ ownership: "generated",
132
+ path: artifact.filename,
133
+ provenance: {
134
+ kind: "text",
135
+ mapping: artifact.mapping,
136
+ sourceMap: artifact.sourceMap,
137
+ sourceMapFilename: artifact.sourceMapFilename
138
+ },
139
+ role: "entry"
140
+ }]
141
+
142
+ if (mapDirective == "external") {
143
+ artifacts.push({
144
+ content: `${JSON.stringify(artifact.sourceMap)}\n`,
145
+ contentKind: "text",
146
+ mediaType: "application/json",
147
+ ownership: "generated",
148
+ path: artifact.sourceMapFilename,
149
+ provenance: {
150
+ kind: "synthetic",
151
+ reason: "Serialized Source Map v3 projection for the generated entry artifact.",
152
+ relatedOrigins: []
153
+ },
154
+ role: "mapping"
155
+ })
156
+ }
157
+
158
+ return constructArtifactSet({
159
+ artifacts,
160
+ target: language
161
+ })
162
+ }
163
+
164
+ const registryRole = role == "binary" ? "binaryBackend" : "applicationBackend"
165
+ const backend = languageRegistry.resolve(language, registryRole, module?.location)
166
+
167
+ return constructArtifactSet(backend({filename, language, module, sources}))
168
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantifold",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Language-neutral semantic code analysis, transformation, generation, and execution toolkit",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -51,7 +51,9 @@
51
51
  "@lezer/java": "^1.1.3",
52
52
  "@ruby/prism": "^1.9.0",
53
53
  "comment-parser": "^1.4.1",
54
- "php-parser": "^3.7.0"
54
+ "php-parser": "^3.7.0",
55
+ "tree-sitter": "0.25.1",
56
+ "tree-sitter-python": "0.25.0"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@eslint/js": "^10.0.1",