@h2loop/autosar 1.3.0 → 1.4.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.
- package/README.md +3 -1
- package/agents/autosar-integrator.md +12 -0
- package/package.json +1 -1
- package/plugins/index.js +37 -30
- package/python/ecuc_engine/arxml_edit.py +163 -7
- package/python/ecuc_engine/arxml_new.py +113 -0
- package/python/ecuc_engine/cli.py +20 -2
- package/skills/autosar/SKILL.md +52 -0
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ Deterministic tools for AUTOSAR Classic integration work, so the agent never han
|
|
|
9
9
|
| `ecuc_validate` | multiplicity, mandatory, range, enum, type, string, ref target/dest, dangling, duplicates → LSP-like diagnostics | local |
|
|
10
10
|
| `ecuc_find` | structured queries over the ECUC (conditions, references to/from) | local |
|
|
11
11
|
| `ecuc_crosscheck` | per-module cross-check table (extract, gateway, NvM sizeof, capacity, packing, handles) | local |
|
|
12
|
-
| `
|
|
12
|
+
| `arxml_check` | verify non-ECUC ARXML: strict schema load, every reference resolves with the right DEST, file:line | local |
|
|
13
|
+
| `arxml_new` | create a new, empty R25-11 ARXML file (header + AR-PACKAGE tree) for `arxml_edit` to fill | local |
|
|
14
|
+
| `arxml_edit` | structured in-place edits of System extract / SWC ARXML (DEST inferred, schema-ordered, missing packages created) | local |
|
|
13
15
|
| `autosar_add_signal` | add a signal end to end (ECUC + extract chain + DBC), validate or roll back | local |
|
|
14
16
|
| `arxml_diff` | semantic diff by AUTOSAR path (ignores UUIDs, order, whitespace, file splits) | local |
|
|
15
17
|
| `autosar_trace` | config ↔ generated C symbol ↔ in-house callers, via the H2Loop code graph | H2Loop backend |
|
|
@@ -18,6 +18,8 @@ permission:
|
|
|
18
18
|
"ecuc_read": allow
|
|
19
19
|
"ecuc_find": allow
|
|
20
20
|
"ecuc_crosscheck": allow
|
|
21
|
+
"arxml_new": allow
|
|
22
|
+
"arxml_check": allow
|
|
21
23
|
"arxml_edit": allow
|
|
22
24
|
"autosar_add_signal": allow
|
|
23
25
|
"autosar_audit": allow
|
|
@@ -40,6 +42,16 @@ permission:
|
|
|
40
42
|
---
|
|
41
43
|
You are an AUTOSAR Classic integrator. Load the `autosar` skill first and follow it.
|
|
42
44
|
|
|
45
|
+
Every change follows the skill's ground → decide → change → verify loop: get each fact the change depends on from the
|
|
46
|
+
tools (context engine, `ecuc_read schema=true`, `ecuc_find`, `ecuc_crosscheck`, spec IDs) and list it with its source
|
|
47
|
+
BEFORE deciding; plan only from those facts (no guessed values, ids or reference targets); change only with the
|
|
48
|
+
structured tools; then verify (`arxml_check`, `ecuc_validate`, generate/build/verify_rte), fix and verify again until
|
|
49
|
+
every check is clean. Never report done with a failing or unrun check.
|
|
50
|
+
|
|
51
|
+
Method (new extract / SWC / interface file):
|
|
52
|
+
- `arxml_new` (file + packages), then one `arxml_edit` call with the elements in dependency order, then `arxml_check`
|
|
53
|
+
until CLEAN. Never write ARXML by hand, by script, or via a renamed temp file.
|
|
54
|
+
|
|
43
55
|
Method (validation / fix tasks):
|
|
44
56
|
- `ecuc_validate` unfiltered first — necessary, NOT sufficient. Never conclude "no faults" from one validate call.
|
|
45
57
|
- Write a todo checklist with one item per module present (Com, PduR, CanIf, EcuC, NvM/Fee/Ea, Dem, Os, Rte) and run
|
package/package.json
CHANGED
package/plugins/index.js
CHANGED
|
@@ -1,58 +1,65 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{readFileSync as le}from"fs";import{isAbsolute as ue,join as M,resolve as fe}from"path";import{tool as b}from"@kilocode/plugin";import{homedir as z}from"os";import{join as
|
|
3
|
-
`).filter(Boolean).at(-1);if(!d)return{ok:!1,error:`engine produced no output (exit ${
|
|
4
|
-
`);function
|
|
5
|
-
Did you mean: ${
|
|
6
|
-
Schema errors: ${JSON.stringify(
|
|
2
|
+
import{readFileSync as le}from"fs";import{isAbsolute as ue,join as M,resolve as fe}from"path";import{tool as b}from"@kilocode/plugin";import{homedir as z}from"os";import{join as E}from"path";var K=()=>process.env.HYDRON_TEST_HOME||z(),I=(r,i)=>process.env[r]||E(K(),i),Z=()=>E(I("XDG_CONFIG_HOME",".config"),"hydron-cli"),Q=()=>E(I("XDG_DATA_HOME",".local/share"),"hydron-cli"),ee=()=>E(I("XDG_STATE_HOME",".local/state"),"hydron-cli");async function D(r){let i=Bun.file(r);if(!await i.exists())return;return i.json().catch(()=>{return})}async function T(){let r=process.env.KILO_API_URL?.trim();if(r)return r.replace(/\/+$/,"");let i=await D(E(Z(),"server-config.json")),c=typeof i?.apiBaseUrl==="string"?i.apiBaseUrl:void 0;if(!c)throw Error("H2Loop backend URL unknown: set KILO_API_URL or sign in with `hydron auth login`.");return c.replace(/\/+$/,"")}function te(r){let i=r?.split(".")[1];if(!i)return"";try{let c=JSON.parse(Buffer.from(i.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return typeof c.sub==="string"?c.sub:""}catch{return""}}async function ne(r){let i=te(r.access);if(i)return i;if(!r.refresh)return"";return new Bun.CryptoHasher("sha256").update(r.refresh).digest("hex").slice(0,12)}async function J(){let i=(await D(E(Q(),"auth.json")))?.hydron;if(!i||i.type!=="oauth"||!i.access)throw Error("Not signed in to H2Loop. Run `hydron auth login` (or sign in from the TUI), then retry.");return i}async function B(){let r=await J();if(typeof r.expires==="number"&&r.expires>0&&Date.now()>=r.expires-30000)throw Error("The H2Loop session token has expired. Send any message in Hydron (the CLI refreshes it) or re-login with `hydron auth login`, then retry.");return r.access}async function F(r){let i=await fetch(`${await T()}/api/v1/workspaces`,{headers:{Authorization:`Bearer ${await B()}`},signal:r?AbortSignal.any([r,AbortSignal.timeout(30000)]):AbortSignal.timeout(30000)});if(i.status===401)throw Error("H2Loop rejected the session (401). Re-login with `hydron auth login`, then retry.");if(!i.ok)throw Error(`could not list H2Loop projects: HTTP ${i.status}`);let c=await i.json();return Array.isArray(c)?c:c?.items??[]}var H=()=>E(ee(),"h2loop-project.json");async function C(r,i){let c=r===void 0||r===null?"":String(r).trim();if(/^\d+$/.test(c))return c;if(c){let t=await F(i),d=t.filter((f)=>f.name===c),l=d.length?d:t.filter((f)=>f.name?.toLowerCase()===c.toLowerCase());if(l.length===1)return String(l[0].id);if(l.length>1)throw Error(`workspace "${c}" is ambiguous: ${l.map((f)=>`${f.name} (id ${f.id})`).join(", ")}; pass the numeric id`);let h=t.filter((f)=>f.name?.toLowerCase().includes(c.toLowerCase())).slice(0,10);throw Error(`no H2Loop project named "${c}"`+(h.length?`; similar: ${h.map((f)=>`${f.name} (id ${f.id})`).join(", ")}`:`; you have ${t.length} project(s)`))}let e=await D(H());if(!e)throw Error("No H2Loop project selected (run `hydron projects select <id>`), or pass workspace_id (id or name).");let a=await J();if(e.project&&typeof e.project==="object"){let t=e.project;if(typeof e.account==="string"&&e.account!==await ne(a))throw Error("The selected H2Loop project belongs to another account; select one again or pass workspace_id.");if(t.id!==void 0)return String(t.id)}let s=e;if(s.id===void 0)throw Error(`${H()} holds no project id; select a project again or pass workspace_id.`);if(!(await F(i)).some((t)=>String(t.id)===String(s.id)))throw Error(`The selected project ${s.name??""} (id ${s.id}) is not accessible to the signed-in account; select another or pass workspace_id.`);return String(s.id)}async function O(r,i,c,e){let a=`${await T()}${r}?workspace_id=${encodeURIComponent(i)}`,s=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${await B()}`},body:JSON.stringify(c),signal:e?AbortSignal.any([e,AbortSignal.timeout(120000)]):AbortSignal.timeout(120000)}),n=await s.text();if(s.status===401)throw Error("H2Loop rejected the session (401). Re-login with `hydron auth login`, then retry.");if(s.status===404&&n.includes("Not Found"))throw Error(`${r} is not available on ${await T()} (backend predates the AUTOSAR endpoints).`);if(!s.ok)throw Error(`H2Loop ${r} failed: HTTP ${s.status} ${n.slice(0,800)}`);try{return JSON.parse(n)}catch(t){throw Error(`H2Loop ${r} returned non-JSON (${String(t)}): ${n.slice(0,300)}`)}}var U=/\.arxml\b/i,L="Blocked: .arxml files must not be edited by hand (it breaks DEFINITION-REF/DEST, UUIDs and the round trip). Use ecuc_edit for ECUC values, arxml_edit for System-extract/SWC elements (or autosar_add_signal for a new signal), then ecuc_validate. To start a NEW .arxml file, call arxml_new (file + package paths), then fill it with arxml_edit.";function W(r,i){if(process.env.AUTOSAR_ALLOW_ARXML_EDIT==="1"||!i)return;let c=typeof i.filePath==="string"?i.filePath:typeof i.file_path==="string"?i.file_path:"";if(["edit","write","multiedit"].includes(r)){let e=Array.isArray(i.edits)?i.edits.some((a)=>typeof a?.filePath==="string"&&U.test(a.filePath)):!1;if(U.test(c)||e)return`${L} (${r} on ${c||"an .arxml file"})`;return}if(r==="apply_patch"||r==="patch"){let e=String(i.patchText??i.patch??""),a=e.match(/^\*\*\* (?:Add|Update|Delete) File: (.+\.arxml)\s*$/im)??e.match(/^\+\+\+ (?:b\/)?(.+\.arxml)/im);return a?`${L} (${r} on ${a[1].trim()})`:void 0}if(r==="bash"){let e=String(i.command??"");if(!U.test(e))return;let a=/\b(?:sed|gsed)\b[^|;&]*\s-(?:[a-zA-Z]*i|-in-place)\b/.test(e)||/\bperl\b[^|;&]*\s-[a-zA-Z]*i/.test(e),s=/(?:^|[^<>&0-9])>>?\s*['"]?[^\s'"|;&]*\.arxml\b/i.test(e)||/\btee\b[^|;&]*\.arxml\b/i.test(e),n=/\b(?:mv|cp|ln|install|rsync)\b[^|;&]*\.arxml['"]?\s*(?:$|[|;&])/im.test(e)||/\b(?:os\.(?:rename|replace)|shutil\.(?:move|copy\w*)|fs\.(?:rename|copyFile)\w*|Path\([^)]*\)\.rename)\([^)]*\.arxml/i.test(e)||/\.(?:rename|replace)\(\s*['"][^'"]*\.arxml/i.test(e),t=/open\([^)]*\.arxml[^)]*,\s*(?:mode\s*=\s*)?['"][wa+]/i.test(e)||/\.arxml['"]?\s*\)?\s*\.write_(?:text|bytes)\b/i.test(e)||/write_(?:text|bytes)\([^)]*\)[^;\n]*\.arxml/i.test(e)||/\bwriteFile(?:Sync)?\([^)]*\.arxml/i.test(e)||/\.write\(\s*['"][^'"]*\.arxml/i.test(e)||/\b(?:awk|gawk)\b[^|;&]*-i\s+inplace/.test(e)||/\bed\b\s+-s\s+[^|;&]*\.arxml/.test(e);if(a||s||t||n)return`${L} (bash: ${e.slice(0,120)})`}return}import{existsSync as re}from"fs";import{dirname as ie,join as P}from"path";import{fileURLToPath as se}from"url";var oe="autosar-data==0.17.0";function ae(){let r=ie(se(import.meta.url)),i=[P(r,"..","python"),P(r,"..","..","python")].find((c)=>re(P(c,"ecuc_engine","__main__.py")));if(!i)throw Error(`ECUC engine not found next to ${r} (expected ../python/ecuc_engine)`);return i}async function ce(r){try{return await Bun.spawn([...r,"-c","import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)"],{stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited===0}catch{return!1}}var j={};async function de(){if(j.cmd!==void 0)return j.cmd;let r=process.env.AUTOSAR_PYTHON?.trim(),c=r?[[r]]:[["uv","run","--no-project","--quiet","--with",oe,"--python",">=3.10","python"],["python3"],["python"],["py","-3"]];for(let e of c)if(await ce(e))return j.cmd=e,e;return j.cmd=null,null}async function y(r,i,c){let e=await de();if(!e)return{ok:!1,error:"no Python >= 3.10 found. Install uv (https://docs.astral.sh/uv/) or Python 3.10+, or set AUTOSAR_PYTHON to an interpreter path."};let a=ae(),s=Bun.spawn([...e,"-m","ecuc_engine","--root",r,...i,"--json"],{cwd:r,env:{...process.env,PYTHONPATH:[a,process.env.PYTHONPATH].filter(Boolean).join(process.platform==="win32"?";":":")},stdout:"pipe",stderr:"pipe",stdin:"ignore",signal:c}),[n,t]=await Promise.all([new Response(s.stdout).text(),new Response(s.stderr).text()]);await s.exited;let d=n.trim().split(`
|
|
3
|
+
`).filter(Boolean).at(-1);if(!d)return{ok:!1,error:`engine produced no output (exit ${s.exitCode}): ${t.trim().slice(-2000)}`};try{return JSON.parse(d)}catch(l){return{ok:!1,error:`engine output was not JSON (${String(l)}): ${d.slice(0,500)} ${t.slice(-500)}`}}}var o=b.schema,pe=["# AUTOSAR pack active","Load the `autosar` skill. Config: ecuc_read/ecuc_find, not grep. Edit via ecuc_edit/arxml_edit/","autosar_add_signal (.arxml hand edits blocked), then ecuc_validate + ecuc_crosscheck per module.","C review: autosar_audit. Runnables: autosar_build_context, autosar_verify_rte, autosar_build.","Cite @path/@def/file:line/hex as tools print; req IDs only if tool/spec text shows them."].join(`
|
|
4
|
+
`);function N(r){if(typeof r!=="string")return r;let i=r.trim();if(!i.startsWith("[")&&!i.startsWith("{")&&!i.startsWith('"'))return r;try{return N(JSON.parse(i))}catch{return r}}var A=(r)=>o.preprocess(N,r);function v(r){let i=N(r);if(i===void 0||i===null||i==="")return;if(typeof i==="string")return[i];if(Array.isArray(i))return i.map(String).filter((c)=>c.trim());return[String(i)]}var R=()=>o.preprocess(v,o.array(o.string()).optional()),k=" In answers cite, for every element, its AUTOSAR path (the value's @path, e.g. /Pkg/Com/ComConfig/MySig), its"+" DEFINITION-REF (@def, e.g. /AUTOSAR/EcucDefs/Com/ComConfig/ComSignal) and file:line \u2014 never give the"+` DEFINITION-REF when the path is asked. A value's file:line is its "@at:<Param>" (the VALUE element; "@at" is`+' the container); integers come with "@hex:<Param>" \u2014 copy both, never compute a line or a base conversion'+" yourself. Cite a requirement ID only if it appears verbatim in a tool result or spec text read this session.";function x(r,i=200){let c=r.slice(0,i).map((e)=>`${e.file}:${e.line} ${e.severity.toUpperCase()} ${e.rule} ${e.path} \u2014 ${e.message}${e.defRef?` [def ${e.defRef}]`:""}`);if(r.length>i)c.push(`\u2026 ${r.length-i} more`);return c}function w(r){let i=r.didYouMean?`
|
|
5
|
+
Did you mean: ${r.didYouMean.join(", ")}`:"",c=r.schema?`
|
|
6
|
+
Schema errors: ${JSON.stringify(r.schema)}`:"";return`ERROR: ${r.error}${i}${c}`}function S(r){let i=r.warnings??[];return i.length?i.map((c)=>`WARNING: ${c}`).join(`
|
|
7
7
|
`)+`
|
|
8
|
-
`:""}function g(i
|
|
9
|
-
`);return c.length>
|
|
10
|
-
`):
|
|
11
|
-
{"${e}": {"command": "${e==="generate"?"<vendor-cli> -p Config/Ecu.dpa -g {module}":"make -C Build all"}", "cwd": ".", "timeoutSec": 1800}}`;let d=
|
|
8
|
+
`:""}function g(r,i){if(!i)return r;return ue(i)?i:fe(r,i)}async function he(r){let i=Bun.file(M(r,".hydron","autosar.json"));if(!await i.exists())return{};return i.json().catch((c)=>{throw Error(`.hydron/autosar.json is not valid JSON: ${String(c)}`)})}async function me(r,i){let c=(await he(r))[i];if(typeof c==="string")return{command:c};if(c&&typeof c==="object"&&typeof c.command==="string")return c;return}function G(r,i=150){let c=r.split(`
|
|
9
|
+
`);return c.length>i?[`\u2026 (${c.length-i} earlier lines omitted)`,...c.slice(-i)].join(`
|
|
10
|
+
`):r}async function ge(r,i,c,e){let a=process.platform==="win32"?["cmd","/d","/s","/c",r]:["sh","-c",r],s=Date.now(),n=Bun.spawn(a,{cwd:i,stdout:"pipe",stderr:"pipe",stdin:"ignore",signal:AbortSignal.any([e,AbortSignal.timeout(c*1000)])}),[t,d]=await Promise.all([new Response(n.stdout).text(),new Response(n.stderr).text()]);return{code:await n.exited,out:t,err:d,secs:Math.round((Date.now()-s)/1000)}}var be=(r)=>(r??[]).map((i)=>`CAN ID ${i.canIdHex??"?"} (${i.canId??"?"}) ${i.direction} via ${i.canIfPdu}, controller ${i.controller??"?"}, network ${(i.network??[]).join(", ")||"?"}`).join("; ");async function X(r,i,c){let e=i.filter((t)=>t?.path&&/\/Com((Group)?Signal|IPdu)$/.test(String(t.definition_ref??""))&&t.ipdu===void 0&&t.direction===void 0);if(!e.length)return[];let a=await y(r,["signal-owners",...new Set(e.map((t)=>t.path))],c);if(!a.ok)return[`NOTE: owning I-PDU not computed locally (${a.error})`];let s=a.owners,n=[];for(let t of e){let d=s[t.path];if(d===null||d===void 0){n.push(`NOTE: ${t.path} is not in the local checkout (${r}); owning I-PDU/direction unknown`);continue}t.ipdu_local=d,n.push(d.length?`${t.path}: owning I-PDU ${d.map((l)=>`${l.ipdu} (ComIPduDirection=${l.direction??"?"}${l.can?.length?`; ${be(l.can)}`:""})`).join(", ")} [local ECUC]`:`${t.path}: referenced by no ComIPdu (ComIPduSignalRef) in the local ECUC`)}return n}var ye=(r)=>{if(typeof r==="number"&&Number.isSafeInteger(r))return r;if(typeof r==="string"&&/^\s*-?(0x[0-9a-f]+|\d+)\s*$/i.test(r)){let i=r.trim(),c=i.startsWith("-")?-Number(i.slice(1)):Number(i);return Number.isSafeInteger(c)?c:null}return null},q=(r)=>r<0?`-0x${(-r).toString(16).toUpperCase()}`:`0x${r.toString(16).toUpperCase()}`;async function V(r,i,c){let e=i.filter((t)=>typeof t?.path==="string");if(!e.length)return[];let a=await y(r,["value-sites",...new Set(e.map((t)=>t.path))],c),s=a.ok?a.sites:{},n=[];if(!a.ok)n.push(`NOTE: value lines not computed locally (${a.error})`);for(let t of e){let d=s[t.path];if(d){t.values_local=d.values,n.push(`VALUES ${t.path} (container ${d.at}) [local ECUC; cite these lines]:`);for(let[f,p]of Object.entries(d.values))for(let u of Array.isArray(p)?p:[p])n.push(` ${f} = ${JSON.stringify(u.value)}${u.hex?` (dec ${u.dec}, hex ${u.hex})`:""} @ ${u.at}`);continue}let l=t.values&&typeof t.values==="object"?t.values:{},h={};for(let[f,p]of Object.entries(l)){let u=ye(p);if(u!==null)h[f]=`dec ${u}, hex ${q(u)}`}if(Object.keys(h).length)t.values_int=h;if(a.ok)n.push(`NOTE: ${t.path} is not in the local checkout (${r}): its file:line${t.file?` ${t.file}:${t.line??"?"}`:""} is where the ELEMENT starts, not a value line. To cite a value's line, read that file; never estimate one.`)}return n}var Y=(r)=>{let i=r.notes??[],c=r.errors??[];return[...i.map((e)=>`NOTE: ${typeof e==="string"?e:JSON.stringify(e)}`),...c.map((e)=>`BACKEND ERROR: ${typeof e==="string"?e:JSON.stringify(e)}`)]};async function we(r,i){try{let c=await i(),e=typeof c==="string"?c:c.output;if(e&&e.trim())return c;return`${r}: no output (this is a bug in the AUTOSAR pack; retry or report it)`}catch(c){return`ERROR (${r}): ${c instanceof Error?c.message:String(c)}`}}function _e(r){for(let[i,c]of Object.entries(r)){let e=c.execute;c.execute=(a,s)=>we(i,()=>e(a,s))}return r}function $e(r,i=process.env){let c=new Set;for(let e of(i.AUTOSAR_DISABLE_TOOLS??"").split(/[\s,]+/))if(e)c.add(e);if(r)try{let e=JSON.parse(le(M(r,".hydron","autosar.json"),"utf8"));if(Array.isArray(e.disableTools)){for(let a of e.disableTools)if(typeof a==="string"&&a)c.add(a)}}catch{}return c}function Se(r,i){if(i.size===0)return r;return Object.fromEntries(Object.entries(r).filter(([c])=>!i.has(c)))}var ve=async(r)=>{let i=$e(r?.directory),c=(e)=>b({description:e==="generate"?"Run the project's AUTOSAR code generator (e.g. the vendor's configurator CLI) as configured in .hydron/autosar.json `generate` ({command, cwd?, timeoutSec?} or a string; `{module}` and `{args}` placeholders are substituted). Run it only after ecuc_validate reports no errors. Asks the user for permission. Returns the exit code and the output tail; follow with arxml_diff/autosar_build.":"Run the project's build command as configured in .hydron/autosar.json `build` ({command, cwd?, timeoutSec?} or a string; `{module}`/`{args}` substituted). Asks the user for permission. Returns the exit code and the output tail (compiler/linker errors). Use after autosar_generate.",args:{module:o.string().optional().describe("module name substituted for {module} in the command, if used"),args:o.string().optional().describe("extra arguments substituted for {args} (or appended if no placeholder)"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(a,s){let n=g(s.directory,a.project_dir),t=await me(n,e);if(!t)return`No \`${e}\` command configured. Add it to ${M(n,".hydron","autosar.json")}, e.g.
|
|
11
|
+
{"${e}": {"command": "${e==="generate"?"<vendor-cli> -p Config/Ecu.dpa -g {module}":"make -C Build all"}", "cwd": ".", "timeoutSec": 1800}}`;let d=a.args??"",l=t.command.replaceAll("{module}",a.module??""),h=l.includes("{args}")?l.replaceAll("{args}",d):`${l}${d?` ${d}`:""}`,f=g(n,t.cwd);await s.ask({permission:`autosar_${e}`,patterns:[h],always:[t.command],metadata:{command:h,cwd:f}}),s.metadata({title:`${e}: ${h}`});let p=await ge(h,f,t.timeoutSec??3600,s.abort),u=p.code===0?"OK":`FAILED (exit ${p.code})`;return{title:`autosar_${e} ${u}`,output:[`$ ${h} (cwd ${f}, ${p.secs}s) -> ${u}`,p.out.trim()?`--- stdout (tail) ---
|
|
12
12
|
${G(p.out)}`:"",p.err.trim()?`--- stderr (tail) ---
|
|
13
13
|
${G(p.err,80)}`:""].filter(Boolean).join(`
|
|
14
|
-
`),metadata:{exit:p.code}}}});return{"tool.execute.before":async(e,
|
|
14
|
+
`),metadata:{exit:p.code}}}});return{"tool.execute.before":async(e,a)=>{let s=W(e.tool,a.args);if(s)throw Error(s)},"experimental.chat.system.transform":async(e,a)=>{a.system.push(pe)},tool:Se(_e({ecuc_read:b({description:`THE way to answer any question about configured ECUC values (use it instead of grep/read on .arxml). Read AUTOSAR ECUC configuration as compact ECUC JSON (keyed by def names: "<DefName>:<ShortName>" for containers, params by def short-name, references as "->/path"; every container carries @path = its AUTOSAR path, @def = its DEFINITION-REF and @at = its file:line; every value is followed by "@at:<Param>" = file:line of its VALUE element, and integers by "@hex:<Param>" ("@dec:<Param>" when the ARXML text is hex)). No path: lists modules and the param-def/config files found. path may be a full AUTOSAR path, a SHORT-NAME ('DoorLatch') or a partial path ('ComConfig/DoorLatch', 'DoorLatch/ComBitPosition'); ambiguous names return candidate paths. A module/container: its values to `+"`depth` levels (merged across split files). A param path (\u2026/Container/Param) or a def path "+"(/AUTOSAR/EcucDefs/\u2026): the value and/or the definition. schema=true adds the def summary (allowed "+"params with type/range/literals/multiplicity, allowed sub-containers) \u2014 read it before ecuc_edit. "+"Never read or edit ARXML by hand."+k,args:{path:o.string().optional().describe("AUTOSAR path, SHORT-NAME or partial path of a module/container/parameter, or a def path"),depth:o.number().int().min(0).max(20).optional().describe("sub-container levels to expand (default 2)"),schema:o.boolean().optional().describe("include the param-def summary for the element"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=["read"];if(e.path)s.push(e.path);if(s.push("--depth",String(e.depth??2)),e.schema)s.push("--schema");let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok)return S(n)+w(n);let{ok:t,engine:d,ms:l,warnings:h,...f}=n;return S(n)+JSON.stringify(f,null,1)}}),ecuc_edit:b({description:"Edit ECUC ARXML in place \u2014 the ONLY way to change configuration (edit/write/sed on .arxml are blocked). "+"Get exact paths and allowed params from ecuc_read (schema=true) first. Ops by AUTOSAR path: {op:'set', path, param, value} (value scalar, '->/ref/path', or a list for multi-valued "+"params), {op:'add', path:<parent>, container:'DefName:ShortName', values:{\u2026ECUC JSON\u2026}}, {op:'add', "+"path, param, value} (append a value), {op:'remove', path[, param[, value]]}. The engine picks the value element type, DEFINITION-REF/DEST and VALUE-REF DEST from the param defs, rejects out-of-range/non-literal values, patches only the changed bytes in the file each element lives in (UUIDs, order, comments preserved), applies all ops or none, and checks ARXML schema validity. Returns the unified diff and the validation of touched files. ALWAYS run ecuc_validate after editing and fix what it reports."+k,args:{ops:A(o.array(A(o.object({op:o.enum(["set","add","remove"]),path:o.string().describe("AUTOSAR path of the target (or parent for add-container)"),param:o.string().optional().describe("parameter/reference def short-name"),value:o.any().optional().describe("value: number | boolean | string | '->/path' | list"),container:o.string().optional().describe("for add: 'DefName:ShortName' (or 'Name' if equal)"),values:A(o.record(o.string(),o.any()).optional()).describe("for add: ECUC JSON of the new container")}))).min(1)).describe("list of ops (a JSON array; a JSON-encoded string is also accepted)"),dry_run:o.boolean().optional().describe("compute and show the diff without writing"),force:o.boolean().optional().describe("write values that fail def checks (avoid; explain why if used)"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=["edit","--ops",JSON.stringify(e.ops)];if(e.dry_run)s.push("--dry-run");if(e.force)s.push("--force");let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok){let h=n.changes??[];return S(n)+w(n)+(h.length?`
|
|
15
15
|
Rejected diff:
|
|
16
16
|
${h.map((f)=>f.diff).join(`
|
|
17
|
-
`)}`:"")}let t=n.changes??[],d=n.validation,l=[`${n.written?"Written":"Dry run (nothing written)"}: ${n.applied} op(s), ${t.length} file(s) changed.`,...(n.notes??[]).map((h)=>`NOTE: ${h}`),...t.map((h)=>h.diff)];if(d)l.push(d.validated?`Validation of touched files: ${JSON.stringify(d.summary)}`:"Validation skipped: no param defs loaded.",...
|
|
18
|
-
`)}}}),ecuc_validate:b({description:"Validate ECUC configuration against the param defs (project .hydron/autosar.json paramDefs, auto-discovered ECUC-MODULE-DEF files, or $AUTOSAR_PARAMDEFS): multiplicity, mandatory params/containers, integer/float ranges, enum literals, booleans, string length/regex, reference targets and destination types, dangling refs, duplicate paths, unknown definitions, plus semantic rule packs (H2L-*: Com signals inside their I-PDU and non-overlapping incl. Motorola bit order, unique handle IDs) that run even without defs. Returns LSP-like diagnostics "+"file:line severity rule path \u2014 message [def DEFINITION-REF]. Run after EVERY ecuc_edit and before "+"autosar_generate. `files` takes files, directories or globs; omit it to validate everything (preferred). The output always lists which files/modules were loaded; a filter that matches nothing is an ERROR. If it reports nothing on a project you expect to have issues, rerun unfiltered and check the loaded-file list."+
|
|
19
|
-
`)}}}),arxml_diff:b({description:"Semantic ARXML diff by AUTOSAR path between two files or directories (e.g. a git checkout of the old Config/ vs the current one, or Output/ before/after generation inputs). Ignores UUIDs, whitespace, comments, element order and file splits; ECUC values are compared per parameter. Returns added/removed/changed elements with the changed parameter values. Use it to review edits and PRs instead of a textual diff.",args:{a:o.string().describe("old: file, directory (recursive) or glob, relative to the project root or absolute"),b:o.string().describe("new: file, directory (recursive) or glob, relative to the project root or absolute"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,
|
|
20
|
-
`)}}}),autosar_config_lookup:b({description:"Look up one ARXML element in the H2Loop code graph by AUTOSAR path: the element (module, DEFINITION-REF, file:line, values), its direct children, references out and in (who points at it), and linked C symbols. Elements in the local checkout get a VALUES block with each value's VALUE-element file:line and integers in dec + hex. Use it to navigate config relations across the ingested repo (including files not in the local checkout); for local edits use ecuc_read/ecuc_edit."+
|
|
21
|
-
`)}}}),ecuc_find:b({description:"Structured query over the ECUC configuration \u2014 use it instead of grep/read on ARXML. Filter containers by "+`module, container_def (def short-name, e.g. PduRRoutingPath, ComSignal, NvMBlockDescriptor), where ("ComIPduDirection=SEND, ComBitSize>8"; =, !=, >, <, >=, <=, ~regex), ref_to (containers that reference this path directly or through up to 2 references, e.g. all routing paths whose destination is an EcuC Pdu), and referenced_by (e.g. all ComSignals referenced by I-PDU Y). Returns AUTOSAR paths, the container's file:line (at) and its values, each with "@at:<Param>" (file:line of the VALUE element) and, for integers, "@hex:<Param>".`+
|
|
17
|
+
`)}`:"")}let t=n.changes??[],d=n.validation,l=[`${n.written?"Written":"Dry run (nothing written)"}: ${n.applied} op(s), ${t.length} file(s) changed.`,...(n.notes??[]).map((h)=>`NOTE: ${h}`),...t.map((h)=>h.diff)];if(d)l.push(d.validated?`Validation of touched files: ${JSON.stringify(d.summary)}`:"Validation skipped: no param defs loaded.",...x(d.diagnostics,50));return{title:`ecuc_edit ${t.map((h)=>h.file).join(", ")}`,output:S(n)+l.join(`
|
|
18
|
+
`)}}}),ecuc_validate:b({description:"Validate ECUC configuration against the param defs (project .hydron/autosar.json paramDefs, auto-discovered ECUC-MODULE-DEF files, or $AUTOSAR_PARAMDEFS): multiplicity, mandatory params/containers, integer/float ranges, enum literals, booleans, string length/regex, reference targets and destination types, dangling refs, duplicate paths, unknown definitions, plus semantic rule packs (H2L-*: Com signals inside their I-PDU and non-overlapping incl. Motorola bit order, unique handle IDs) that run even without defs. Returns LSP-like diagnostics "+"file:line severity rule path \u2014 message [def DEFINITION-REF]. Run after EVERY ecuc_edit and before "+"autosar_generate. `files` takes files, directories or globs; omit it to validate everything (preferred). The output always lists which files/modules were loaded; a filter that matches nothing is an ERROR. If it reports nothing on a project you expect to have issues, rerun unfiltered and check the loaded-file list."+k,args:{files:R().describe("only report diagnostics in these: files, directories (recursive, trailing slash optional) or globs (`**/Com*.arxml`), relative to the project root or absolute. A filter that matches no loaded file is reported as an ERROR (H2L-FILTER-NOMATCH), never as clean"),module:o.string().optional().describe("only validate this module (e.g. Com); an unknown module is an ERROR, not clean"),max:o.number().int().min(1).optional().describe("cap the diagnostics listed (default 200)"),checkGenerated:o.boolean().optional().describe("regenerate in a scratch copy and report stale generated files (H2L-GEN-001). Default: on when a generate command is configured and the configuration is < 5 MB"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=["validate"];for(let u of v(e.files)??[])n.push("--file",u);if(e.module)n.push("--module",e.module);if(e.checkGenerated===!0)n.push("--check-generated");if(e.checkGenerated===!1)n.push("--no-check-generated");let t=await y(s,n,a.abort);if(!t.ok)return S(t)+w(t);let{diagnostics:d,scope:l}=t,h=d.filter((u)=>u.rule!=="H2L-DISCOVERY"),f=t.validated?`Validated ${t.modules.join(", ")||"(no modules)"} against ${t.defsLoaded} module def(s): ${h.length?JSON.stringify(t.summary):"no findings"}`:`Param definitions NOT loaded: only the H2L-* semantic rules ran. ${h.length?JSON.stringify(t.summary):"no findings"}`,p=[`Loaded ${l.configFiles} config file(s)`+(l.filter?`; files filter ${JSON.stringify(l.filter.files)} matched ${l.filter.matchedCount}`:"")+(l.module?`; module filter ${l.module}`:"")+":",...l.files.slice(0,30).map((u)=>` ${u.file} (${u.modules.join(", ")})`),...l.files.length>30||l.filesTruncated?[` \u2026 ${l.files.length-30+(l.filesTruncated??0)} more`]:[]];return{title:`ecuc_validate ${JSON.stringify(t.summary)}`,output:S(t)+[f,...x(d,e.max??200),...p].join(`
|
|
19
|
+
`)}}}),arxml_diff:b({description:"Semantic ARXML diff by AUTOSAR path between two files or directories (e.g. a git checkout of the old Config/ vs the current one, or Output/ before/after generation inputs). Ignores UUIDs, whitespace, comments, element order and file splits; ECUC values are compared per parameter. Returns added/removed/changed elements with the changed parameter values. Use it to review edits and PRs instead of a textual diff.",args:{a:o.string().describe("old: file, directory (recursive) or glob, relative to the project root or absolute"),b:o.string().describe("new: file, directory (recursive) or glob, relative to the project root or absolute"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=await y(s,["diff",e.a,e.b],a.abort);if(!n.ok)return w(n);let{ok:t,engine:d,ms:l,...h}=n;return JSON.stringify(h,null,1)}}),autosar_trace:b({description:"Trace AUTOSAR configuration to code through the H2Loop code graph of the selected project: query with an AUTOSAR path, a SHORT-NAME, or a C symbol (reverse lookup). Returns the ARXML element (path, module, DEFINITION-REF, file:line, values), the C symbols it configures (generated macros/const tables via the symbolic-name rule or token/value matching, with method + score) and their callers in in-house code. For a ComSignal it also shows the owning I-PDU and its ComIPduDirection (direction lives on the I-PDU). Elements in the local checkout get a VALUES block: each value with the file:line of its VALUE element and integers in dec + hex (the graph's file:line is only where the element starts). Use for impact analysis ('what code changes if I change X?') and 'where does this symbol come from?'. Requires the project's code to be ingested."+k,args:{query:o.string().describe("AUTOSAR path, SHORT-NAME, or C symbol"),data_source_id:o.number().int().optional().describe("restrict to one code data source"),limit:o.number().int().min(1).max(100).optional().describe("max matches (default server-side)"),workspace_id:o.union([o.string(),o.number()]).optional().describe("H2Loop project id or name (default: the project selected with `hydron projects select`)"),project_dir:o.string().optional().describe("local project root used to add I-PDU/direction for signals (default: session directory)")},async execute(e,a){let s=await C(e.workspace_id,a.abort),n={query:e.query};if(e.data_source_id!==void 0)n.data_source_id=e.data_source_id;if(e.limit!==void 0)n.limit=e.limit;let t=await O("/api/v1/mcp/autosar/trace",s,n,a.abort),d=t.matches??[],l=g(a.directory,e.project_dir),h=d.map((_)=>_.arxml).filter((_)=>!!_),[f,p]=await Promise.all([X(l,h,a.abort),V(l,h,a.abort)]),u=[`${d.length} match(es) for "${e.query}" in workspace ${s}`+(t.data_sources_queried!==void 0?` (data sources queried: ${JSON.stringify(t.data_sources_queried)})`:""),...Y(t),...f,...p,JSON.stringify(d,null,1)];if(!d.length)u.push("No match: try a SHORT-NAME, a full AUTOSAR path from ecuc_read, or the C symbol.");return{title:`autosar_trace ${e.query}`,output:u.join(`
|
|
20
|
+
`)}}}),autosar_config_lookup:b({description:"Look up one ARXML element in the H2Loop code graph by AUTOSAR path: the element (module, DEFINITION-REF, file:line, values), its direct children, references out and in (who points at it), and linked C symbols. Elements in the local checkout get a VALUES block with each value's VALUE-element file:line and integers in dec + hex. Use it to navigate config relations across the ingested repo (including files not in the local checkout); for local edits use ecuc_read/ecuc_edit."+k,args:{path:o.string().describe("AUTOSAR path, e.g. /Pkg/Com/ComConfig/MySignal"),data_source_id:o.number().int().optional().describe("restrict to one code data source"),workspace_id:o.union([o.string(),o.number()]).optional().describe("H2Loop project id or name (default: the project selected with `hydron projects select`)"),project_dir:o.string().optional().describe("local project root used to add I-PDU/direction for signals (default: session directory)")},async execute(e,a){let s=await C(e.workspace_id,a.abort),n={path:e.path};if(e.data_source_id!==void 0)n.data_source_id=e.data_source_id;let t=await O("/api/v1/mcp/autosar/config-lookup",s,n,a.abort),d=t.matches??[],l=g(a.directory,e.project_dir),h=d.map((u)=>u.element).filter((u)=>!!u),[f,p]=await Promise.all([X(l,h,a.abort),V(l,h,a.abort)]);return{title:`autosar_config_lookup ${e.path}`,output:[`${d.length} match(es) for ${e.path} in workspace ${s}`,...Y(t),...f,...p,JSON.stringify(d,null,1)].join(`
|
|
21
|
+
`)}}}),ecuc_find:b({description:"Structured query over the ECUC configuration \u2014 use it instead of grep/read on ARXML. Filter containers by "+`module, container_def (def short-name, e.g. PduRRoutingPath, ComSignal, NvMBlockDescriptor), where ("ComIPduDirection=SEND, ComBitSize>8"; =, !=, >, <, >=, <=, ~regex), ref_to (containers that reference this path directly or through up to 2 references, e.g. all routing paths whose destination is an EcuC Pdu), and referenced_by (e.g. all ComSignals referenced by I-PDU Y). Returns AUTOSAR paths, the container's file:line (at) and its values, each with "@at:<Param>" (file:line of the VALUE element) and, for integers, "@hex:<Param>".`+k,args:{module:o.string().optional(),container_def:o.string().optional().describe("container definition short-name"),where:o.union([o.string(),o.record(o.string(),o.any())]).optional().describe("conditions on parameter values"),ref_to:o.string().optional().describe("AUTOSAR path / SHORT-NAME that the containers must reference"),referenced_by:o.string().optional().describe("container whose references define the result set"),limit:o.number().int().min(1).max(500).optional(),project_dir:o.string().optional()},async execute(e,a){let s=["find"];if(e.module)s.push("--module",e.module);if(e.container_def)s.push("--container-def",e.container_def);if(e.where)s.push("--where",typeof e.where==="string"?e.where:JSON.stringify(e.where));if(e.ref_to)s.push("--ref-to",e.ref_to);if(e.referenced_by)s.push("--referenced-by",e.referenced_by);s.push("--limit",String(e.limit??50));let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok)return S(n)+w(n);let{ok:t,engine:d,ms:l,warnings:h,...f}=n;return S(n)+JSON.stringify(f,null,1)}}),autosar_spec_id_check:b({description:"Check AUTOSAR requirement IDs (SWS_/SRS_/TPS_/ECUC_/RS_ \u2026, e.g. SWS_Com_00197, 'SWS_NvM_00898/00899') "+"against the project's own AUTOSAR spec texts (.hydron/autosar.json `specDocs` globs of .txt/.md exports, or $AUTOSAR_SPEC_DOCS). Returns per ID: DEFINED (with document, line and the requirement text), MENTIONED-ONLY (occurs only in trace tables / running text), NOT-FOUND or MALFORMED. Run it BEFORE citing any ID that did not come verbatim from a tool result; never cite an ID it does not report DEFINED, and describe the rule with the returned text, not from memory.",args:{ids:R().describe("requirement IDs to check (a list, a JSON-encoded list, or one ID)"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=v(e.ids)??[];if(!s.length)return'ERROR: pass ids, e.g. ["SWS_Com_00197"]';let n=await y(g(a.directory,e.project_dir),["spec-ids",...s],a.abort);if(!n.ok)return w(n);let t=n.results;return{title:`autosar_spec_id_check ${JSON.stringify(n.counts)}`,output:[`Checked ${t.length} ID(s) against ${n.docs} spec text file(s) (${n.docsOrigin}; ${n.indexedIds} IDs indexed): ${JSON.stringify(n.counts)}`,...t.map((d)=>`${d.id} ${d.status}`+(d.doc?` \u2014 ${d.doc}:${d.line}`:"")+(d.didYouMean?` (did you mean ${d.didYouMean}?)`:"")+(d.text?`
|
|
22
22
|
${d.text}`:"")),t.some((d)=>d.status!=="DEFINED")?"Do not cite IDs that are not DEFINED; state the rule without an ID instead.":""].filter(Boolean).join(`
|
|
23
|
-
`)}}}),ecuc_crosscheck:b({description:"Per-module cross-check table: runs the semantic rule packs for one module (Com, PduR, EcuC, NvM, Fee, Ea, "+"CanIf, Dem, ...) \u2014 config vs System/ECU extract (H2L-SYS), gateway routing vs extract (H2L-PDUR), NvM/Fee "+"length vs the C RAM object's sizeof (H2L-NVM), memory capacity (H2L-MEM), signal packing and handle IDs \u2014 "+"and returns, per element, the findings, plus notes on what could NOT be checked. Run it for every module present after ecuc_validate; a clean validate alone is not proof of a correct configuration."+
|
|
23
|
+
`)}}}),ecuc_crosscheck:b({description:"Per-module cross-check table: runs the semantic rule packs for one module (Com, PduR, EcuC, NvM, Fee, Ea, "+"CanIf, Dem, ...) \u2014 config vs System/ECU extract (H2L-SYS), gateway routing vs extract (H2L-PDUR), NvM/Fee "+"length vs the C RAM object's sizeof (H2L-NVM), memory capacity (H2L-MEM), signal packing and handle IDs \u2014 "+"and returns, per element, the findings, plus notes on what could NOT be checked. Run it for every module present after ecuc_validate; a clean validate alone is not proof of a correct configuration."+k,args:{module:o.string().describe("module name, e.g. PduR"),project_dir:o.string().optional()},async execute(e,a){let s=await y(g(a.directory,e.project_dir),["crosscheck",e.module],a.abort);if(!s.ok)return S(s)+w(s);let n=s.rows,t=s.otherFindings??[];return{title:`ecuc_crosscheck ${e.module}: ${s.summary}`,output:[`${e.module}: ${s.summary} (element kinds: ${s.elementKinds.join(", ")||"none"})`,...n.map((d)=>`${d.element} (${d.at})
|
|
24
24
|
${d.findings.join(`
|
|
25
|
-
`)}`),...
|
|
26
|
-
`)}}}),
|
|
25
|
+
`)}`),...x(t,50),...(s.notes??[]).map((d)=>`NOTE: ${d}`)].join(`
|
|
26
|
+
`)}}}),arxml_new:b({description:"Create a NEW, empty ARXML file (System/ECU extract, SWC, interfaces, data types): the R25-11 header (AUTOSAR_00054) and the AR-PACKAGE tree you name, nothing else, strictly checked. This is the only way to start an .arxml file -- writing one by hand is blocked. Then add the elements with arxml_edit (parent_path = one of these packages). Never overwrites an existing file; use arxml_edit to extend existing ones.",args:{file:o.string().describe("project-relative path ending in .arxml, e.g. Input/SWC_DoorMonitor.arxml"),packages:R().describe('AUTOSAR package paths to create, e.g. ["/Swcs", "/Interfaces", "/DataTypes"]'),schema:o.string().optional().describe("AUTOSAR_000NN; default AUTOSAR_00054 (R25-11). Only to match an older project"),dry_run:o.boolean().optional(),project_dir:o.string().optional()},async execute(e,a){let s=["arxml-new","--file",e.file];for(let t of v(e.packages)??[])s.push("--package",t);if(e.schema)s.push("--schema",e.schema);if(e.dry_run)s.push("--dry-run");let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok)return w(n);return`${n.written?"Created":"Dry run"} ${n.file} (${n.schema}) with packages ${n.packages.join(", ")}.
|
|
27
|
+
Next: arxml_edit ops with parent_path = one of these packages (missing sub-packages are created for you), then arxml_check.`}}),arxml_check:b({description:"VERIFY step for non-ECUC ARXML (System/ECU extract, SWC, interfaces): strict AUTOSAR schema load of each file, every *-REF/*-TREF resolves to an element in the project, and its DEST is that element's real tag. Run it after every arxml_new/arxml_edit change and before you say a file is done; 'clean' is the only acceptable end state. ECUC values are verified with ecuc_validate instead.",args:{files:R().optional().describe("limit to files whose project path contains these strings (default: all non-ECUC ARXML)"),project_dir:o.string().optional()},async execute(e,a){let s=["arxml-check"];for(let l of v(e.files)??[])s.push("--file",l);let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok)return w(n);let t=n.problems??[],d=n.files.join(", ");if(!t.length)return`CLEAN: ${d} (${n.references} references checked, schema ${n.schemaChecked?"strict":"not available"})`;return`NOT CLEAN: ${t.length} problem(s) in ${d}
|
|
28
|
+
${t.slice(0,80).join(`
|
|
29
|
+
`)}`+(t.length>80?`
|
|
30
|
+
... ${t.length-80} more`:"")+`
|
|
31
|
+
Fix each with arxml_edit (set/remove/add), then run arxml_check again.`}}),arxml_edit:b({description:"Structured in-place edit of NON-ECUC ARXML (System/ECU extract, SWC, interfaces) \u2014 never write that XML by "+"hand; to start a new file use arxml_new. Ops: {op:'add', parent_path, element:'I-SIGNAL', short_name, values:{TAG: value | '->/ref/path' | {nested} | [list]}}, {op:'set', path, element:'START-POSITION', value}, {op:'remove', path}. Missing packages along parent_path are created (top level needs `file` when the project has several ARXML files). Every *-REF gets its DEST from the referenced element's real tag (targets must exist or be added earlier in the same call), elements are placed and ordered per the AUTOSAR schema, references are re-checked and the file is strictly re-loaded; nothing is written if anything fails. For ECUC values use ecuc_edit; to add a whole signal use autosar_add_signal.",args:{ops:A(o.array(A(o.record(o.string(),o.any()))).min(1)).describe("list of ops"),dry_run:o.boolean().optional(),project_dir:o.string().optional()},async execute(e,a){let s=["arxml-edit","--ops",JSON.stringify(N(e.ops))];if(e.dry_run)s.push("--dry-run");let n=await y(g(a.directory,e.project_dir),s,a.abort),t=n.changes??[];if(!n.ok)return w(n)+(n.problems?.length?`
|
|
27
32
|
${n.problems.join(`
|
|
28
33
|
`)}`:"")+(t.length?`
|
|
29
34
|
Rejected diff:
|
|
30
|
-
${t.map((
|
|
31
|
-
`)}`:"");return`${n.written?"Written":"Dry run"}; created ${JSON.stringify(n.created)}
|
|
32
|
-
${
|
|
35
|
+
${t.map((h)=>h.diff).join(`
|
|
36
|
+
`)}`:"");let d=n.notes??[],l=n.createdPackages??[];return`${n.written?"Written":"Dry run"}; created ${JSON.stringify(n.created)}`+(l.length?`; new packages ${l.join(", ")}`:"")+(d.length?`
|
|
37
|
+
NOTE: ${d.join(`
|
|
38
|
+
NOTE: `)}`:"")+`
|
|
39
|
+
${t.map((h)=>h.diff).join(`
|
|
33
40
|
`)}
|
|
34
|
-
Next: ecuc_validate.`}}),autosar_add_signal:b({description:"Add one Com signal end to end, consistently: ComSignal (ComHandleId = lowest unused; existing handles never move) + ComIPduSignalRef in the ECUC, SYSTEM-SIGNAL / I-SIGNAL / I-SIGNAL-TO-I-PDU-MAPPING and the rest of the signal chain (triggering, port, extract references) in the System extract, and the DBC SG_ line when a .dbc carries the frame. Everything is derived by following references from an existing signal of the same I-PDU. It refuses on overlap or out-of-bounds bits, picks the first free byte-aligned position when bit_position is omitted, runs ecuc_validate after writing and rolls back on any new finding. Then run autosar_generate, use ComConf_ComSignal_<name> in the C code, and autosar_build.",args:{name:o.string(),ipdu:o.string().describe("ComIPdu AUTOSAR path or SHORT-NAME"),bit_size:o.number().int().min(1).max(64).optional(),type:o.string().optional().describe("ComSignalType, e.g. UINT8 (default)"),bit_position:o.number().int().min(0).optional().describe("omit for the first free byte-aligned position"),endianness:o.enum(["LITTLE_ENDIAN","BIG_ENDIAN","OPAQUE"]).optional(),init:o.union([o.number(),o.string()]).optional(),transfer_property:o.string().optional().describe("e.g. PENDING, TRIGGERED, TRIGGERED_ON_CHANGE"),dry_run:o.boolean().optional(),project_dir:o.string().optional()},async execute(e,
|
|
35
|
-
${
|
|
36
|
-
`)}`:"");let{ok:t,engine:d,ms:l,...h}=n;return JSON.stringify(h,null,1)}}),autosar_audit:b({description:"Integration review \u2014 run this FIRST when reviewing C code that uses BSW services. Deterministically scans "+"C files (default: .hydron/autosar.json `audit` paths, else every .c in the project except generated files, "+"recognised by banner or *_Cfg/*_PBcfg/Rte* names \u2014 folder names do not matter) for Com_Send/ReceiveSignal[Group], "+"NvM_Read/WriteBlock/RestoreBlockDefaults, Dem_SetEventStatus/ReportErrorStatus, PduR_*Transmit and Rte_Write_/Rte_Read_ (resolved through the generated Output/ RTE headers), resolves every handle through the ECUC (symbolic name or raw number), and reports: H2L-API-001 raw numeric handle, -002 unknown handle, -003 direction mismatch (send on a RECEIVE I-PDU signal or receive on SEND), -004 C object width vs ComSignalType, -005 NvM NULL_PTR without a permanent RAM block (NULL_PTR WITH one is valid, SWS_NvM_00900). Each finding has file:line, evidence (configured values + AUTOSAR paths) and a spec citation; calls it cannot check are listed as unchecked, never guessed. Confirm findings with ecuc_read / autosar_trace.",args:{paths:
|
|
41
|
+
Next: arxml_check (and ecuc_validate if ECUC changed).`}}),autosar_add_signal:b({description:"Add one Com signal end to end, consistently: ComSignal (ComHandleId = lowest unused; existing handles never move) + ComIPduSignalRef in the ECUC, SYSTEM-SIGNAL / I-SIGNAL / I-SIGNAL-TO-I-PDU-MAPPING and the rest of the signal chain (triggering, port, extract references) in the System extract, and the DBC SG_ line when a .dbc carries the frame. Everything is derived by following references from an existing signal of the same I-PDU. It refuses on overlap or out-of-bounds bits, picks the first free byte-aligned position when bit_position is omitted, runs ecuc_validate after writing and rolls back on any new finding. Then run autosar_generate, use ComConf_ComSignal_<name> in the C code, and autosar_build.",args:{name:o.string(),ipdu:o.string().describe("ComIPdu AUTOSAR path or SHORT-NAME"),bit_size:o.number().int().min(1).max(64).optional(),type:o.string().optional().describe("ComSignalType, e.g. UINT8 (default)"),bit_position:o.number().int().min(0).optional().describe("omit for the first free byte-aligned position"),endianness:o.enum(["LITTLE_ENDIAN","BIG_ENDIAN","OPAQUE"]).optional(),init:o.union([o.number(),o.string()]).optional(),transfer_property:o.string().optional().describe("e.g. PENDING, TRIGGERED, TRIGGERED_ON_CHANGE"),dry_run:o.boolean().optional(),project_dir:o.string().optional()},async execute(e,a){let s=["add-signal","--name",e.name,"--ipdu",e.ipdu,"--type",e.type??"UINT8"];if(e.bit_size!==void 0)s.push("--bit-size",String(e.bit_size));if(e.bit_position!==void 0)s.push("--bit-position",String(e.bit_position));if(e.endianness)s.push("--endianness",e.endianness);if(e.init!==void 0)s.push("--init",String(e.init));if(e.transfer_property)s.push("--transfer-property",e.transfer_property);if(e.dry_run)s.push("--dry-run");let n=await y(g(a.directory,e.project_dir),s,a.abort);if(!n.ok)return w(n)+(n.diagnostics?.length?`
|
|
42
|
+
${x(n.diagnostics,20).join(`
|
|
43
|
+
`)}`:"");let{ok:t,engine:d,ms:l,...h}=n;return JSON.stringify(h,null,1)}}),autosar_audit:b({description:"Integration review \u2014 run this FIRST when reviewing C code that uses BSW services. Deterministically scans "+"C files (default: .hydron/autosar.json `audit` paths, else every .c in the project except generated files, "+"recognised by banner or *_Cfg/*_PBcfg/Rte* names \u2014 folder names do not matter) for Com_Send/ReceiveSignal[Group], "+"NvM_Read/WriteBlock/RestoreBlockDefaults, Dem_SetEventStatus/ReportErrorStatus, PduR_*Transmit and Rte_Write_/Rte_Read_ (resolved through the generated Output/ RTE headers), resolves every handle through the ECUC (symbolic name or raw number), and reports: H2L-API-001 raw numeric handle, -002 unknown handle, -003 direction mismatch (send on a RECEIVE I-PDU signal or receive on SEND), -004 C object width vs ComSignalType, -005 NvM NULL_PTR without a permanent RAM block (NULL_PTR WITH one is valid, SWS_NvM_00900). Each finding has file:line, evidence (configured values + AUTOSAR paths) and a spec citation; calls it cannot check are listed as unchecked, never guessed. Confirm findings with ecuc_read / autosar_trace.",args:{paths:R().describe("C files, directories or globs to scan (default: .hydron/autosar.json audit paths, else every .c under the project except generator output); matching nothing is an ERROR"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=await y(s,["audit",...v(e.paths)??[]],a.abort);if(!n.ok)return S(n)+w(n);let t=(n.notes??[]).map((u)=>`NOTE: ${u}`),{findings:d,unchecked:l,scanned:h}=n,f=[`Scanned ${h.sources.length} C file(s), ${h.calls} BSW call(s): ${d.length?JSON.stringify(n.summary):"no findings"}`,...d.map((u)=>`${u.file}:${u.line} ${u.severity.toUpperCase()} ${u.rule} ${u.call}
|
|
37
44
|
${u.message}
|
|
38
45
|
evidence: ${JSON.stringify(u.evidence)}
|
|
39
46
|
spec: ${u.cite}${u.fix?`
|
|
40
47
|
fix: ${u.fix}`:""}`)];if(l.length)f.push(`Unchecked (${l.length}):`,...l.slice(0,50).map((u)=>` ${u.file}:${u.line} ${u.call} \u2014 ${u.reason}`));let p=n.unreferenced??{};for(let[u,_]of Object.entries(p))f.push(`INFO configured but never referenced in the scanned code \u2014 ${u}: ${_.count} (e.g. ${_.sample.slice(0,5).join(", ")})`);return f.push(...t),{title:`autosar_audit ${JSON.stringify(n.summary)}`,output:S(n)+f.join(`
|
|
41
|
-
`)}}}),autosar_verify_rte:b({description:"Verify Rte_* calls in C against the SW-C description (SWS_RTE naming, R25-11). Each call is mapped to its enclosing function, then to every runnable whose entry point (RUNNABLE-ENTITY SYMBOL) reaches it, and classified: OK (the runnable has the access point), NOT-ALLOWED (the API exists for the SW-C but this runnable has no dataSendPoint/dataReceivePoint/serverCallPoint/... for it), INVENTED (no such port, element, runnable, or wrong direction/semantics). For NOT-ALLOWED/INVENTED it returns ready-to-run arxml_edit ops (dry-run checked) that add the access point / port / data element. Run it after writing runnable code and never finish with a failing verify.",args:{paths:
|
|
42
|
-
`),metadata:{counts:l}}}}),autosar_build_context:b({description:"Assemble the context for implementing or changing a runnable, deterministically (same inputs -> byte-identical text) in a fixed order: 1 task, 2 SW-C definition (ports, interfaces, types, runnables, events), 3 ALLOWED RTE API for the runnable with the exact declarations from Rte_<Swc>.h (else SWS_RTE signatures), 4 the runnable's current C body, 5 functions it calls, 6 related config (Com signals / NvM blocks behind its ports), 7 FUZZY similar functions in other SW-Cs, 8 FUZZY similar past git changes (ARXML delta + C hunks). Fits budget_tokens (~4 chars/token) by dropping 8, 7, then shortening bodies; never the allowed API. Returns the text plus a JSON manifest of every source included. Call it FIRST for any runnable task and use only the allowed APIs it lists.",args:{swc:o.string().describe("SW-C short name or AUTOSAR path"),runnable:o.string().optional().describe("runnable short name or SYMBOL (default: all runnables)"),task:o.string().describe("the task, verbatim"),budget_tokens:o.number().int().min(500).max(200000).optional().describe("token budget (default 24000)"),use_backend:o.boolean().optional().describe("if the runnable has no local C definition, look it up with the backend trace of the selected workspace"),workspace_id:o.union([o.string(),o.number()]).optional().describe("H2Loop project id or name for use_backend"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,
|
|
48
|
+
`)}}}),autosar_verify_rte:b({description:"Verify Rte_* calls in C against the SW-C description (SWS_RTE naming, R25-11). Each call is mapped to its enclosing function, then to every runnable whose entry point (RUNNABLE-ENTITY SYMBOL) reaches it, and classified: OK (the runnable has the access point), NOT-ALLOWED (the API exists for the SW-C but this runnable has no dataSendPoint/dataReceivePoint/serverCallPoint/... for it), INVENTED (no such port, element, runnable, or wrong direction/semantics). For NOT-ALLOWED/INVENTED it returns ready-to-run arxml_edit ops (dry-run checked) that add the access point / port / data element. Run it after writing runnable code and never finish with a failing verify.",args:{paths:R().describe("C files, directories or globs (default: all in-house C)"),diff_against:o.string().optional().describe("git ref: only calls on lines changed since this ref (plus untracked files)"),swc:o.string().optional().describe("restrict to one SW-C (short name or AUTOSAR path)"),runnable:o.string().optional().describe("restrict to one runnable (short name or SYMBOL)"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=["verify-rte",...v(e.paths)??[]];if(e.diff_against)n.push("--diff-against",e.diff_against);if(e.swc)n.push("--swc",e.swc);if(e.runnable)n.push("--runnable",e.runnable);let t=await y(s,n,a.abort);if(!t.ok)return w(t);let{findings:d,counts:l}=t,h=d.filter((m)=>m.status!=="OK"),f=t.scanned,p=[`Verified ${f.calls} Rte_* call(s) in ${f.files.length} file(s) against SW-C(s) ${t.swcs.join(", ")}: OK ${l.OK}, NOT-ALLOWED ${l["NOT-ALLOWED"]}, INVENTED ${l.INVENTED}`+(h.length?" \u2014 FAILING":" \u2014 PASS")];for(let m of h){if(p.push(`${m.at} ${m.status} ${m.call} in ${m.function}() [${m.swc??"?"}${m.runnable?`/${m.runnable}`:""}]`,` ${m.why}`),m.expected)p.push(` expected: ${m.expected} [${m.cite}]`);if(m.hint)p.push(` hint: ${m.hint}`);if(m.arxml_edit?.length)p.push(` fix (arxml_edit ops${m.opsChecked===!0?", dry-run OK":m.opsChecked?`, dry-run FAILED: ${m.opsChecked}`:""}): ${JSON.stringify(m.arxml_edit)}`)}let u=t.notChecked??[];if(u.length)p.push(`Not checked (${u.length}): ${u.map((m)=>`${m.call} @ ${m.at}`).join(", ")}`);let _=t.runnablesWithoutCode??[];if(_.length)p.push(`Runnables without a C definition: ${_.map((m)=>`${m.swc}.${m.runnable} (${m.symbol})`).join(", ")}`);for(let m of t.notes??[])p.push(`NOTE: ${m}`);if(h.length)p.push("Next: rewrite the call with an allowed API (autosar_build_context lists them), or \u2014 only if the behaviour "+"truly needs new data \u2014 apply the ops with arxml_edit, run autosar_generate, then autosar_verify_rte again.");return{title:`autosar_verify_rte ${h.length?"FAIL":"PASS"} ${JSON.stringify(l)}`,output:p.join(`
|
|
49
|
+
`),metadata:{counts:l}}}}),autosar_build_context:b({description:"Assemble the context for implementing or changing a runnable, deterministically (same inputs -> byte-identical text) in a fixed order: 1 task, 2 SW-C definition (ports, interfaces, types, runnables, events), 3 ALLOWED RTE API for the runnable with the exact declarations from Rte_<Swc>.h (else SWS_RTE signatures), 4 the runnable's current C body, 5 functions it calls, 6 related config (Com signals / NvM blocks behind its ports), 7 FUZZY similar functions in other SW-Cs, 8 FUZZY similar past git changes (ARXML delta + C hunks). Fits budget_tokens (~4 chars/token) by dropping 8, 7, then shortening bodies; never the allowed API. Returns the text plus a JSON manifest of every source included. Call it FIRST for any runnable task and use only the allowed APIs it lists.",args:{swc:o.string().describe("SW-C short name or AUTOSAR path"),runnable:o.string().optional().describe("runnable short name or SYMBOL (default: all runnables)"),task:o.string().describe("the task, verbatim"),budget_tokens:o.number().int().min(500).max(200000).optional().describe("token budget (default 24000)"),use_backend:o.boolean().optional().describe("if the runnable has no local C definition, look it up with the backend trace of the selected workspace"),workspace_id:o.union([o.string(),o.number()]).optional().describe("H2Loop project id or name for use_backend"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=["build-context","--swc",e.swc,"--task",e.task,"--budget-tokens",String(e.budget_tokens??24000)];if(e.runnable)n.push("--runnable",e.runnable);let t=await y(s,n,a.abort);if(!t.ok)return w(t);let d=t.text,l=t.codeMissing??[];if(l.length&&e.use_backend){let f=[];try{let p=await C(e.workspace_id,a.abort);for(let u of l){let _=await O("/api/v1/mcp/autosar/trace",p,{query:u,limit:3},a.abort);f.push(`### backend trace for ${u} (workspace ${p})
|
|
43
50
|
${JSON.stringify(_.matches??[],null,1).slice(0,6000)}`)}}catch(p){f.push(`(backend lookup unavailable: ${p instanceof Error?p.message:String(p)})`)}d+=`
|
|
44
51
|
## 4b. RUNNABLE CODE FROM THE BACKEND (not part of the deterministic context)
|
|
45
52
|
${f.join(`
|
|
46
53
|
`)}
|
|
47
54
|
`}let h={swc:t.swc,runnables:t.runnables,estimatedTokens:t.estimatedTokens,budgetTokens:t.budgetTokens,reductions:t.reductions,overBudget:t.overBudget??!1,sha1:t.sha1,sections:t.sections,sources:t.manifest};return{title:`autosar_build_context ${t.swc}${t.runnables.length===1?`/${t.runnables[0]}`:""} ~${t.estimatedTokens} tok`,output:`${d}
|
|
48
55
|
---
|
|
49
|
-
MANIFEST ${JSON.stringify(h)}`,metadata:{sha1:t.sha1,estimatedTokens:t.estimatedTokens}}}}),autosar_codegen:b({description:"Deterministic BSW configuration code generation from the ECUC ARXML + param defs (no LLM output: the same ARXML always gives the same C). Modules: NvM, MemIf, Fee, Ea, Dem, PduR, CanIf, Port, Dio (any other configured module can be named; it then gets the generic def-driven emission only). Emits <Mip>_Cfg.h (pre-compile #defines, STD_ON/STD_OFF, <MIP>_CFG_* version macros, TPS_ECUC_02108 symbols <Mip>Conf_<ContainerDef>_<Name>), <Mip>_Cfg.c / _Lcfg.c / _PBcfg.c by each parameter's config class, const tables for multiplicity-n containers, MemMap-wrapped, SchM_<Mip>.h and <Mip>_MemMap.h. Static-code struct layouts (the SIP's NvM_BlockDescriptorType, PduR_RoutingPathType, ...) come from a binding selected by the project's headers. Missing handle IDs are assigned deterministically and written back into the ECUC ARXML in place (TPS_ECUC_02107) unless write_back=false. Writes to the scratch dir .hydron/codegen/out unless out_dir is given ('project' = the project's Output dir). Follow with autosar_codegen_diff against the existing Output/, then autosar_build.",args:{modules:
|
|
56
|
+
MANIFEST ${JSON.stringify(h)}`,metadata:{sha1:t.sha1,estimatedTokens:t.estimatedTokens}}}}),autosar_codegen:b({description:"Deterministic BSW configuration code generation from the ECUC ARXML + param defs (no LLM output: the same ARXML always gives the same C). Modules: NvM, MemIf, Fee, Ea, Dem, PduR, CanIf, Port, Dio (any other configured module can be named; it then gets the generic def-driven emission only). Emits <Mip>_Cfg.h (pre-compile #defines, STD_ON/STD_OFF, <MIP>_CFG_* version macros, TPS_ECUC_02108 symbols <Mip>Conf_<ContainerDef>_<Name>), <Mip>_Cfg.c / _Lcfg.c / _PBcfg.c by each parameter's config class, const tables for multiplicity-n containers, MemMap-wrapped, SchM_<Mip>.h and <Mip>_MemMap.h. Static-code struct layouts (the SIP's NvM_BlockDescriptorType, PduR_RoutingPathType, ...) come from a binding selected by the project's headers. Missing handle IDs are assigned deterministically and written back into the ECUC ARXML in place (TPS_ECUC_02107) unless write_back=false. Writes to the scratch dir .hydron/codegen/out unless out_dir is given ('project' = the project's Output dir). Follow with autosar_codegen_diff against the existing Output/, then autosar_build.",args:{modules:R().describe("modules to generate (default: every supported module that is configured)"),out_dir:o.string().optional().describe("output dir, relative to the project or absolute; 'project' = codegen.outDir / Output. Default: scratch .hydron/codegen/out"),write_back:o.boolean().optional().describe("write assigned handle IDs into the ECUC ARXML (default true)"),binding:o.string().optional().describe("force a static-code binding (e.g. 'sip-standin'), or 'none' for generic tables only"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=["codegen"];for(let p of v(e.modules)??[])n.push("--module",p);if(e.out_dir)n.push("--out",e.out_dir);if(e.write_back===!1)n.push("--no-write-back");if(e.binding)n.push("--binding",e.binding);let t=await y(s,n,a.abort);if(!t.ok){let p=t.handleErrors??[];return S(t)+w(t)+(p.length?`
|
|
50
57
|
${p.map((u)=>` ${u}`).join(`
|
|
51
58
|
`)}`:"")}let{modules:d,handles:l,timings:h}=t,f=[`Generated ${Object.keys(d).length} module(s) into ${t.outDir} in ${h.total_ms} ms (AUTOSAR R${t.release}):`,...Object.entries(d).map(([p,u])=>` ${p} (${u.variant}${u.binding?`, binding ${u.binding}`:", generic tables"}): ${u.files.join(", ")}; ${u.symbols} symbol(s)`+u.notes.map((_)=>`
|
|
52
59
|
NOTE: ${_}`).join("")),l.assigned.length?`Assigned ${l.assigned.length} handle ID(s) (${l.writtenBack?"written back into the ECUC ARXML":"in memory only, NOT written back"}):
|
|
53
|
-
`+l.assigned.slice(0,40).map((p)=>` ${p.path}/${p.param} = ${p.value} (hex ${
|
|
60
|
+
`+l.assigned.slice(0,40).map((p)=>` ${p.path}/${p.param} = ${p.value} (hex ${q(p.value)})`).join(`
|
|
54
61
|
`)+(l.changes.length?`
|
|
55
62
|
${l.changes.map((p)=>p.diff).join(`
|
|
56
63
|
`)}`:""):"Handle IDs: all already present in the ECUC (nothing assigned).",...(t.notes??[]).map((p)=>`NOTE: ${p}`)];return{title:`autosar_codegen ${Object.keys(d).join(", ")}`,output:S(t)+f.join(`
|
|
57
|
-
`)}}}),autosar_codegen_diff:b({description:"Semantic diff of generated C configuration code against a reference dir (e.g. the vendor-generated Output/): compares #define names and values and the contents of every initialised table/struct element by element, plus table ordering, after normalising numbers (0x2A0u == 672), casts, parentheses and macro references. Ignores formatting, comments, banners and include order. Reports per module: matching symbols, missing (in reference only), extra (generated only), value mismatches with both values.",args:{reference_dir:o.string().describe("reference code dir, e.g. Output (relative to the project or absolute)"),generated_dir:o.string().optional().describe("generated code dir (default: the autosar_codegen scratch dir)"),modules:
|
|
58
|
-
`)}}}),autosar_generate:c("generate"),autosar_build:c("build")}),
|
|
64
|
+
`)}}}),autosar_codegen_diff:b({description:"Semantic diff of generated C configuration code against a reference dir (e.g. the vendor-generated Output/): compares #define names and values and the contents of every initialised table/struct element by element, plus table ordering, after normalising numbers (0x2A0u == 672), casts, parentheses and macro references. Ignores formatting, comments, banners and include order. Reports per module: matching symbols, missing (in reference only), extra (generated only), value mismatches with both values.",args:{reference_dir:o.string().describe("reference code dir, e.g. Output (relative to the project or absolute)"),generated_dir:o.string().optional().describe("generated code dir (default: the autosar_codegen scratch dir)"),modules:R().describe("only these modules"),project_dir:o.string().optional().describe("project root (default: session directory)")},async execute(e,a){let s=g(a.directory,e.project_dir),n=["codegen-diff",e.reference_dir];if(e.generated_dir)n.push(e.generated_dir);for(let h of v(e.modules)??[])n.push("--module",h);let t=await y(s,n,a.abort);if(!t.ok)return w(t);let d=t.modules,l=[`Semantic diff ${t.reference} (reference) vs ${t.generated} (generated): ${JSON.stringify(t.total)}`,`Not generated (reference only): ${t.notGenerated.join(", ")||"-"}; not in reference: ${t.notInReference.join(", ")||"-"}`];for(let[h,f]of Object.entries(d)){if(l.push(`== ${h}: ${f.summary.matching} matching, ${f.summary.missing} missing, ${f.summary.extra} extra, ${f.summary.mismatches} mismatch(es); table elements ${f.elements.match} equal / ${f.elements.mismatch} different`),f.defines.missing.length)l.push(` missing #define: ${f.defines.missing.slice(0,30).join(", ")}`);for(let p of f.defines.mismatch.slice(0,30))l.push(` #define ${p.name}: reference ${p.reference} | generated ${p.generated}`);if(f.objects.missing.length)l.push(` missing object: ${f.objects.missing.join(", ")}`);if(f.objects.extra.length)l.push(` extra object: ${f.objects.extra.join(", ")}`);for(let p of f.objects.order)l.push(` order differs: ${p.name} (same rows)`);for(let p of f.objects.mismatch)l.push(` ${p.name}: ${p.count} element(s) differ`,...p.elements.slice(0,12).map((u)=>` ${u.element}: reference ${u.reference} | generated ${u.generated}`));if(f.defines.extra.length)l.push(` extra #define (${f.defines.extra.length}): ${f.defines.extra.slice(0,12).join(", ")}${f.defines.extra.length>12?", \u2026":""}`)}return{title:`autosar_codegen_diff ${JSON.stringify(t.total)}`,output:l.join(`
|
|
65
|
+
`)}}}),autosar_generate:c("generate"),autosar_build:c("build")}),i)}},Re=ve;export{Re as Autosar};
|
|
@@ -7,6 +7,10 @@ Ops (paths are AUTOSAR paths of identifiables; tags are XML element names):
|
|
|
7
7
|
{"op": "set", "path": "/Communication/Pdus/P/X", "element": "START-POSITION", "value": 40}
|
|
8
8
|
{"op": "remove", "path": "/Communication/ISignals/X"}
|
|
9
9
|
|
|
10
|
+
- A parent_path that does not exist yet is created as AR-PACKAGEs below its deepest existing package (or at the
|
|
11
|
+
top level of ``file``, or of the only non-ECUC ARXML file), so a new file from arxml_new can be filled
|
|
12
|
+
directly. Only packages are created: a missing path below a non-package (a typo in a SWC path) is an error.
|
|
13
|
+
- The ecuc_edit shape is accepted for add ({"path": parent, "container": "TAG:Name"}) and rewritten, with a note.
|
|
10
14
|
- The aggregating collection (ELEMENTS, I-SIGNAL-TO-PDU-MAPPINGS, ...) is inferred from the AUTOSAR schema
|
|
11
15
|
(autosar-data) when not given; children are written in schema order.
|
|
12
16
|
- Every *-REF / *-TREF gets its DEST from the referenced element's actual tag ("->/path"), looked up in all
|
|
@@ -95,6 +99,7 @@ class ArxmlEditor:
|
|
|
95
99
|
self.files: dict = {}
|
|
96
100
|
self.original: dict = {}
|
|
97
101
|
self.added_tags: dict = {} # path -> tag of elements added in this call
|
|
102
|
+
self.created_packages: list = []
|
|
98
103
|
|
|
99
104
|
# ---------- files / lookup ----------
|
|
100
105
|
def tree_for(self, path: str) -> tuple:
|
|
@@ -115,6 +120,53 @@ class ArxmlEditor:
|
|
|
115
120
|
raise ArxmlEditError(f"{path} not found in {self.p.rel(f)}")
|
|
116
121
|
return ft, ft.by_path[path]
|
|
117
122
|
|
|
123
|
+
def root_of(self, file: Optional[str]) -> tuple:
|
|
124
|
+
"""(FileTree, AUTOSAR root node) for a top-level package: ``file`` or the only non-ECUC ARXML."""
|
|
125
|
+
if file:
|
|
126
|
+
f = os.path.realpath(os.path.join(self.p.root, file))
|
|
127
|
+
known = [os.path.realpath(x) for x in self.p.sources.input + self.p.sources.config]
|
|
128
|
+
if f not in known:
|
|
129
|
+
raise ArxmlEditError(f"file {file!r} is not an ARXML file of this project (create it with arxml_new)")
|
|
130
|
+
ft = self.load(next(x for x in self.p.sources.input + self.p.sources.config if os.path.realpath(x) == f))
|
|
131
|
+
return ft, ft.root
|
|
132
|
+
cands = list(self.p.sources.input)
|
|
133
|
+
if len(cands) == 1:
|
|
134
|
+
ft = self.load(cands[0])
|
|
135
|
+
return ft, ft.root
|
|
136
|
+
if not cands:
|
|
137
|
+
raise ArxmlEditError("no non-ECUC ARXML file in this project to put a new top-level package in; create "
|
|
138
|
+
"one with arxml_new first")
|
|
139
|
+
raise ArxmlEditError("a new top-level package needs \"file\" (which ARXML file to put it in); one of: "
|
|
140
|
+
+ ", ".join(self.p.rel(c) for c in cands[:20]))
|
|
141
|
+
|
|
142
|
+
def parent_for(self, parent_path: str, file: Optional[str]) -> tuple:
|
|
143
|
+
"""(FileTree, node) of parent_path, creating missing AR-PACKAGEs below the deepest existing package."""
|
|
144
|
+
pp = parent_path.rstrip("/")
|
|
145
|
+
if pp == "":
|
|
146
|
+
return self.root_of(file)
|
|
147
|
+
try:
|
|
148
|
+
return self.tree_for(pp)
|
|
149
|
+
except ArxmlEditError:
|
|
150
|
+
pass
|
|
151
|
+
segs = [s for s in pp.split("/") if s]
|
|
152
|
+
anc, k = "", 0
|
|
153
|
+
for i in range(len(segs) - 1, 0, -1):
|
|
154
|
+
cand = "/" + "/".join(segs[:i])
|
|
155
|
+
try:
|
|
156
|
+
_, node = self.tree_for(cand)
|
|
157
|
+
except ArxmlEditError:
|
|
158
|
+
continue
|
|
159
|
+
if node.tag != "AR-PACKAGE":
|
|
160
|
+
raise ArxmlEditError(f"{pp} does not exist, and {cand} is a {node.tag}, not a package (only missing "
|
|
161
|
+
"packages are created; check the path)")
|
|
162
|
+
anc, k = cand, i
|
|
163
|
+
break
|
|
164
|
+
for s in segs[k:]:
|
|
165
|
+
self.op_add({"parent_path": anc or "/", "element": "AR-PACKAGE", "short_name": s, "file": file})
|
|
166
|
+
anc = f"{anc}/{s}"
|
|
167
|
+
self.created_packages.append(anc)
|
|
168
|
+
return self.tree_for(pp)
|
|
169
|
+
|
|
118
170
|
def load(self, f: str) -> FileTree:
|
|
119
171
|
if f not in self.files:
|
|
120
172
|
with open(f, "rb") as fh:
|
|
@@ -208,8 +260,9 @@ class ArxmlEditor:
|
|
|
208
260
|
def op_add(self, op: dict):
|
|
209
261
|
parent_path, tag = op.get("parent_path"), op.get("element")
|
|
210
262
|
if not parent_path or not tag:
|
|
211
|
-
raise ArxmlEditError("add needs parent_path and element"
|
|
212
|
-
|
|
263
|
+
raise ArxmlEditError("add needs parent_path (the package or element to add under) and element (the XML "
|
|
264
|
+
"tag), e.g. " + ADD_EXAMPLE)
|
|
265
|
+
ft, parent = self.parent_for(parent_path, op.get("file"))
|
|
213
266
|
version = _version(ft.data)
|
|
214
267
|
ptype = _etype(ft.chain(parent), version)
|
|
215
268
|
coll = op.get("collection")
|
|
@@ -221,7 +274,8 @@ class ArxmlEditor:
|
|
|
221
274
|
coll = c
|
|
222
275
|
break
|
|
223
276
|
if coll is None:
|
|
224
|
-
raise ArxmlEditError(f"{tag} cannot be placed under {parent.tag} {parent_path} in this schema"
|
|
277
|
+
raise ArxmlEditError(f"{tag} cannot be placed under {parent.tag} {parent_path} in this schema; "
|
|
278
|
+
f"{parent.tag} takes: {', '.join(_placeable(ptype, version))[:600]}")
|
|
225
279
|
elif coll is None:
|
|
226
280
|
coll = "ELEMENTS" if parent.tag == "AR-PACKAGE" else None
|
|
227
281
|
sn = op.get("short_name")
|
|
@@ -342,13 +396,63 @@ class ArxmlEditor:
|
|
|
342
396
|
return problems
|
|
343
397
|
|
|
344
398
|
|
|
399
|
+
ADD_EXAMPLE = ('{"op": "add", "parent_path": "/Interfaces", "element": "SENDER-RECEIVER-INTERFACE", '
|
|
400
|
+
'"short_name": "If_X", "values": {"DATA-ELEMENTS": {"VARIABLE-DATA-PROTOTYPE": {"SHORT-NAME": "Val", '
|
|
401
|
+
'"TYPE-TREF": "->/DataTypes/uint8"}}}}')
|
|
402
|
+
_TAG = re.compile(r"[A-Z][A-Z0-9-]*\Z")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _placeable(ptype, version) -> list:
|
|
406
|
+
"""Tags that can be added under an element: direct children and the children of its collections."""
|
|
407
|
+
out = []
|
|
408
|
+
for c in _order(ptype):
|
|
409
|
+
if c == "SHORT-NAME":
|
|
410
|
+
continue
|
|
411
|
+
ct = ptype.find_sub_element(c, version)
|
|
412
|
+
subs = [s for s in _order(ct)] if ct is not None else []
|
|
413
|
+
out.append(f"{c} ({'/'.join(subs[:6])}{'/...' if len(subs) > 6 else ''})" if subs else c)
|
|
414
|
+
return out
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def normalize(op: dict, notes: list, i: int) -> dict:
|
|
418
|
+
"""Accept the shapes agents mix up (ecuc_edit's path/container, name/tag) for add; note each rewrite."""
|
|
419
|
+
if op.get("op") != "add":
|
|
420
|
+
return op
|
|
421
|
+
op = dict(op)
|
|
422
|
+
if not op.get("parent_path") and op.get("path"):
|
|
423
|
+
op["parent_path"] = op.pop("path")
|
|
424
|
+
notes.append(f"op {i}: 'path' read as parent_path")
|
|
425
|
+
cont = op.get("container")
|
|
426
|
+
if not op.get("element") and isinstance(cont, str):
|
|
427
|
+
tag, _, sn = cont.partition(":")
|
|
428
|
+
if _TAG.match(tag):
|
|
429
|
+
op.pop("container")
|
|
430
|
+
op["element"] = tag
|
|
431
|
+
if sn and not op.get("short_name"):
|
|
432
|
+
op["short_name"] = sn
|
|
433
|
+
notes.append(f"op {i}: container {cont!r} read as element {tag!r}" + (f", short_name {sn!r}" if sn else ""))
|
|
434
|
+
for alias, key in (("tag", "element"), ("name", "short_name"), ("shortName", "short_name")):
|
|
435
|
+
if alias in op and not op.get(key):
|
|
436
|
+
op[key] = op.pop(alias)
|
|
437
|
+
notes.append(f"op {i}: {alias!r} read as {key!r}")
|
|
438
|
+
vals = op.get("values")
|
|
439
|
+
if isinstance(vals, dict) and "SHORT-NAME" in vals and not op.get("short_name"):
|
|
440
|
+
vals = dict(vals)
|
|
441
|
+
op["short_name"] = vals.pop("SHORT-NAME")
|
|
442
|
+
op["values"] = vals
|
|
443
|
+
notes.append(f"op {i}: values.SHORT-NAME read as short_name")
|
|
444
|
+
return op
|
|
445
|
+
|
|
446
|
+
|
|
345
447
|
def arxml_edit(project: Project, ops, dry_run: bool = False) -> dict:
|
|
346
448
|
if isinstance(ops, dict):
|
|
347
449
|
ops = [ops]
|
|
348
450
|
if not isinstance(ops, list) or not ops or not all(isinstance(o, dict) for o in ops):
|
|
349
|
-
return {"ok": False, "error": "ops must be a non-empty list of op objects"}
|
|
451
|
+
return {"ok": False, "error": "ops must be a non-empty list of op objects, e.g. [" + ADD_EXAMPLE + "]"}
|
|
350
452
|
ed = ArxmlEditor(project)
|
|
351
453
|
created = []
|
|
454
|
+
notes: list = []
|
|
455
|
+
ops = [normalize(o, notes, i) for i, o in enumerate(ops)]
|
|
352
456
|
try:
|
|
353
457
|
for i, op in enumerate(ops):
|
|
354
458
|
kind = op.get("op")
|
|
@@ -364,7 +468,7 @@ def arxml_edit(project: Project, ops, dry_run: bool = False) -> dict:
|
|
|
364
468
|
except ArxmlEditError as e:
|
|
365
469
|
raise ArxmlEditError(f"op {i} ({kind} {op.get('path') or op.get('parent_path')}): {e}") from None
|
|
366
470
|
except ArxmlEditError as e:
|
|
367
|
-
return {"ok": False, "error": str(e), "written": False}
|
|
471
|
+
return {"ok": False, "error": str(e), "written": False, "notes": notes}
|
|
368
472
|
problems = ed.check()
|
|
369
473
|
changes = []
|
|
370
474
|
for f, ft in ed.files.items():
|
|
@@ -375,7 +479,7 @@ def arxml_edit(project: Project, ops, dry_run: bool = False) -> dict:
|
|
|
375
479
|
changes.append({"file": rel, "diff": "".join(difflib.unified_diff(a, b, f"a/{rel}", f"b/{rel}", n=1))})
|
|
376
480
|
if problems:
|
|
377
481
|
return {"ok": False, "error": "edit rejected; nothing written", "problems": problems, "changes": changes,
|
|
378
|
-
"written": False}
|
|
482
|
+
"written": False, "notes": notes}
|
|
379
483
|
if not dry_run:
|
|
380
484
|
for f, ft in ed.files.items():
|
|
381
485
|
if ft.data != ed.original[f]:
|
|
@@ -383,5 +487,57 @@ def arxml_edit(project: Project, ops, dry_run: bool = False) -> dict:
|
|
|
383
487
|
with open(tmp, "wb") as fh:
|
|
384
488
|
fh.write(ft.data)
|
|
385
489
|
os.replace(tmp, f)
|
|
386
|
-
return {"ok": True, "written": not dry_run, "created": [c for c in created if c],
|
|
490
|
+
return {"ok": True, "written": not dry_run, "created": [c for c in created if c],
|
|
491
|
+
"createdPackages": ed.created_packages, "notes": notes, "changes": changes,
|
|
387
492
|
"schemaChecked": _ad() is not None}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def arxml_check(project: Project, files: Optional[list] = None) -> dict:
|
|
496
|
+
"""Verify non-ECUC ARXML on demand: strict schema load, every *-REF resolves, DEST = the target's real tag.
|
|
497
|
+
|
|
498
|
+
The verify step of the loop for System-extract / SWC / interface files (ecuc_validate covers ECUC values).
|
|
499
|
+
``files``: project-relative paths or substrings to limit the check; default all non-ECUC files.
|
|
500
|
+
"""
|
|
501
|
+
from .edit import schema_check
|
|
502
|
+
srcs = list(project.sources.input)
|
|
503
|
+
if files:
|
|
504
|
+
want = [str(x).strip().lstrip("./") for x in files if str(x).strip()]
|
|
505
|
+
srcs = [f for f in srcs if any(w in project.rel(f) for w in want)]
|
|
506
|
+
if not srcs:
|
|
507
|
+
return {"ok": False, "error": f"no non-ECUC ARXML file matches {files}; files: "
|
|
508
|
+
+ ", ".join(project.rel(f) for f in project.sources.input[:30])}
|
|
509
|
+
trees = {}
|
|
510
|
+
for f in project.sources.input:
|
|
511
|
+
try:
|
|
512
|
+
with open(f, "rb") as fh:
|
|
513
|
+
trees[f] = FileTree(f, fh.read())
|
|
514
|
+
except Exception as e: # noqa: BLE001
|
|
515
|
+
trees[f] = e
|
|
516
|
+
known = {p: v[0] for p, v in project.identifiables.items()}
|
|
517
|
+
for ft in trees.values():
|
|
518
|
+
if isinstance(ft, FileTree):
|
|
519
|
+
for p, n in ft.by_path.items():
|
|
520
|
+
known.setdefault(p, n.tag)
|
|
521
|
+
problems, refs = [], 0
|
|
522
|
+
for f in srcs:
|
|
523
|
+
ft = trees[f]
|
|
524
|
+
rel = project.rel(f)
|
|
525
|
+
if not isinstance(ft, FileTree):
|
|
526
|
+
problems.append(f"{rel}: not well-formed XML: {ft}")
|
|
527
|
+
continue
|
|
528
|
+
err = schema_check(f, ft.data)
|
|
529
|
+
if err:
|
|
530
|
+
problems.append(f"{rel}: schema: {err}")
|
|
531
|
+
for n in ft.root.iter():
|
|
532
|
+
if not (n.tag.endswith("-REF") or n.tag.endswith("-TREF")) or n.children:
|
|
533
|
+
continue
|
|
534
|
+
refs += 1
|
|
535
|
+
tgt, dest = n.text.strip(), n.attrs.get("DEST")
|
|
536
|
+
if not dest:
|
|
537
|
+
problems.append(f"{rel}:{n.line} {n.tag} -> {tgt}: missing DEST attribute")
|
|
538
|
+
if tgt not in known:
|
|
539
|
+
problems.append(f"{rel}:{n.line} {n.tag} -> {tgt}: target does not exist in the project")
|
|
540
|
+
elif dest and known[tgt] != dest:
|
|
541
|
+
problems.append(f"{rel}:{n.line} {n.tag} DEST={dest} but {tgt} is a {known[tgt]}")
|
|
542
|
+
return {"ok": True, "clean": not problems, "files": [project.rel(f) for f in srcs], "references": refs,
|
|
543
|
+
"problems": problems, "schemaChecked": _ad() is not None}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Create a new, empty ARXML file that arxml_edit can then fill.
|
|
2
|
+
|
|
3
|
+
The .arxml guard blocks hand-written ARXML, and arxml_edit only adds under an existing package, so without
|
|
4
|
+
this an agent on a project with no ARXML yet (or one that needs a new SWC / extract file) has no way in.
|
|
5
|
+
|
|
6
|
+
arxml_new(root, "Input/SWC_DoorMonitor.arxml", ["/Swcs/DoorMonitor", "/Interfaces"])
|
|
7
|
+
|
|
8
|
+
writes the R25-11 (AUTOSAR_00054) header and the given AR-PACKAGE tree, nothing else, strictly re-loads it
|
|
9
|
+
with autosar-data, and refuses to overwrite an existing file or to write outside the project.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .edit import schema_check
|
|
18
|
+
from .xmltree import escape
|
|
19
|
+
|
|
20
|
+
NS = "http://autosar.org/schema/r4.0"
|
|
21
|
+
# The release this pack targets (R25-11 = AUTOSAR_00054); older ones only on request.
|
|
22
|
+
DEFAULT_SCHEMA = "AUTOSAR_00054"
|
|
23
|
+
_NAME = re.compile(r"[A-Za-z][A-Za-z0-9_]{0,127}\Z") # SHORT-NAME (TPS_GST: identifier, starts with a letter)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _schema(version: Optional[str]) -> str:
|
|
27
|
+
v = (version or DEFAULT_SCHEMA).strip()
|
|
28
|
+
if re.fullmatch(r"\d{5}", v):
|
|
29
|
+
v = "AUTOSAR_" + v
|
|
30
|
+
if not re.fullmatch(r"AUTOSAR_\d{5}", v):
|
|
31
|
+
raise ValueError(f"schema {version!r}: expected AUTOSAR_000NN (default {DEFAULT_SCHEMA} = R25-11)")
|
|
32
|
+
return v
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _tree(packages: list) -> dict:
|
|
36
|
+
"""{"Swcs": {"DoorMonitor": {}}, ...} from AUTOSAR package paths."""
|
|
37
|
+
tree: dict = {}
|
|
38
|
+
for p in packages:
|
|
39
|
+
segs = [s for s in str(p).strip().strip("/").split("/") if s]
|
|
40
|
+
if not segs:
|
|
41
|
+
raise ValueError(f"package path {p!r} is empty")
|
|
42
|
+
for s in segs:
|
|
43
|
+
if not _NAME.match(s):
|
|
44
|
+
raise ValueError(f"package {p!r}: {s!r} is not a valid SHORT-NAME (letters, digits, _; starts with "
|
|
45
|
+
"a letter)")
|
|
46
|
+
node = tree
|
|
47
|
+
for s in segs:
|
|
48
|
+
node = node.setdefault(s, {})
|
|
49
|
+
return tree
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def render(packages: list, schema: str = DEFAULT_SCHEMA) -> str:
|
|
53
|
+
lines = ['<?xml version="1.0" encoding="UTF-8"?>',
|
|
54
|
+
f'<AUTOSAR xmlns="{NS}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
|
55
|
+
f'xsi:schemaLocation="{NS} {schema}.xsd">']
|
|
56
|
+
|
|
57
|
+
def pkgs(tree: dict, depth: int):
|
|
58
|
+
lines.append(" " * depth + "<AR-PACKAGES>")
|
|
59
|
+
for name, sub in tree.items():
|
|
60
|
+
lines.append(" " * (depth + 1) + "<AR-PACKAGE>")
|
|
61
|
+
lines.append(" " * (depth + 2) + f"<SHORT-NAME>{escape(name)}</SHORT-NAME>")
|
|
62
|
+
if sub:
|
|
63
|
+
pkgs(sub, depth + 2)
|
|
64
|
+
lines.append(" " * (depth + 1) + "</AR-PACKAGE>")
|
|
65
|
+
lines.append(" " * depth + "</AR-PACKAGES>")
|
|
66
|
+
|
|
67
|
+
pkgs(_tree(packages), 1)
|
|
68
|
+
lines.append("</AUTOSAR>")
|
|
69
|
+
return "\n".join(lines) + "\n"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def arxml_new(root: str, file: str, packages: list, schema: Optional[str] = None, dry_run: bool = False) -> dict:
|
|
73
|
+
if not file:
|
|
74
|
+
return {"ok": False, "error": "file is required (project-relative path ending in .arxml)"}
|
|
75
|
+
if not str(file).lower().endswith(".arxml"):
|
|
76
|
+
return {"ok": False, "error": f"{file}: the file name must end in .arxml"}
|
|
77
|
+
if isinstance(packages, str):
|
|
78
|
+
packages = [packages]
|
|
79
|
+
if not packages:
|
|
80
|
+
return {"ok": False, "error": "give at least one package path, e.g. [\"/Swcs\", \"/Interfaces\"] -- an ARXML "
|
|
81
|
+
"file needs an AR-PACKAGE to hold elements"}
|
|
82
|
+
root_abs = os.path.realpath(root)
|
|
83
|
+
path = os.path.realpath(os.path.join(root_abs, file))
|
|
84
|
+
if os.path.commonpath([root_abs, path]) != root_abs:
|
|
85
|
+
return {"ok": False, "error": f"{file} is outside the project directory {root}"}
|
|
86
|
+
rel = os.path.relpath(path, root_abs)
|
|
87
|
+
if os.path.exists(path):
|
|
88
|
+
return {"ok": False, "error": f"{rel} already exists; add to it with arxml_edit instead"}
|
|
89
|
+
try:
|
|
90
|
+
text = render(packages, _schema(schema))
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
return {"ok": False, "error": str(e)}
|
|
93
|
+
err = schema_check(path, text.encode("utf-8"))
|
|
94
|
+
if err: # a bug here, not the caller's: never write a file that does not load
|
|
95
|
+
return {"ok": False, "error": f"internal: generated file does not load: {err}"}
|
|
96
|
+
if not dry_run:
|
|
97
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
98
|
+
tmp = path + ".arxml-tmp"
|
|
99
|
+
with open(tmp, "w", encoding="utf-8", newline="\n") as fh:
|
|
100
|
+
fh.write(text)
|
|
101
|
+
os.replace(tmp, path)
|
|
102
|
+
pk = sorted({"/" + "/".join(s.strip("/").split("/")[:i + 1])
|
|
103
|
+
for s in packages for i in range(len([x for x in str(s).strip("/").split("/") if x]))})
|
|
104
|
+
return {"ok": True, "written": not dry_run, "file": rel, "schema": _schema(schema), "packages": pk,
|
|
105
|
+
"schemaChecked": err is None and _has_ad(), "content": text}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _has_ad() -> bool:
|
|
109
|
+
try:
|
|
110
|
+
import autosar_data # type: ignore # noqa: F401
|
|
111
|
+
return True
|
|
112
|
+
except Exception: # noqa: BLE001
|
|
113
|
+
return False
|
|
@@ -71,6 +71,13 @@ def main(argv=None) -> int:
|
|
|
71
71
|
ae = sub.add_parser("arxml-edit")
|
|
72
72
|
ae.add_argument("--ops", required=True)
|
|
73
73
|
ae.add_argument("--dry-run", action="store_true")
|
|
74
|
+
an = sub.add_parser("arxml-new", help="create an empty ARXML file (header + AR-PACKAGE tree) for arxml-edit")
|
|
75
|
+
an.add_argument("--file", required=True, help="project-relative path, ending in .arxml")
|
|
76
|
+
an.add_argument("--package", action="append", default=[], help="AUTOSAR package path, e.g. /Swcs/DoorMonitor")
|
|
77
|
+
an.add_argument("--schema", help="AUTOSAR_000NN (default AUTOSAR_00054 = R25-11)")
|
|
78
|
+
an.add_argument("--dry-run", action="store_true")
|
|
79
|
+
ac = sub.add_parser("arxml-check", help="verify non-ECUC ARXML: strict schema + every reference and its DEST")
|
|
80
|
+
ac.add_argument("--file", action="append", default=[], help="limit to files whose path contains this")
|
|
74
81
|
fd = sub.add_parser("find")
|
|
75
82
|
fd.add_argument("--module")
|
|
76
83
|
fd.add_argument("--container-def", dest="container_def")
|
|
@@ -116,7 +123,7 @@ def main(argv=None) -> int:
|
|
|
116
123
|
bc.add_argument("--task", required=True, help="task text, @file, or - for stdin")
|
|
117
124
|
bc.add_argument("--budget-tokens", dest="budget_tokens", type=int, default=24000)
|
|
118
125
|
bc.add_argument("--no-history", dest="history", action="store_false")
|
|
119
|
-
for p in (r, e, v, d, au, so, vs, si, fd, cx, ae, asg, cg, cd, ra, vr, bc): # allow --json after the subcommand too
|
|
126
|
+
for p in (r, e, v, d, au, so, vs, si, fd, cx, ae, an, ac, asg, cg, cd, ra, vr, bc): # allow --json after the subcommand too
|
|
120
127
|
p.add_argument("--json", action="store_true", help=argparse.SUPPRESS)
|
|
121
128
|
a = ap.parse_args(argv)
|
|
122
129
|
t0 = time.time()
|
|
@@ -135,8 +142,16 @@ def run(a) -> dict:
|
|
|
135
142
|
if a.cmd in ("edit", "arxml-edit"):
|
|
136
143
|
try:
|
|
137
144
|
a.ops = _ops(a.ops)
|
|
145
|
+
except json.JSONDecodeError as ex:
|
|
146
|
+
at = f" near: ...{ex.doc[max(0, ex.pos - 80):ex.pos]} <<HERE>> {ex.doc[ex.pos:ex.pos + 40]}..." if ex.doc else ""
|
|
147
|
+
return {"error": f"ops is not valid JSON ({ex.msg} at char {ex.pos}){at}. Nothing was changed. Send ops "
|
|
148
|
+
"as a JSON array of objects (not one long string), and split a very large edit into "
|
|
149
|
+
"several calls."}
|
|
138
150
|
except (ValueError, OSError) as ex:
|
|
139
151
|
return {"error": f"ops is not valid JSON: {ex}"}
|
|
152
|
+
if a.cmd == "arxml-new": # no Project: the directory may hold no ARXML at all yet
|
|
153
|
+
from .arxml_new import arxml_new
|
|
154
|
+
return arxml_new(a.root, a.file, a.package, schema=a.schema, dry_run=a.dry_run)
|
|
140
155
|
if a.cmd == "diff":
|
|
141
156
|
from .diff import diff
|
|
142
157
|
root = os.path.abspath(a.root)
|
|
@@ -178,7 +193,7 @@ def run(a) -> dict:
|
|
|
178
193
|
from .specids import check_ids
|
|
179
194
|
return check_ids(os.path.abspath(a.root), a.ids, docs=a.docs)
|
|
180
195
|
from .model import Project
|
|
181
|
-
project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit", "find", "crosscheck", "signal-owners", "arxml-edit", "add-signal"))
|
|
196
|
+
project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit", "find", "crosscheck", "signal-owners", "arxml-edit", "add-signal", "arxml-check"))
|
|
182
197
|
if a.cmd == "add-signal":
|
|
183
198
|
from .add_signal import AddSignalError, add_signal
|
|
184
199
|
try:
|
|
@@ -187,6 +202,9 @@ def run(a) -> dict:
|
|
|
187
202
|
transfer_property=a.transfer_property, dry_run=a.dry_run)
|
|
188
203
|
except AddSignalError as ex:
|
|
189
204
|
return {"error": str(ex), "written": False}
|
|
205
|
+
if a.cmd == "arxml-check":
|
|
206
|
+
from .arxml_edit import arxml_check
|
|
207
|
+
return arxml_check(project, a.file or None)
|
|
190
208
|
if a.cmd == "arxml-edit":
|
|
191
209
|
from .arxml_edit import arxml_edit
|
|
192
210
|
return arxml_edit(project, a.ops, dry_run=a.dry_run)
|
package/skills/autosar/SKILL.md
CHANGED
|
@@ -21,6 +21,8 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
|
|
|
21
21
|
1. **Never hand-write or hand-edit ARXML** (no `write`/`edit`/`sed` on `.arxml`; the pack blocks them). Every change goes
|
|
22
22
|
through `ecuc_edit`, which picks the value element, DEFINITION-REF/DEST and VALUE-REF DEST from
|
|
23
23
|
the param defs and patches only the changed bytes. Reading raw ARXML for context is fine; prefer `ecuc_read`.
|
|
24
|
+
A **new** System-extract / SWC / interface file is started with `arxml_new` and filled with `arxml_edit` (recipe
|
|
25
|
+
below). Do not write one with Python (`autosar_data` scripts, ElementTree) or a temp file renamed to `.arxml`.
|
|
24
26
|
2. **Validate after every edit**: `ecuc_validate` (scoped with `files`/`module` if large). Fix
|
|
25
27
|
every error you introduced before moving on; report pre-existing ones separately.
|
|
26
28
|
3. **Cite** the AUTOSAR path, the DEFINITION-REF and file:line for every element you discuss or change —
|
|
@@ -60,6 +62,8 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
|
|
|
60
62
|
| Values of a module/container | `ecuc_read path=… depth=…` |
|
|
61
63
|
| All containers matching a condition / referencing X / referenced by Y | `ecuc_find` |
|
|
62
64
|
| Cross-check one module against the extract, C types and memory | `ecuc_crosscheck module=…` |
|
|
65
|
+
| Start a NEW System extract / SWC / interface .arxml file | `arxml_new file=… packages=[…]` |
|
|
66
|
+
| Verify extract / SWC / interface files (schema + every reference + DEST) | `arxml_check` |
|
|
63
67
|
| Edit System extract / SWC / interface XML | `arxml_edit` (DEST inferred, schema-ordered, refs checked) |
|
|
64
68
|
| Add a signal end to end (ECUC + extract + DBC) | `autosar_add_signal` |
|
|
65
69
|
| What may be set/added here (types, ranges, literals, multiplicity) | `ecuc_read path=… schema=true`, or a def path `/AUTOSAR/EcucDefs/<Mod>/…` |
|
|
@@ -87,6 +91,54 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
|
|
|
87
91
|
5. **Never conclude "no faults" from a single validate call**, and never finish with a failing validation: after
|
|
88
92
|
ANY edit run `ecuc_validate`; if it fails, fix it before answering.
|
|
89
93
|
|
|
94
|
+
## Every change: ground → decide → change → verify until clean
|
|
95
|
+
Do not decide what to change from memory or from the prompt alone. Each change goes through four steps, in order:
|
|
96
|
+
1. **Ground (facts first).** Gather every fact the change depends on from the tools, not from recall:
|
|
97
|
+
`autosar_build_context` (for a runnable), `ecuc_read … schema=true` (what may be set: types, ranges, literals,
|
|
98
|
+
multiplicity), `ecuc_find` / `autosar_config_lookup` (what already exists and references what),
|
|
99
|
+
`ecuc_crosscheck` (extract ↔ ECUC ↔ C), the spec text (`autosar_spec_id_check` for any requirement ID you cite).
|
|
100
|
+
Write the facts down in a short list with their source (file:line or tool), including the existing elements you
|
|
101
|
+
will reuse or reference.
|
|
102
|
+
2. **Decide.** From those facts only, write the plan as a todo list: every element to add/change, in dependency order
|
|
103
|
+
(reference targets first), and which tool does each. A link you could not ground (a value, an id, a target) is an
|
|
104
|
+
open question: look it up, or ask; never fill it in with a plausible guess.
|
|
105
|
+
3. **Change** with the structured tools only (`ecuc_edit`, `arxml_new` + `arxml_edit`, `autosar_add_signal`). Prefer
|
|
106
|
+
one call per coherent change; nothing is written if any op fails.
|
|
107
|
+
4. **Verify → fix → verify, until clean.** `arxml_check` (extract/SWC files) and `ecuc_validate` (ECUC), then
|
|
108
|
+
`autosar_generate` + `autosar_codegen_diff` / `autosar_build` and `autosar_verify_rte` where the project has them.
|
|
109
|
+
On any problem: read it, go back to step 1 for that fact, fix with the structured tools, verify again. Repeat until
|
|
110
|
+
every check is clean. Then re-read the request and tick each todo against the evidence. Never report done with a
|
|
111
|
+
failing or unrun check; if something cannot be made clean, say exactly what and why.
|
|
112
|
+
|
|
113
|
+
## New ARXML file (SWC, interface, data types, extract) from nothing
|
|
114
|
+
1. `arxml_new file="Input/SWC_X.arxml" packages=["/DataTypes", "/Interfaces", "/Swcs"]` — the R25-11 header and
|
|
115
|
+
empty packages, strictly checked. It refuses to overwrite; extend existing files with `arxml_edit` instead.
|
|
116
|
+
2. ONE `arxml_edit` call with the elements in dependency order (a reference target must be added earlier in the same
|
|
117
|
+
call): base type → implementation data type → interface → SWC with ports → `SWC-INTERNAL-BEHAVIOR` → runnables
|
|
118
|
+
(with data accesses) → events. Each op is `{op:"add", parent_path, element:<XML tag>, short_name, values:{...}}`;
|
|
119
|
+
references are written `"->/Path/To/Target"` and get their DEST automatically. Example (shortened):
|
|
120
|
+
```
|
|
121
|
+
{op:"add", parent_path:"/DataTypes", element:"SW-BASE-TYPE", short_name:"uint8_base",
|
|
122
|
+
values:{"CATEGORY":"FIXED_LENGTH", "BASE-TYPE-SIZE":8, "BASE-TYPE-ENCODING":"NONE"}}
|
|
123
|
+
{op:"add", parent_path:"/DataTypes", element:"IMPLEMENTATION-DATA-TYPE", short_name:"uint8",
|
|
124
|
+
values:{"CATEGORY":"VALUE", "SW-DATA-DEF-PROPS":{"SW-DATA-DEF-PROPS-VARIANTS":{"SW-DATA-DEF-PROPS-CONDITIONAL":
|
|
125
|
+
{"BASE-TYPE-REF":"->/DataTypes/uint8_base"}}}}}
|
|
126
|
+
{op:"add", parent_path:"/Interfaces", element:"SENDER-RECEIVER-INTERFACE", short_name:"If_X",
|
|
127
|
+
values:{"DATA-ELEMENTS":{"VARIABLE-DATA-PROTOTYPE":[{"SHORT-NAME":"Val", "TYPE-TREF":"->/DataTypes/uint8"}]}}}
|
|
128
|
+
{op:"add", parent_path:"/Swcs", element:"APPLICATION-SW-COMPONENT-TYPE", short_name:"X",
|
|
129
|
+
values:{"PORTS":{"P-PORT-PROTOTYPE":{"SHORT-NAME":"PpX", "PROVIDED-INTERFACE-TREF":"->/Interfaces/If_X"}}}}
|
|
130
|
+
{op:"add", parent_path:"/Swcs/X", element:"SWC-INTERNAL-BEHAVIOR", short_name:"IB"}
|
|
131
|
+
{op:"add", parent_path:"/Swcs/X/IB", element:"RUNNABLE-ENTITY", short_name:"X_Run10ms", values:{"SYMBOL":"X_Run10ms",
|
|
132
|
+
"DATA-WRITE-ACCESSS":{"VARIABLE-ACCESS":{"SHORT-NAME":"W_Val", "ACCESSED-VARIABLE":{"AUTOSAR-VARIABLE-IREF":
|
|
133
|
+
{"PORT-PROTOTYPE-REF":"->/Swcs/X/PpX", "TARGET-DATA-PROTOTYPE-REF":"->/Interfaces/If_X/Val"}}}}}}
|
|
134
|
+
{op:"add", parent_path:"/Swcs/X/IB", element:"TIMING-EVENT", short_name:"TE_10ms",
|
|
135
|
+
values:{"START-ON-EVENT-REF":"->/Swcs/X/IB/X_Run10ms", "PERIOD":0.01}}
|
|
136
|
+
```
|
|
137
|
+
3. If an op is rejected, read the error: it names the allowed sub-elements of that parent. Fix that op and resend
|
|
138
|
+
the call (nothing was written). Do not fall back to writing XML.
|
|
139
|
+
4. `arxml_check` until it says CLEAN (and `ecuc_validate` / `autosar_generate` / `autosar_build` when the project
|
|
140
|
+
has ECUC and a generator). Then tick every requested element against the file.
|
|
141
|
+
|
|
90
142
|
## The loop
|
|
91
143
|
`ecuc_read (schema) → ecuc_edit → ecuc_validate → autosar_generate → arxml_diff / inspect Output → autosar_build → autosar_trace (impact on Source/)`.
|
|
92
144
|
Details and failure handling: `references/workflow.md`.
|