with one click
loci-post-edit
// Compare pre-edit and post-edit compiled artifacts to report execution timing % diff, energy consumption, and control-flow analysis.
// Compare pre-edit and post-edit compiled artifacts to report execution timing % diff, energy consumption, and control-flow analysis.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | loci-post-edit |
| description | Compare pre-edit and post-edit compiled artifacts to report execution timing % diff, energy consumption, and control-flow analysis. |
| when_to_use | MANDATORY after any Edit/Write/MultiEdit to C/C++/Rust source (.c,.cc,.cpp,.cxx,.h,.hpp,.hxx,.rs). Invoke IMMEDIATELY — do not skip, batch, or wait. Also: "analyze the change", "measure the edit", "timing diff". |
This skill merges execution-trace (timing/energy) and control-flow (CFG) analysis into a single post-edit report. It compares pre-edit and post-edit compiled artifacts to show exactly how the change affects hardware execution.
All assembly, CFG, symbol, and ELF inspection in this skill goes through
<asm-analyze-cmd>. Do not use objdump, readelf, addr2line, or
nm as substitutes — asm-analyze produces the per-block CSV, timing CSV,
and annotated CFG the LOCI MCP expects; binutils output is not equivalent.
If asm-analyze returns an error, surface it and stop; do not fall back to
objdump.
Always pass --arch <loci_target> on every asm-analyze call, reading the
value verbatim from the SessionStart LOCI target: line.
Check that loci MCP is connected and authenticated, you see the tools before running the preflight steps that require it. If the MCP is unavailable, tell the user:
LOCI MCP server is not connected. Please run
/mcpin Claude Code to manage MCP servers, then approve the loci server. If it does not appear, restart Claude Code — the plugin registers it automatically on startup.
For plugin to work mcp should be authenticated and connected.
Read the persisted detection results from the <project-context> path (the
per-session keyed file, listed as project context: in this session's
context). It is written by session-init.sh at session start and is the single
source of truth for compiler, architecture, and build system.
Do NOT re-run detection or fall back to ELF/build-system sniffing.
{
"compiler": "...",
"build_system": "...",
"architecture": "...",
"loci_target": "...",
...
}
If the file does not exist, stop and tell the user:
LOCI session context not found. Please restart Claude Code so the plugin setup runs and detects the project environment.
Also check the system-reminder block emitted at session start for:
Target: <target>, Compiler: <compiler>, Build: <build>
LOCI target: <loci_target>
Map the LOCI target to loci MCP supported architectures and binary targets:
| LOCI target | Time from CPU |
|---|---|
| aarch64 | A53 |
| armv7e-m | CortexM4 |
| armv6-m | CortexM0P |
| tc399 | TC399 |
If the architecture is not in this table, emit and stop:
Supported: aarch64 , armv7e-m , armv6-m , tc399
Compile the edited source with the exact compiler + flags preflight used. The
pre-edit hook captured preflight's metadata at
.loci-build/<loci_target>/<basename>.o.meta.json.prev. Pass it via
--meta-prev so the post-edit build inherits those flags rather than
re-detecting them:
<build-metadata-cmd> compile \
--source <path/to/src.cpp> \
--loci-target <loci_target> \
--context "<project-context>" \
--meta-prev .loci-build/<loci_target>/<basename>.o.meta.json.prev \
--phase post-edit
The command writes only the .meta.json sidecar to disk and exits
silently. Do not print the build-metadata block to the user — the
sidecar is the source of truth, and Step 1b's build-metadata diff
already surfaces a LOCI · build mismatch block on its own when parity
fails, which is the only case the user needs to see.
If .o.meta.json.prev does not exist, preflight did not run before this
edit. Omit --meta-prev; build-metadata will re-detect flags and record
them. Report absolute timing only in Step 5 — no % diff is available without
a preflight baseline.
Validate the .o — a standalone -c compile can exit 0 yet produce an
empty object file when the source is wrapped in #if / #ifdef guards
whose defines (-D) were not on the command line. After compiling, run:
<asm-analyze-cmd> extract-symbols --elf-path .loci-build/<loci_target>/<basename>.o --arch <loci_target>
If the result shows 0 symbols or an error mentions "no code" / "preprocessor",
the target function was compiled out. Ask the user for the -D flags the
project build system uses, re-run <build-metadata-cmd> compile, and
re-validate. Do not fall back to a project-built .elf with unknown flags.
<build-metadata-cmd> diff \
--prev .loci-build/<loci_target>/<basename>.o.meta.json.prev \
--curr .loci-build/<loci_target>/<basename>.o.meta.json
build-metadata diff is informational only — never a stop condition.
A non-zero exit means there is a delta to surface to the user; it does
NOT mean skip analysis or skip the report. Always proceed to Step 2 and
run the full timing/CFG analysis regardless of this exit code.
Exit code:
0 — compiler, version, flags, and target match → the timing diff is apples-to-apples; proceed normally.
non-zero — the command prints a LOCI · build mismatch block.
Emit it verbatim in the post-edit report, tag the final verdict as
LOW CONFIDENCE — build environment changed between preflight and post-edit, and continue with full timing analysis. The % diffs may
reflect the toolchain delta rather than the code change — note that,
but still report the numbers. Do not stop, skip steps, or omit the
per-function table on a build mismatch.
A flag_source kind regression (e.g. preflight used gmake-dry-run
but post-edit fell through to defaults) shows up as a dedicated line
in the mismatch block: flag_source kind 'X' → 'Y' — discovery regressed between preflight and post-edit; baseline unreliable.
Treat that as a stronger signal than a flag-list-only delta, but the
same rule applies: surface it, do not stop.
Skip this step entirely only if preflight did not run (no
.o.meta.json.prev).
.o.prev exists (preflight ran before the edit)The pair of artifacts to compare lives in .loci-build/<loci_target>/:
<basename>.o.prev (captured by the pre-edit hook)<basename>.o (just compiled in Step 1)<asm-analyze-cmd> diff-elfs \
--elf-path .loci-build/<loci_target>/<basename>.o.prev \
--comparing-elf-path .loci-build/<loci_target>/<basename>.o \
--arch <loci_target>
This returns lists of modified and added functions. Only these functions
need analysis — skip unchanged code entirely.
.o.prev (preflight did not run)Do NOT invoke diff-elfs — it requires both artifacts and will error
on a missing --elf-path. Skip directly to Step 3 and extract assembly from
the post-edit .o only; treat every function in the output as "added" for
reporting purposes. Note in the final report:
(no preflight baseline — first-edit measurement; % diff not available).
For modified functions, extract assembly from both artifacts:
<asm-analyze-cmd> extract-assembly --elf-path .loci-build/<loci_target>/<basename>.o.prev --functions <func1>,<func2> --arch <loci_target>
<asm-analyze-cmd> extract-assembly --elf-path .loci-build/<loci_target>/<basename>.o --functions <func1>,<func2> --arch <loci_target>
For added functions, extract from post-edit only:
<asm-analyze-cmd> extract-assembly --elf-path .loci-build/<loci_target>/<basename>.o --functions <new_func> --arch <loci_target>
The JSON output contains timing_csv and timing_architecture fields needed
for the MCP call.
The JSON also contains the control_flow_graph field that contains annotated CFG's in text-format optimized for LLM analysis.
Extract fields with jq, not python -c. jq ships with the plugin, handles
UTF-8 cleanly on every platform, and never trips on the Windows console codepage.
Pipe extract-assembly output through jq directly — do not save the JSON to a
file and re-read it from Python:
# Whole JSON to a file you control (use a path inside the project, NOT /tmp on Windows):
<asm-analyze-cmd> extract-assembly --elf-path <…> --functions <…> --arch <loci_target> > .loci-build/extract.json
# Then read each field with jq:
jq -r '.control_flow_graph' .loci-build/extract.json # annotated CFG text
jq -r '.timing_architecture' .loci-build/extract.json # timing arch string
jq -c '.timing_csv_chunks[]' .loci-build/extract.json # one chunk per line, ready to pass to MCP
jq '.timing_csv_chunks | length' .loci-build/extract.json # chunk count
/tmp/... is a Git Bash convenience path that Windows-native Python and the venv
python cannot resolve. Always write to a path inside the working directory (e.g.
.loci-build/) so every tool — bash, jq, Python, MCP — sees the same file.
Call mcp__plugin_loci_loci__get_assembly_block_exec_behavior for all chunks in
parallel (one call per chunk, all in the same response):
csv_text: the chunkarchitecture: the timing_architecture value from step 3IMPORTANT: Issue all chunk calls simultaneously in a single message — do NOT call them sequentially. Concatenate the result CSVs (skip duplicate headers) before computing metrics.
Do this for both pre-edit and post-edit assembly of modified functions, and for post-edit only of added functions.
From the MCP response and also using the annotated CFG's from step 3, compute:
execution_time_nsenergy_ws (report in uWs)The MCP response CSV columns are exactly: function_name, std_dev_ns,
execution_time_ns, energy_ws. Reference those column names literally
when reading rows. Use execution_time_ns and energy_ws; the
std_dev_ns column is not surfaced in this skill.
bl / blx call-site rows (pre AND post)Hot-path blocks that end in bl / blx are call sites; the MCP
returns only the branch-only / single-instruction cost for that block
(e.g. ~32 ns on Cortex-M0+), NOT the callee body. You MUST expand every
such site on both the pre-edit and post-edit hot paths before computing
the Worst path / Happy path / Energy values that go into the table —
otherwise both sides are entry-block-only and a callee-internal
regression (e.g. a 200 → 240 ns body change at a bl site) silently
shows up as 0 ns delta because both sides counted only the bl
instruction.
For each hot-path block ending in bl / blx:
function_name starts with
<callee>_ are present in the same MCP response): replace the
call-site cost with bl_cost + Σ over the callee's hot-path blocks of (execution_time_ns + std_dev_ns), and energy similarly.
Recurse one more level if the callee itself contains an in-binary
bl. Stop at depth 2.<callee>_* rows in the response — e.g.
FreeRTOS / vendor library): keep bl_cost as a lower bound.
Append (≥ … ns — external callees unmeasured) to the Note of
every affected summary row — Worst path, Happy path, and/or Energy
whenever that row's hot path includes the external callee — and add
a CFG-Analysis line naming the external callee.The Worst / Happy / Energy values that go into the Worst path / Happy
path / Energy rows are the expanded sums. If any included hot-path
block is an external callee kept at bl_cost, the corresponding row
remains a lower bound and must be annotated accordingly. The Hot
blocks breakdown between table and verdict still uses per-block CSV
rows (the user wants to see the heaviest blocks) — but a
bl-terminated block in that breakdown should be labelled
bl <callee> so the reader knows its measured cost is just the
branch, with the callee's body counted separately in the Worst path
total when available, or left unmeasured for external callees.
For modified functions, compute % diff:
diff_pct = ((post_value - pre_value) / pre_value) * 100
The diff is meaningful only after expansion — if either side is entry-block-only, the % diff is between two understated baselines and the noise-margin downgrade rule will silently mask real regressions.
LOCI MCP server is not connected. Run
/mcpin Claude Code to manage MCP servers, then approve the loci server. If it does not appear, restart Claude Code — the plugin registers it automatically on startup.
LOCI usage quota reached — post-edit analysis skipped.
<server error message verbatim — includes usage/limit, reset countdown, and upgrade link>
The server message already contains reset time and upgrade CTA, e.g.:
"Daily token limit reached (31,000 / 30,000 tokens). Resets in 4h 23m.
Upgrade to Premium at auroralabs.com for 300,000 tokens/day."
Show it verbatim. Then end the skill.Before emitting any output, think through each of these questions.
Increment R (co-reasoning counter) by 1 for this pass.
execution_time_ns as a Performance sub-finding.
Note when the change is timing-neutral or improves performance.new hot-path block <addr> (top-N)).The output has three blocks in order: (1) conclusion table, (2) voice remark, then the LOCI footer. No free-form prose sections, no multi-paragraph Reasoning write-ups, no per-callee enumerations.
The build-metadata block from build-metadata compile is intentionally
NOT shown to the user. The only build-related thing that ever surfaces in
the report is the LOCI · build mismatch block, and only when Step 1b's
build-metadata diff actually finds a parity break — that block prints
itself, emit it verbatim when it appears.
Icon vocabulary: ✅ PASS · ⚠️ WARNING · ❌ FAIL.
Row-inclusion rules:
.o.prev present and non-empty)Order when present. Before/After columns carry the metric value (timing or energy); sub-findings ride in the Note.
execution_time_ns diff and hot-path position (new block in top-3).
Status: ✅ if |diff%| ≤ 10% or improvement AND no new hot-path
block; ⚠️ if |diff%| > 10% with absolute within budget OR a new
hot-path block landed in top-3; ❌ if a known budget is exceeded.
Before/After = execution_time_ns. Note format:
<pre>→<post> ns (±X%) [, new hot-path block <addr> (top-N)].±X% and absolute when small.stack: <N> B (<usage>%) — <verdict>. No Before/After.memory: ROM <X>% / RAM <Y>% — <verdict>. No Before/After.Build-parity issues are NOT a table row. build-metadata diff's own
LOCI · build mismatch block (emitted on non-zero exit) already
handles that case visibly and loudly.
.o.prev)Drop the Before column; single-column After for the Performance and
Energy rows (no ±% in the Note since there is no baseline to diff
against — record the absolute values as the new baseline). Safety,
Stack, and Memory rows fire on the same triggers as the with-baseline
case.
## Post-Edit: <FunctionName>
| Gate | Before | After | Status | Note |
|--------------------|-----------|-----------|:------:|-----------------------------|
| <row 1 applicable> | <val> | <val> | ? | <cited reason> |
| ... | ... | ... | ? | ... |
Verdict: **<OK|CAUTION|FLAG>** — <one sentence cause>
## Post-Edit: <FunctionName> (NEW)
| Gate | After | Status | Note |
|--------------------|-----------|:------:|-----------------------------|
| <row 1 applicable> | <val> | ? | <cited reason> |
| ... | ... | ? | ... |
Verdict: **<OK|CAUTION|FLAG>** — <one sentence cause>
(no pre-edit artifact — first measurement on this branch)
## Post-Edit: process_message
| Gate | Before | After | Status | Note |
|--------------|----------|----------|:------:|-----------------------------------|
| Performance | 1404 ns | 3474 ns | ⚠️ | +147%, new hot-path block bb_0x1ea (top-1) |
| Energy | 0.20 µWs | 0.49 µWs | ⚠️ | +148%, absolute <1 µWs |
Verdict: **CAUTION (acceptable)** — explicable, once-per-event handler
When the table footer is CAUTION or FLAG, don't stop at reporting.
The skill must:
bb_0x1ea is a wide-integer arithmetic step — consider
narrowing the type to a 32-bit integer where the value range allows,
saves ~500 ns.")Before emitting the final conclusion table, inspect what the first-pass
reasoning produced. If any pattern below matches, loop back BEFORE
emitting. Each extra MCP call increments M; each looped-back synthesis
increments R. The table the user sees is the post-loop version.
| Row pattern | Trigger |
|---|---|
| Performance ⚠️ with both timing-regression AND new-hot-path-block sub-findings | The new block IS the regression. Don't just report — propose a concrete optimization (cache, lighter callee, inline, different data type) naming the specific block in the Note. Follow the "Action on CAUTION or FLAG" flow. |
| Performance AND Energy ⚠️ both regress | Real regression in two metrics, not isolated to one. Confidence in ⚠️ is high; proceed to propose root cause. |
| Stack Note shows usage > 80% of task budget | Re-run stack-depth with larger --max-recursion-depth to confirm; surface the top frame contributor by name in the Note before emitting. |
| Memory Note shows region > 90% | Re-run memory-report with --top-n 20 to identify the specific symbols pushing the region toward its limit before emitting. |
Before the footer, add one short LOCI voice remark (max 15 words) that acknowledges the user's work grounded in a specific number from the analysis. Attribute improvements to the user ("clean work", "smart move", "tight code"). For concerns, be honest and constructive with specifics. Skip if the analysis produced no results or the user needs raw data only.
After emitting all per-function reports and the voice remark, append the footer as the last thing printed — only if N > 0. If no functions were processed, do NOT emit the footer.
Record cumulative stats (run via Bash before rendering the footer).
Pass --verdict "<verbatim-verdict-line>" so the verdict ride-along
ships alongside the per-function trends payload — the line is the same
string already rendered to chat (Verdict: OK — <cause>, Verdict: CAUTION — <cause>,
or Verdict: FLAG — <cause>), unbolded, no surrounding asterisks.
Also pass --gates '<gates-json>' — a compact JSON object capturing
the per-row Status from the conclusion table just rendered. Map the
icons: ✅→pass · ⚠️→warn · ❌→fail. Only include gates that fired
this run (omitted gates were not part of the table). Allowed gate
names: Safety · Performance · Energy · Stack · Memory.
Example for the worked example above:
{"Performance":"warn","Energy":"warn"}.
<venv-python> <plugin-dir>/lib/loci_stats.py record --context-file "<project-context>" --skill post-edit --functions <N> --mcp-calls <M> --co-reasoning <R> --verdict "<verbatim-verdict-line>" --gates '<gates-json>'
Record per-function measurements (single Bash call for all functions). Pipe all measurements as JSONL via stdin. Skip functions where MCP timing was unavailable.
echo '<jsonl_records>' | <venv-python> <plugin-dir>/lib/loci_stats.py record-measurement --context-file "<project-context>" --stdin --skill post-edit
Where <jsonl_records> is one JSON object per line for each modified/added
function with post-edit timing values:
{"fn":"<func1>","worst_ns":<execution_time_ns>,"energy_uws":<E>,"src":"<source_file>"}
{"fn":"<func2>","worst_ns":<execution_time_ns>,"energy_uws":<E>,"src":"<source_file>"}
The worst_ns field name is the storage-schema key consumed by
loci_stats.py (preserved for compat with prior on-disk measurements);
pass execution_time_ns into it. The happy_ns field is no longer
written.
Read trend lines (single Bash call for all functions; capture output):
<venv-python> <plugin-dir>/lib/loci_stats.py trend-line --context-file "<project-context>" --function <func1>,<func2>,...
One line. Icon-led, no surrounding bars, middle-dot separators, spaces
around the → arrow. The trend-line output is the primary scalar —
parse it into <fn> · <pre> → <post> ns (<±pct>, <N> edits):
<icon> LOCI post-edit · <fn> · <pre> → <post> ns (<±pct>, <N> edits)
<icon> — mirrors the body's conclusion-table verdict: ✅ for OK,
⚠️ for CAUTION, ❌ for FLAG.<fn> — when N = 1, the single edited function. When N > 1, the
compact form is replaced by the expanded form (see below).Worked example (clean run, N=1):
✅ LOCI post-edit · Connection_ConnEventHandler · 1815 → 1498 ns (-17%, 2 edits)
When post-edit escalated into stack-depth or memory-report AND the
escalated skill returned clean, append a space-separated +<skill>
marker to the primary scalar:
✅ LOCI post-edit · Connection_ConnEventHandler · 1815 → 1498 ns (-17%, 2 edits) +stack-depth
A non-clean escalated result already flips a Stack/Memory row in the
post-edit conclusion table to ⚠️/❌, which flips the post-edit verdict
to CAUTION/FLAG and triggers expansion via the verdict rule below. So
+<skill> only ever appears next to a green icon.
Replace the compact form with the expanded multi-line form if any of the following is true:
⚠️ CAUTION or ❌ FLAG.LOCI · build mismatch block was emitted
earlier in the report (toolchain changed between preflight and
post-edit; % diffs are low-confidence).N > 1 functions were modified/added in this run — the compact line
cannot carry per-function trends honestly; render the expanded form
with one ↳ trend: line per function.Expanded form:
─── LOCI · post-edit ───────────────────
<N> functions · <M> MCP calls · <R> co-reasoning
Verdict: <OK | CAUTION | FLAG> — <one-line summary>
↳ trend: <trend-line-output> ← one line per function
────────────────────────────────────────
The expanded form does not include the cumulative branch-stats line.
mcp__plugin_loci_loci__get_assembly_block_exec_behavior (exec-behaviors)
(typically 2 for modified functions: pre + post; 1 for added functions)