Structured Editing and Sync
Not everyone who owns intent writes IntentLang by hand. A product manager thinks in fields:
the goal, the guarantees, the inputs, what must never happen. A structured editor (like
SkillsTech Studio) lets them edit those fields, but the .intent file must stay the source of
truth , and a human's comments must survive the round trip. That is the job of the sync and
patch API.
It has three pieces, all deterministic (no AI) and browser-safe via
@skillstech/intentlang/core, so a web editor uses them with no Node build:
parseToStructured(source), the canonical structured view a UI renders from.proposeIntent(structured, { base }), a reviewable proposal from an edited graph.applyEdits(source, edits), lossless, comment-preserving field edits on the source.
Read: source to structured
parseToStructured returns the canonical Intent Graph (what a projector
maps to its own nodes and edges) plus a flat, PM-friendly field summary:
import { parseToStructured } from "@skillstech/intentlang/core";
const structured = parseToStructured(source);
// -> { schema: "intent-sync-v1", mission, graph, fields: {
// goal, why, guarantees[], neverRules[], inputs[], outputs[], decisions[], ...
// } }
IL stays the source of truth: the structured view is a projection, not a fork.
Propose: structured to source, with a reviewable diff
When the user edits the structured graph, proposeIntent regenerates IntentLang source and,
against a base, a reviewable diff , it never applies a silent rewrite:
import { proposeIntent } from "@skillstech/intentlang/core";
const p = proposeIntent(editedStructured, { base: source });
// -> { ok, source, diff, ambiguities, lostNodes, validation, warnings, applied: false }
diff, added / removed / changed nodes and relationships, plus any approvals the change invalidates. Render it; let the human apply it.ambiguities, every node whose classification is not factual (proposed,assumed,inferred,unknown). These are proposals to confirm, not guesses to accept.validation, the canonical-vocabulary and no-dangling check on the proposed graph.applied: false, always. The proposal is for review; nothing is written.
Regenerating source from a graph does not preserve free-text comments. When the base has
comments, warnings says so and points you to applyEdits , the comment-preserving path.
Edit: patch the source in place, comments intact
applyEdits applies field-level edits directly to the .intent source, touching only the
target lines. Comments, formatting, stable ids, and every untouched block stay byte-identical,
and the result comes out already intent fmt-clean:
import { applyEdits } from "@skillstech/intentlang/core";
const result = applyEdits(source, [
{ op: "setField", field: "goal", value: "Create an approved invoice, exactly once." },
{ op: "addGuarantee", statement: "an order is invoiced at most once", verify: "idempotency test" },
{ op: "addField", section: "input", name: "idempotencyKey", type: "IdempotencyKey" },
{ op: "removeNever", match: "payment token" },
]);
// -> { schema: "intent-patch-v1", source, applied, skipped }
Supported operations:
| Op | Effect |
|---|---|
setField |
Replace the body of goal / why / problem (creating it if absent). |
addGuarantee |
Insert a guarantee block (with optional because and verify). |
removeGuarantee |
Remove the guarantee whose statement matches. |
addNever / removeNever |
Add or remove a never rule. |
addField |
Add a typed field to input / output (creating the block if absent). |
removeField |
Remove a field , and its indented modifiers, so no orphans are left. |
setFieldType |
Change a field's type in place. |
Nothing is applied blindly: an edit that matches nothing, has a bad section, or is missing
arguments lands in skipped with a reason, and the rest still apply.
The loop
Together the three close the Human ↔ Structured ↔ IntentLang loop with IL as the source of
truth: read with parseToStructured, propose graph-level changes with proposeIntent (diffed,
never silent), and apply field edits with applyEdits (lossless, comments kept). The sync
contract is intent-sync-v1; the patch contract is intent-patch-v1. Both are pre-1.0 and
version independently.