tia-verify
Skill12 starsChanged 29 days agoMITView on GitHub
Run the offline VERIFICATION LADDER on an open TIA project (rung 1 compile, only Error blocks; rung 2 consistency + export/COMPARE roundtrip + VCI workspace diff; rung 3 simulation, future) and generate the FAT/SAT I/O list (rung 6, read-only). Use when the user asks to "verify / compile / check the project builds", "diff the project against a VCI workspace", for the "FAT/SAT I/O list", an "IO list" or "which tags sit on which channels", after tia-scaffold or any authoring change, or before tia-handoff (a clean compile is the Gd9 precondition for deploy). Class M-off: compiles via the tia MCP; repairs the known bare-CPU protection errors (RECIPES) and re-compiles; the I/O list only reads. STOPS at deploy (G3 is gated). Not for a style/architecture audit (tia-review) or for downloading (tia-handoff).
In this repository10 other files it gives its agents.
- codex/AGENTS.mdAGENTS.md
- .claude-plugin/CLAUDE.mdCLAUDE.md
- cpu-selectSkill
- spec-planSkill
- spec-validateSkill
- tia-backupSkill
- tia-handoffSkill
- tia-initSkill
- tia-reviewSkill
- tia-scaffoldSkill
The file
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 2 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 28
If no tia_* tool is available, the tia MCP is not connected: say so and stop.
- Very specific: line 30
Never report success after an error, never retry a refused call with changed arguments, and never cross a human gate: relay a …
- Distinctive: line 63
When asked for the I/O list: the FAT/SAT I/O list of step 6, one per PLC, *complete* or starting with the words NOT complete and why.
- Very specific: line 74
Auto-repair the known case: If the errors are the bare-CPU protection signature (password / certificate / access level), apply the …
- Very specific: line 97
An existing workspace is a baseline: its diff is READ-ONLY: When the workspace has mapped objects, walk tia_vci_status {workspace, limit: …
- Very specific: line 104
NOT complete. Never call tia_vci_export or tia_vci_import just to make a comparison equal: tia_vci_export over a mapped object REPLACES its …
- Very specific: line 118
An import overwrites the project object and has no undo: Run tia_vci_import with dryRun:true first, read statusBefore and …
- Very specific: line 130
Read coverage on EVERY page, never only on the first: every page compares its own rows, so a later page can report less than an earlier one.
- Very specific: line 208
Report + hand off: If errors:0 AND rung 2's comparisons are equal (or explicitly pending human commit), state that Gd9 is satisfied → …
- Very specific: line 216
Compile-clean gate-keeps deploy (Gd9): Never let tia-handoff/download proceed with any Error.
- Distinctive: line 221
Safety: Never modify F-logic to "make it compile" (G5).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
tia-verify — the verification ladder, rungs 0–3, and the FAT/SAT I/O list, rung 6 (class M-off)
Prove a project "works in practice" offline, the only honest pre-deploy evidence (the style/architecture
audit is tia-review). The ladder: rung 0 static (spec-validate), rung 1 compile + consistency,
rung 2 project-as-code export + COMPARE (roundtrip proof), rung 3 simulation (PLCSIM, future). A
clean compile is the precondition (Gd9) for export and download — so this gate-keeps tia-handoff.
Rung 6 is the FAT/SAT delivery: this skill covers its I/O list (step 6), generated from tia_io_map, a
read independent of the compile; the human signs the FAT/SAT record, the skill never does.
Errors and reporting
- First call
tia_ping: requirereachable:trueandactivated:true.activated:nullwith a busydetail: use the job/no-job rule below. Otherwise stop, quotedetail/hintverbatim and say nothing changed (tia_guide {"topic":"gotchas"}). - If no
tia_*tool is available, thetiaMCP is not connected: say so and stop. Never continue from memory and never work around it with hand-made HTTP calls (they bypass the MCP's gate checks). - On a tool error (
isError:true), quote itserrorandkindverbatim, relay itshint, and state what was done and what was not. Apply only the remedy the hint or this skill names, at most once; otherwise stop and ask. - Never report success after an error, never retry a refused call with changed arguments, and never cross a human gate: relay a
requires_human_actionanswer verbatim and wait for the human. - Poll
tia_job_status {jobId}forstatus:"running"+jobId, orkind:"busy"only for a running job whose ID is known (e.g.details.jobId). Never repeat a running call. Without an ID, wait/recheck withtia_status;connect_incomplete→ host-inspection handoff (tia_guide {"topic":"gui-gates"}), never retry connect. After timeout, check state first. - This skill names these remedies for
tia_io_map(step 6): a409cursor_staleanswer, orcoveragethat differs between pages (restart the page walk from the first page, once for each); and a page that arrives with an omission marker (repeat that page with a smallerlimit, at most three times). Every other tool error stops the I/O list as above: report it as NOT complete, or as not done when no page was read. - Step 6 distinguishes
tia_io_mapjobs from rejected reads; the job/no-job rule applies everywhere.
Preconditions
- A project open (
tia_status→hasProject:true). Connecting is G1 — defer to the human.
Inputs / tools
tia_compile(per CLP) — returns{ state, errors, warnings, messages[] (tree), blockChanges, verification, nextHints };blockChangeslists the blocks whose fingerprint changed during the compile.tia_plc_blocks(isConsistentper block),tia_export_xml(bulk SimaticML to disk),tia_export_block(ONE block inline withsha256/normalizedSha256and thecontentHashthat guards a block write) andtia_block_compare(rung 2's real proof; a raw hash is NOT proof of equality).tia_export_type— export ONE PLC data type inline with itscontentHash(the hash reader of a type).- Protection repair:
tia_obj_service+tia_obj_invoke Protect+tia_obj_set_attributes(../tia-scaffold/RECIPES.md). - VCI (rung 2, only when a workspace exists or the human asks):
tia_vci_workspaces,tia_vci_status(class R, paged),tia_vci_export,tia_vci_import(M-off,dryRunfirst) andtia_vci_workspace_create. I/O list (rung 6, class R):tia_plcs,tia_io_map {plc, limit:100}. - The long tools (
tia_compile, the exports,tia_block_compare,tia_vci_status,tia_vci_export,tia_vci_import,tia_io_map) may answerstatus:"running"+jobId: polltia_job_status {jobId}until the final result — never repeat the original call.
Outputs
- Per CLP:
compiled=clean|error,errors,warnings, and — if errors — the leaf messages (the real text; recurse the tree). - A ladder verdict: which rungs passed (0 static, 1 compile/consistency, 2 export+compare
roundtrip, and the VCI rung when it ran: complete, or NOT complete and why, with the rows that
differ by name) and whether deploy (
tia-handoff) is unblocked. - When asked for the I/O list: the FAT/SAT I/O list of step 6, one per PLC, complete or starting with the words NOT complete and why. It is a document, not a passed rung: it neither unblocks nor blocks deploy.
Procedure (in order)
- Confirm open (
tia_status). - Rung 1 — compile each CLP (
tia_compile). Interpret: onlyState=Errorblocks; warnings never block (Gd8). The tree's intermediate nodes have empty descriptions — recurse to the leaves for the real error text (never report "3 errors" without it;located, when present, already holds the leaves with their path). ReadblockChanges: a block regenerated by this compile needs rung 2 below. - Auto-repair the known case. If the errors are the bare-CPU protection signature
(password / certificate / access level), apply the
RECIPES.mdprotection recipe (the variant for the CPU family/firmware; theProtectpassword comes from the human, never invented) and re-compile once. If the same errors remain, stop and report the leaf messages. When you ask for the password, tell the human what the recipe changes: Full access (S7-1500) or access control disabled (S7-1200, FW V4.5+), which the CPU keeps after the next download; name the change in the report. - Consistency. Confirm
isConsistent(tia_plc_blocks); a freshly imported block, or an instance DB whose FB interface changed, is inconsistent until compiled. A (software-scope) re-compile (tia_compile) regenerates the instance DB ("change":"modified"inblockChanges): re-run it and re-check. Only if it still reports inconsistent, delete the instance DB (tia_block_delete) and recreate it (tia_instancedb_create) once — say that its start values are reset — then recompile; if still inconsistent, stop and report. Never reach forUpdateProgram()(it does not regenerate). Rung 2 — project-as-code, export + COMPARE (roundtrip proof).
tia_export_xmlto a directory for a bulk git diff. Then, for any blockblockChangesflagged (or any block you need proof for), calltia_block_compare {plc, block, xml:"<the committed file's content>"}—verdict:"equal"is the actual proof the project matches what's versioned;"different"names the first differing line.tia_export_blockgives the project's current XML inline.VCI diff and round trip — when a VCI workspace exists or the human asks for one: the VCI tools take leaf objects only (PLC blocks, PLC data types, tag tables), never a device, a PLC, a group or the library: a container is refused with
vci_not_leaf. Start withtia_vci_workspaces:mappedObjectsabove 0 means a baseline exists.An existing workspace is a baseline: its diff is READ-ONLY. When the workspace has mapped objects, walk
tia_vci_status {workspace, limit: 100}untilnextCursorisnull(samelimit,cursorunchanged) and classify every row.equalis the only verified match.projectChanged,workspaceChanged,conflict,workspaceFileMissingandunequalare differences: report each by name (project changed, file changed, both, file missing, side unknown); they are the result, not something to repair.unknown,unsupportedandstatusFailedprove nothing, neither equality nor a diff: report them by name, and the diff is NOT complete. Never calltia_vci_exportortia_vci_importjust to make a comparison equal:tia_vci_exportover a mapped object REPLACES its workspace file with the project content, andtia_vci_importreplaces the project object with the file, so either call destroys the very difference you were asked to report. Refreshing the baseline is an explicit decision of the human: name the objects, ask, and only then runtia_vci_exportwithdryRun:truefirst (every object the plan lists withaction: "synchronize"is a file that will be replaced) and then for real.A new workspace or new objects: export, then prove it. Never create a workspace and never choose a root path without the human: call
tia_vci_workspace_createonly with therootPaththe human gave, afterdryRun:true. Export the objects under review withtia_vci_exportanddryRun:truefirst, read the plan (action: "export"creates a new file; an object listed assynchronizeis already mapped, so stop and treat it as the baseline case above), then apply it. Prove the export by walkingtia_vci_statusas above: EVERY row of EVERY page must beequal.An import overwrites the project object and has no undo. Run
tia_vci_importwithdryRun:truefirst, readstatusBeforeandoverwritesProjectChangesof each object and say what the import would discard, and ask the human before the real call whenoverwritesProjectChangesistrue. PassexpectedContentHashper kind, from a fresh read of the project object: for a block thecontentHashoftia_export_block {plc, block}, for a type thecontentHashoftia_export_type {plc, type}, and for a tag table none (no hash exists for it and the serve refuses one), so rely on the dry run and the postcheck. Call an import verified only when every imported object haspostcheck.verified:true.409import_unverifiedmeans the project object WAS already written and409vci_batch_incompletemeansdonelists written and skipped objects: report what was written, never "nothing changed". A504worker_restarted, atimeoutor a lost session during a VCI write leaves the outcome UNKNOWN or partial: readtia_vci_statusbefore repeating anything.Read
coverageon EVERY page, never only on the first: every page compares its own rows, so a later page can report less than an earlier one. A page withcoverage.complete: false, anyunknown,unsupportedorstatusFailedrow, an omission marker that remains after three repeats with a smallerlimit, a walk that did not finish, or atotalthat differs between pages makes the VCI rung NOT complete: say which, and do not claim the workspace is in sync. Atotalthat differs between pages means the workspace changed during the walk, so restart it once from the first page, withoutcursor, likecursor_stale; if it differs again, treat the rung as not complete.status:"running"+jobIdidentifies this request: polltia_job_status {jobId}until the final result and never repeat the call.kind:"busy"means this request did not run: only for a running job whosejobIdis known (e.g.details.jobId), wait withtia_job_status {jobId}, discard its result, then retry this call once unchanged; a secondbusymakes the rung not complete. Without that ID, stop as not complete and use the no-job handoff above. A page whereobjectsis replaced by an object withomitted:true(an omission marker) was too large and has nonextCursor: repeat that same call with a smallerlimit, at most three times; never follow a cursor from an omitted page, it would skip what the marker replaced.Rung 3 (PLCSIM) is future — note it, don't fake it.
- Rung 6 — FAT/SAT I/O list (read-only: the I/O map is class R, so this step changes nothing and the skill
stays M-off). Do it when the user asks for the I/O list, for the FAT/SAT delivery or for which tags sit on which
channels. It is independent of the compile: the map is a read of the hardware and the tags, so it needs no
rung 1 and no
errors:0, and when the user asks only for the list you do step 1 and this step and skip the compile. The skill produces the list; the human signs the FAT/SAT record, the skill never does.- One list per PLC.
tia_io_maptakes ONEplcper call: for several CPUs loop overtia_plcsand make one list per PLC, each from its own page walk. - Page the map.
tia_io_map {plc, limit:100}for the first page, then the same call withcursorset to thenextCursorof the page just read, passed back unchanged, untilnextCursorisnull. Keepplcandlimitidentical on every page. Every page re-walks the whole PLC, so every page can take minutes on a big project: paging bounds the size of a response, not the time of the walk. - Read
coverageon EVERY page, never only on the first: every page re-walks the PLC, so a later page can report less than an earlier one. Keep thecoverageof each page (complete,unreadable,channels,channelsUnmapped,tagsWithIoAddress) and compare them: coverage that differs between pages means the project changed during the walk, so restart it once from the first page, withoutcursor, likecursor_stale; if it still differs, the list is NOT complete. - Answers that are not failures and differ. (a)
status:"running"+jobIdidentifies this request: polltia_job_status {jobId}until the final result and never repeat the call; after atimeout, check the state (tia_status) before repeating. (b)kind:"busy"means this request did not run: only for a running job whosejobIdis known (e.g.details.jobId), wait withtia_job_status {jobId}, discard its result, then retry this call once unchanged; a secondbusystops the walk as NOT complete (or not done if no page was read). Without that ID, stop likewise and use the no-job handoff above. (c)409cursor_stalemeans the project changed during the walk: restart from the first page, withoutcursor, once; if it is stale again, stop the walk and treat the list as NOT complete. (d) A page with an omission marker (omitted:true, usually in place ofentries) was too large and carries nonextCursor: repeat that same call, with the samecursor(or none for the first page) and a smallerlimit, for example half of the last one, at most three times, and follow anextCursoronly from a page that arrives without a marker; never follow a cursor from an omitted page, it would skip what the marker replaced. If a marker remains after three repeats, stop the walk and treat the list as NOT complete. - Rows of the table. One row per item of
tags[]of everychannelentry and of everyrangeentry with statusmatched; one row with an empty Tag cell for achannelentry with statusunmatchedand for a channellessrangeentry (channels:0) with statusunmatched; one row perkind:"tag"entry withstatus:"unmatched", whose Device / module / channel cell stays empty. Arangeentry with statuschannelsis no row at all: its channels are entries of their own. Arangeentry with statusunresolvedis no row either: it goes in 'Could not be mapped', with its tags. A tag that sits on severalchannelentries (a match that spans channels) is ONE row, identified by itstableandname, that lists all those channels in its Device / module / channel cell. The columns are, exactly: Tag | Address | Data type | Device / module / channel | Comment | Status. The Status ismatched,unmatchedorconflict, copied from the entry and never recomputed; aconflictrow names theflagsof its entry (multipleTags,spansChannels,partial). - Could not be mapped. Entries with
status:"unresolved"(achannel, or arangewith itstags) andkind:"tag"entries withstatus:"unparsed"are NOT rows of that table: they go in a separate list, 'Could not be mapped', one item per entry: for achannel, itsdevice,path,channelnumber and itsunresolvedtext; for arange, itsdevice,path,ioType,fromtotoand the tags listed in itstags, with the words 'its channels are unresolved, see their reasons'; for atag, itstable,nameand itsreasontext. - Under the table, always: the coverage numbers
channels,channelsUnmappedandtagsWithIoAddress(those of the last page read; on a complete walk they are the same on every page),coverage.limitsverbatim, and the sentence that the channel addresses are DERIVED (addressBasis: computed from the address range of the item and the channel number, never read from TIA) and must be spot-checked against the TIA hardware editor before the list is used for FAT/SAT. - Completeness. The list is complete only when the page walk reached
nextCursor: nullwith every page intact andcoverage.complete: trueon every page. In every other case the report starts with the words NOT complete and says why: an omission marker you could not clear, a walk you did not finish (busyagain, atimeout, a secondcursor_stale, coverage that still differs),coverage.complete: falseon any page, anycoverage.unreadableentry, any unresolved channel or range, any unparsed tag. Then list thecoverage.unreadableentries of every page (kind,device,name,reason; say so whenunreadableTruncatedis true) and claim nothing about channels or tags that were not read.coverage.limitsis not a reason for NOT complete by itself, but it is always printed. - Never guess. An unmatched tag is never declared 'unused' or 'orphan': it may sit on a PROFIBUS DP device or
on a device outside the PLC's PROFINET IO systems, which
coverage.limitssays are not read. Never invent, round or 'fix' an address, and never edit tags or hardware to make rows match: this step is read-only, and any change goes totia-scaffold.
- One list per PLC.
- Report + hand off. If
errors:0AND rung 2's comparisons areequal(or explicitly pending human commit), state that Gd9 is satisfied →tia-handoffmay prepare the (gated) download. A VCI rung that is NOT complete is reported as such and never counted as proof. If errors remain, list the leaf messages + fixes; do NOT proceed to deploy. If the I/O list was asked for, give it with its completeness: it does not change the Gd9 verdict.
Hard guarantees
- Compile-clean gate-keeps deploy (Gd9). Never let
tia-handoff/download proceed with anyError; warnings are allowed (report them). Real errors live in the leafmessages: summarizing only the count is a failure of this skill. - M-off only. Compile/export/protection-repair are offline; the I/O list is a read. Download/online (G3/G4)
are gated and live in
tia-handoff. - Safety. Never modify F-logic to "make it compile" (G5); report and defer to the human.
- VCI is leaf-only, read-only on a baseline and human-rooted. Pass nothing but PLC blocks, PLC data
types and tag tables to the VCI tools, never export or import over an existing workspace just to make
a diff come out equal (refreshing the baseline is the human's decision), create no workspace and choose
no root path without the human, and never report an import as verified without
postcheck.verified:true. - The I/O list is derived, read-only, never guessed and states its completeness. The addresses are DERIVED:
say so and ask for the spot-check in the TIA hardware editor; invent, round or fix no address and edit no tag
or hardware to make rows match; the human signs the FAT/SAT record. A walk that did not finish, a page that
kept an omission marker,
coverage.complete: falseon any page or coverage that differs between pages makes the list NOT complete: it says why and claims nothing about what was not read. An unmatched tag is never called unused or an orphan.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

