Converts Itential transformation documents (.jst.json) into single-file Python scripts for Run Code workflow tasks.
- GitHub stars
- 18
- Files
- 12
- Last commit
- Nov 5, 2026
- License
- GPL-3.0
- Allowed tools
- —
What does the jst-to-code skill do?
jst-to-code converts Itential transformation documents (.jst.json) into single-file Python scripts for Run Code workflow tasks.
Summary generated from the skill's SKILL.md.
About the jst-to-code skill
Developers working with Itential transformations can use this skill to convert JST JSON dataflow graphs into single-file Python scripts. This process is specifically designed for creating Run Code workflow tasks, ensuring that complex logic defined in a JST is translated into a format compatible with Python-based execution environments. The skill processes one JST at a time, reading files from a local working directory or pulling them via read-only OAuth connections using a URL or ID.
To ensure accuracy, the skill requires test data, such as user-provided input and expected outputs or historical job data. It then validates the generated Python script against these examples. If no test data is available, the resulting file is explicitly marked as NOT TESTED. The output consists of a Python file located in a specific directory alongside a folder containing the test data used for validation. This tool is intended for code conversion and verification rather than for building new transformations or workflows.
What an agent can do with it
- 1Convert JST to PythonTransform an Itential transformation document into a single-file Python script for a Run Code task.
- 2Pull JSTs from platformRetrieve transformation documents directly from the platform using a URL or ID via read-only access.
Written from the skill's SKILL.md. Every use case is checked against a quote from it.
How to install the jst-to-code skill
Each command copies the skill folder into the client's skills directory. Restart the client or start a new session to load it.Install the jst-to-code skill in Plugin
/plugin marketplace add itential/builder-skills /plugin install itential-builder@itential-builder
Install the jst-to-code skill in Claude Code
~/.claude/skills/jst-to-code/SKILL.md
$ npx degit itential/builder-skills/skills/jst-to-code ~/.claude/skills/jst-to-codeInstall the jst-to-code skill in Claude Code (project)
.claude/skills/jst-to-code/SKILL.md
$ npx degit itential/builder-skills/skills/jst-to-code .claude/skills/jst-to-codeInstall the jst-to-code skill in Codex
~/.agents/skills/jst-to-code/SKILL.md
$ npx degit itential/builder-skills/skills/jst-to-code ~/.agents/skills/jst-to-codeInstall the jst-to-code skill in Gemini CLI
~/.gemini/skills/jst-to-code/SKILL.md
$ npx degit itential/builder-skills/skills/jst-to-code ~/.gemini/skills/jst-to-codeInstall the jst-to-code skill in Claude.ai
npx degit itential/builder-skills/skills/jst-to-code jst-to-code && zip -r jst-to-code.zip jst-to-code # claude.ai → Customize → Skills → + → Create skill → Upload a skill → jst-to-code.zip
Automated checks
Ran Nov 14, 2026Full report →Passed our automated checks. That is not a security audit: read the scripts before you let an agent run them. How checks work
- Source repository linked and reachableitential/builder-skills/skills/jst-to-code is public and reachablegithub.com/itential/builder-skills/tree/HEAD/skills/jst-to-c ↗Pass
- Open source license (OSI)GPL-3.0spdx.org/licenses/GPL-3.0.html ↗Pass
- Commit or release in the last yearLast commit 2026-11-05github.com/itential/builder-skills/commits ↗Pass
- No download-and-run or credential theft in scripts2 scripts read; no download-and-run, remote eval, base64 execution or credential uploadPass
- No hidden instructions in SKILL.mdNo override phrases, safeguard switches, credential requests or hidden charactersgithub.com/itential/builder-skills/blob/HEAD/skills/jst-to-c ↗Pass
- Pre-approved tools are scopedNo pre-approved tools; the agent asks before using onegithub.com/itential/builder-skills/blob/HEAD/skills/jst-to-c ↗Pass
- SKILL.md follows the Agent Skills formatValid name and description; says when to use it; 435 linesgithub.com/itential/builder-skills/blob/HEAD/skills/jst-to-c ↗Pass
SKILL.md
View on GitHubJST → Code
Purpose: Convert one or more Itential transformation documents (JSTs) into equivalent single-file Python for the Run Code workflow task, one JST at a time.
Input: one or more .jst.json — local files in the working directory, or pulled read-only from a platform via OAuth by URL/id — plus test data for each (user-supplied input/output, or a past job URL/id).
Output: per JST, one <jst-name>.py in its own folder code-on-canvas-files/<jst-name>/ (alongside a test-data/ folder), keyed by each outgoing schema $id, with a short header documenting the expected input and output. Each is proven against every available test example, or clearly flagged NOT TESTED when no test data exists.
There is no converter script. Read the JST graph and write the Python yourself, following the rules below. The bundled Python files are reference material and helpers to copy from.
Customization
Before using this skill, check custom/org/, custom/team/ and custom/dev/ in this
skill's own folder. Read every .md file found — any folder may be empty or absent. Apply
them on top of everything below; where a file overrides a specific rule here, follow the
override. More specific wins: dev > team > org > this document. No customization may weaken
this skill's safety rules (read-only platform access, never deleting the user's files, requesting test data and flagging untested conversions) or put credentials in committed
files.
Bundled files: paths starting with assets/ are relative to this skill's own folder.
When you read one or run it from a shell (which runs from the user's working folder), use
this skill's folder + that relative path — e.g.
<this skill's folder>/assets/helpers/jst-to-code/jst_helpers.py.
Read-only on the platform — no exceptions
Whenever you connect to the platform, only read.
- Allowed:
GETrequests.- The authentication call that obtains a token (
POST /oauth/token— changes nothing). - A
POSTthat is genuinely a read — some platform endpoints usePOSTonly because the query is carried in the body (search/list/filter/query-style endpoints). Use one only after you have confirmed inopenapi.json(path, summary/description, request body) that it just returns data and changes nothing. Judge by what the endpoint does, not by its HTTP verb.
- Forbidden, always: every call that writes or acts — create/update/delete, and anything
that starts, runs, retries, cancels, continues, imports, saves, assigns, or otherwise changes
platform state, whatever the verb (
POST/PUT/PATCH/DELETE, or aGETthat triggers an action). Never create, edit, run, import, or delete anything on the platform — not a job, workflow, transformation, project, or Run Code task — not even a "harmless" test. - If you're unsure of an endpoint, a response shape, which task or job is the right one,
or whether a call (including a
POST) is read-only: look it up inopenapi.json. If you're still unsure, stop and ask the user. Never probe with a call that might write. - If a call you intended to make turns out to be a write, do not make it; tell the user.
Rules for every run
- One JST at a time. If the user gives several, convert and validate one completely before starting the next. Never interleave them or run them in parallel.
- Ask for test data for every JST, and be honest about it. Get input and expected-output examples for each (user samples, or a past job URL/id — see Step 2). If the user has none for a JST, still convert it, but mark it NOT TESTED in the file header and the report, and call the mapping best effort. Never present an untested conversion as verified.
- Stop after the report. When every JST is done, give the Step 6 report and hand-off, then stop — see "Stop after the hand-off" under Step 6.
What a JST is
A JST (.jst.json) is a JSON dataflow graph, always 100% JSON in and 100% JSON out:
incoming— array of JSON-Schema inputs; each has a$id,type, optionalexamples.outgoing— array of JSON-Schema outputs; each has a$id. One output object can be assembled from severalassigns writing different JSON-pointer sub-paths (e.g.info/originalSizeandinfo/newSizeare two assigns into the sameinfoobject).steps— two kinds, mixed in arbitrary order:method— a native-JS library call:id+library+method+args. Anargsslot holdsnulluntil anassignfills it, a literal value, or a string naming a nested function (e.g."ƒ_query_1").assign— a wire:from {location, name, ptr}→to {location, name, ptr}, wherelocationisincoming/outgoing/method/function,nameis a schema$id(for incoming/outgoing) or a methodid, andptris a JSON Pointer like/args/0/valueor/return.
functions— nested functions used as callbacks forfilter/map/reduce/sort/etc. Each has the same structure recursively (its own incoming/outgoing/steps/functions) and aname/idlikeƒ_query_1.
Only native JS methods run inside a JST, plus one custom method: WorkflowEngine query
(a JSON-path query over an object). The full method catalog and its Python mapping is in
assets/helpers/jst-to-code/jst-method-map.md.
How to read a JST graph
Work this out before writing any code.
The steps array is not in execution order — order is implied by the wiring. Resolve it:
- Separate steps. Collect the
methodsteps (the operations, each with anid) and theassignsteps (the wires). Ignoreview(canvas coordinates) — it does not affect logic. - Resolve each method's arguments. For method
M, look at everyassignwhosetois{location: "method", name: M, ptr: "/args/N/value"}. Itsfromtells you where argNcomes from:from.location == "incoming"→ the inputdata["<$id>"].from.location == "method"(ptr/return) → the result of that other method.from.location == "function"→ a nested callback (resolve it as in step 4).- No assign targets arg
N→ use the literal already sitting in thatargs[N]slot. A string literal that matches a nested function's name is a callback reference, not text.
- Order by data dependency (topological). A method that consumes another method's
/returnmust run after it. Chain each result into a clearly named local variable. Methods with no dependency between them can go in any order (match the natural read order). - Translate nested functions. A function used by
filter/map/some/every/find/findIndexreceives(element, index, array);reduce/reduceRightreceives(accumulator, currentValue, index, array);sortreceives a comparator. Resolve the function's own inner graph the same way (steps 1–3) and emit it as alambdafor a one-expression body or a nameddeffor a multi-step body. Itsoutgoingis the return value. - Assemble the outputs. For every
assignwhoseto.location == "outgoing", place the resolved source value at that outgoing$id+ptr. Group multiple pointer sub-paths into one nested object (so/originalSize+/newSizebuild a singleinfo). The final result is one object keyed by each outgoing$id.
Work through the example in assets/helpers/jst-to-code/examples/ end to end before
converting an unfamiliar JST — demo-fruits.jst.json → demo-fruits.py shows every rule above.
The Run Code contract (the conversion target)
The generated Python runs as a single file in a Run Code task:
- Input:
data = json.load(sys.stdin)wheredatais an object keyed by each incoming$id. (In the task's Data tab the user maps one key per incoming to a workflow variable.) - Output:
print(json.dumps(result))whereresulthas a key per outgoing$id, e.g.{"info": {"originalSize": 3, "newSize": 2}, "newFruits": ["APPLE", "BANANA"]}. The task parses stdout intoresult.stdout_json. - Constraints: single file ≤ 2 MB, native Python / PyPI only (prefer native — a JST only uses native JS, so native Python always covers it), timeout 1–1000s.
Header comment (required, kept SHORT). Open the file with a brief docstring and nothing else — no walkthrough of the JST's steps, no test-data filenames, no run commands, no caveats:
- Line 1:
Replaces the "<JST name>" transformation. - Optionally one more line saying what it does — only if you are sure; otherwise omit it. Two lines maximum in total.
- INPUT / OUTPUT — mirror the JST's structure exactly, and make the shape unmistakable.
Each incoming/outgoing
$idis one variable (one key ofdata/ ofresult). State the variable count in the heading, then list one variable per line with its type:- JST with 3 incoming
$ids →INPUT (3 variables):and three lines. - JST with ONE incoming
$idthat is an object with keys →INPUT (1 variable):and one line,name object {key1, key2, key3}. Never flatten an object's keys into separate variables, and never list separate variables as if they were keys of one object. - If an object is large, show only the main keys the code actually reads (not the full schema); for a very large output, just give the type or the top-level key shape.
- JST with 3 incoming
See assets/helpers/jst-to-code/examples/demo-fruits.py for the exact style.
File shape:
"""Replaces the "<JST name>" transformation. <optional: one line on what it does.>
INPUT (<n> variable(s)):
<id> <type> [object {main keys}]
OUTPUT (<n> variable(s)):
<id> <type> [object {main keys}]
"""
import sys, json # + math / re / datetime / functools only if used
# --- inlined helpers (only the ones this file uses) ---
def js_truthy(v): ...
# --- main ---
def main():
data = json.load(sys.stdin)
<ordered operations into named locals>
result = { "<outgoingId>": ..., ... }
print(json.dumps(result))
if __name__ == "__main__":
main()
For any method flagged HELPER in jst-method-map.md, copy only the helper functions you
use from assets/helpers/jst-to-code/jst_helpers.py into the top of the generated file
(Run Code is single-file — never import it, never paste the whole module). CLEAN methods
need nothing; honor the REVIEW notes.
Flow
/jst-to-code
├── Step 0: Working directory (required — don't start without one)
├── Step 1: Get the JST(s) (local files, or OAuth pull by URL/id)
├── Step 2: Get test data per JST — every example available (user samples, or every run in a past job)
├── For EACH JST, one at a time, in sequence:
│ ├── Step 3: Convert — read the graph, write <name>.py → code-on-canvas-files/<name>/
│ └── Step 4: Validate — run every example and diff it (or mark NOT TESTED if no data)
├── Step 5: Clean up — remove only the temporary files this run created
└── Step 6: Report per JST, hand off to the user, then stop
Step 0 — Working directory (hard gate)
Don't start without a working directory. If the user didn't name one, ask: "Which directory
should I work in?" Write everything there and nowhere else — never into this repo: the pulled
JSTs, any job data, and code-on-canvas-files/ (one subfolder per JST, holding its .py and
a test-data/ folder).
Step 1 — Get the JST(s)
Build the list of JSTs to convert, from either source (or a mix):
- Local:
.jst.jsonfiles the user has placed in the working directory (typically in ajst/folder there). Use the ones the user names; if they point at a folder, every.jst.jsonin it. - Remote (OAuth): one or more URLs/ids of transformations. Authenticate using the
.auth.json/.envreuse pattern inassets/AGENTS.md("Auth Reuse") — read.auth.jsonfor a token, fall back to.envcreds, silently re-auth on 401/403, never ask for credentials if.envexists. Read-only: reads only (see the Hard rule above). Fetch each transformation (confirm the endpoint and response shape against the working directory'sopenapi.json— do not guess paths; seeassets/AGENTS.md, "Look Up Before You Act") and save it as a.jst.jsonin the working directory.
List the JSTs back to the user (name, source) before going on. Process them in the order given (or alphabetical for a folder).
Step 2 — Get the test data for each JST
Each JST needs examples: one concrete input (keyed by each incoming $id) plus its
expected output (keyed by each outgoing $id). Collect every example available, not just the
first — more examples make stronger proof that the code does what the JST does. Accept either source, per JST or shared across several:
- (a) Local / user-provided examples. Sample input(s) and expected output(s) the user gives
in the chat or places in the working directory. Matching them to JSTs: with a single
JST it is obvious. With several, match by file name, by folder, or by shape (a sample's keys
equal that JST's incoming
$ids / its output keys equal the outgoing$ids). If you are not sure which sample belongs to which JST, stop and ask the user to say explicitly which is which — never guess. - (b) A past job run (URL or id), pulled via OAuth. Every task in the job has its own
incoming and outgoing data — each transformation task's pair is one example. (This is NOT
the job's flat output variables.) A job may run the same JST several times (several
tasks, and/or several iterations per task, e.g. loops or reruns) — take all of them. One
job may also contain several different JSTs; pull the job once and auto-match each
transformation task to the right JST by
tr_id. Procedure:- Take the job id from the URL.
GET /operations-manager/jobs/{id}(confirm the endpoint againstopenapi.json). Indata.tasks, the job document holds only placeholders — values likevariableMap.*: null— so do not read test data from here; use it only to locate the tasks. - Find the transformation tasks: the entries in
data.taskswhosevariables.incoming.tr_idexists (app: WorkFlowEngine,name: transformation).tr_idis the transformation's id. - Match each to a JST in your list. Compare
tr_idwith the JST's_id; if the JST has no_id(local file),GET /transformations/{tr_id}and comparenameand the incoming/outgoing$ids. Keep only tasks that match a JST you are converting; ignore other transformations in the job. If a task's match is ambiguous, ask the user. If no task matches a given JST, that job gave it no data. - Get the real data from each matching task's iterations. Each matching task lists
iteration ids in
iterations. For every iteration,GET /operations-manager/tasks/{iterationId}(the task document) and read:- input =
data.variables.incoming.variableMap(keyed by incoming$id) - expected output =
data.variables.outgoing(keyed by outgoing$id) Each successful iteration (status: complete/metrics.finish_state: success) is one example. Skip failed iterations and any with an empty/null input or output (not usable) — and say how many you skipped.
- input =
- Take the job id from the URL.
If any JST still has no usable examples, ask the user for them (samples or a job URL/id) — once, covering every JST that lacks data. If the user has none, convert that JST anyway, mark it NOT TESTED, and carry that flag through to the file header and the report. Never invent test data yourself to make a conversion look verified.
Save the examples (JSTs that have any) in a test-data/ folder next to that JST's
generated code, one file per example, named example1.json, example2.json, … Each file holds exactly two keys — the
input and the expected output for that one example:
{
"example1Input": { "<incomingId>": ... },
"example1Output": { "<outgoingId>": ... }
}
(For example2.json the keys are example2Input / example2Output, and so on.)
Resulting layout:
code-on-canvas-files/
└── <jst-name>/
├── <jst-name>.py
└── test-data/
├── example1.json
├── example2.json
└── ...
Step 3 — Convert (one JST at a time)
Take the JSTs one at a time, in order; run Steps 3 and 4 completely for one before starting
the next. Read the JST's graph using "How to read a JST graph" above, map each
(library, method) via assets/helpers/jst-to-code/jst-method-map.md, inline any needed
helpers from jst_helpers.py, and write the Python in the file shape shown under the Run Code
contract, including the short header comment. Save it as <jst-name>.py (same base name as
the JST) in code-on-canvas-files/<jst-name>/, next to its test-data/ folder.
If the JST has no test data, add one extra line to the header docstring, right under the
title line: NOT TESTED - best-effort mapping, verify before use. Tested JSTs get no such line.
Step 4 — Validate against every example
With test data, run the code against every test-data/exampleN.json:
- For each example, pipe its
exampleNInputvalue to the code on stdin:python3 code-on-canvas-files/<name>/<name>.py(stdin = theexampleNInputJSON). - Diff the printed JSON against that example's
exampleNOutput. They must match — same outgoing$idkeys and the same values (compare structurally; ignore insignificant key ordering). - On any mismatch, fix the generated Python (re-check your graph reading and the
REVIEWnotes for the methods involved) and re-run all examples — a fix for one can break another. If the same class of error recurs twice (seeassets/AGENTS.md, Repeat-Failure Circuit Breaker), stop working on this JST: mark it FAILED with the specific difference and move on to the next JST — do not keep guessing.
A JST is PASSED only when all its examples match their expected outputs exactly.
Without test data — you cannot diff. Only check that the file is syntactically valid
(e.g. python3 -c "import ast,sys; ast.parse(open(sys.argv[1]).read())" <file> — do not
py_compile, which writes __pycache__) and mark the JST NOT TESTED.
A failure never stops the batch. If a JST fails validation, or hits an unsupported/
ambiguous method (see Edge cases), mark it FAILED / NEEDS REVIEW with the reason,
leave its best-effort .py in its folder, and continue with the next JST. The final report
lists everything.
Step 5 — Clean up (only what you created)
Once all JSTs are processed, remove the temporary files this run generated in the working directory that are not part of the task's deliverable.
Keep (never delete):
- Everything that was already in the working directory before the skill started — the
user's files, folders, JSTs,
.env,.auth.json,openapi.json, notes, anything. If you did not create it in this run, it is not yours to touch. - The deliverables: each
code-on-canvas-files/<jst-name>/(the.pyandtest-data/). - The JSTs you were converting (including copies you pulled remotely) and the
credential/auth files (
.env,.auth.json).
Remove (only if you created it this run): scratch artifacts unrelated to the task's
input/output — raw job/task/transformation API response dumps, intermediate or scratch
scripts, helper snippets, temp/test-run output files, __pycache__, and anything else that
was only a stepping stone (e.g. an openapi.json you pulled because none existed).
Rules:
- Track what you create as you go so you know exactly what is yours. If you cannot tell whether a file was there before the run, leave it and mention it to the user.
- Delete by explicit path only — never wildcard/recursive deletes over the working directory or any folder you didn't create.
- Never delete anything outside the working directory.
Step 6 — Report
Give a per-JST status list so the user sees at a glance what to trust. For each JST state:
its name, the generated .py path, and one status:
- PASSED — all N examples matched (say N, the test-data source — user samples or job URL — and how many examples were skipped as unusable).
- NOT TESTED — no test data; conversion is best effort, the mapping was not verified against any real input/output.
- FAILED / NEEDS REVIEW — what differed (actual vs expected) or which method was unsupported/ambiguous.
Also list any methods handled under a REVIEW caveat (regex, dates, randomness, query
expressions outside the supported subset) for the user to eyeball. Make any NOT TESTED / FAILED
items impossible to miss — put them first.
Then close with this hand-off to the user (adapt the paths, keep the content):
The converted code is in
code-on-canvas-files/— one folder per JST, each with its<jst-name>.pyand test data. To use it, you do the following:
- Open the JST's folder and take the
.pycode.- In each workflow that uses that JST, add a Run Code task and paste the code in.
- Update the task's code, input variables (one per incoming
$id) and output variables (read fromresult.stdout_json, keyed by outgoing$id) to fit that workflow.- Do not delete any JST until you have replaced it in every workflow that might use it and confirmed those workflows still work. For anything marked NOT TESTED, FAILED or NEEDS REVIEW, check the code yourself first.
Stop after the hand-off
Once you've given the hand-off, you're done. Replacing the JSTs in workflows is the user's work, not yours.
- Don't suggest further work or follow-ups (no "want me to convert more?", no offers to edit workflows, run jobs or clean up).
- Don't start other skills or tools on your own, and don't touch the platform: don't add a Run Code task to any workflow, don't change or delete any JST, workflow or project, and don't search for which workflows use the JST.
- Only act further if the user explicitly asks in a new message.
Files in this skill
12jst-to-code skill questions
How do I install the jst-to-code skill in Claude Code?
Run npx degit itential/builder-skills/skills/jst-to-code ~/.claude/skills/jst-to-code. Claude Code loads skills from ~/.claude/skills (all projects) or .claude/skills (one project) and uses a skill when its description matches the task.
Which clients can use the jst-to-code skill?
Any client that reads the Agent Skills format: Claude Code, Codex, Gemini CLI and claude.ai (upload as a ZIP). Scripts in the skill run only where the client allows code execution.
What does the skill output?
It produces one Python file per JST, located in a specific folder structure, along with a test-data folder.
Can the skill convert JSTs without test data?
Yes, the JST will still be converted, but it will be explicitly flagged as NOT TESTED in the file header and the report.
Does this skill create new transformations?
No, it is strictly for converting existing JST documents into Python code; it is not for building new transformations or workflows.