diff --git a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md index 9f372f4..c0b9bb3 100644 --- a/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md +++ b/.claude/skills/duplo-extension-dev/reference/02-authoring-guide.md @@ -80,8 +80,11 @@ Ties the bundle together. For a typed extension (see the hello-world manifest): } ``` Critical: `archetype` must be `"typed"` so the loader takes the DLL path; the five `*Type` names must be the -fully-qualified type names in your DLL; `backend.assemblyDir` is `//backend`. **Bump `version` on -every backend code change** ([06](06-registration.md#reloading-changed-code--bump-manifestversion)). +fully-qualified type names in your DLL; `backend.assemblyDir` is `//backend` and each +`skills[].folder` is `//skills/`. **Bump `version` on every backend code change** +([06](06-registration.md#reloading-changed-code--bump-manifestversion)) — you don't have to bump either path +with it, since `build-extension.sh` derives both from `id` and `version` when it writes the bundle manifest. +It prints a `NOTE:` if a checked-in value disagreed. ### Naming diff --git a/scripts/build-extension.sh b/scripts/build-extension.sh index da36b75..e9fe60b 100755 --- a/scripts/build-extension.sh +++ b/scripts/build-extension.sh @@ -214,8 +214,40 @@ echo " host provides $(wc -l < "$PROVIDED" | tr -d ' ') assemblies (excluded echo "==> Assembling bundle" PKG="$DIR/dist/pkg" rm -rf "$PKG"; mkdir -p "$PKG/backend" "$PKG/fe" "$PKG/skills" -# Bundle manifest with the real host SDK version pinned in. -jq --arg v "$SDK_VER" '.sdkVersion = $v' "$DIR/manifest.json" > "$PKG/manifest.json" +# Bundle manifest with the real host SDK version pinned in, and both // paths derived from +# id+version. The host stages the bundle to /// but reads the entry assembly +# from backend.assemblyDir and each provisioning skill from skills[].folder, so all three have to agree. +# Maintained by hand they drift the moment you bump version alone, and what that breaks depends on the target: +# +# backend.assemblyDir a host that still has the old version on disk keeps running the OLD backend DLL and +# says nothing; a host that's never seen it refuses the install outright with +# "Extension entry assembly not found: ...//backend/". +# skills[].folder quieter in both directions. The loader's seeder returns early on a missing directory, +# so a fresh host loads fine with the skill silently absent — Agent-mode provisioning +# then fails later with a confusing "no skill" — and a host holding the old version +# seeds the OLD skill. +# +# Both are always //…, so compute them here instead of trusting the checked-in values. The skill +# tail varies per entry (skills/), so only the first two segments are replaced; a manifest with no +# skills array is left alone rather than given an empty one. +jq --arg v "$SDK_VER" ' + .sdkVersion = $v + | .backend.assemblyDir = (.id + "/" + .version + "/backend") + | .id as $i | .version as $r + | if (.skills | type) == "array" then + .skills = [ .skills[] + | if ((.folder | split("/") | length) > 2) + then .folder = ($i + "/" + $r + "/" + (.folder | split("/") | .[2:] | join("/"))) + else . end ] + else . end +' "$DIR/manifest.json" > "$PKG/manifest.json" +# Report drift rather than repairing it silently, so the checked-in value gets fixed at the source too. +_ad_src=$(jq -r '.backend.assemblyDir // ""' "$MANIFEST") +_ad_out=$(jq -r '.backend.assemblyDir' "$PKG/manifest.json") +[ "$_ad_src" = "$_ad_out" ] || echo " NOTE: stale backend.assemblyDir '$_ad_src' in $MANIFEST — bundled as '$_ad_out'" +_sk_src=$(jq -r '[.skills[]?.folder] | join(", ")' "$MANIFEST") +_sk_out=$(jq -r '[.skills[]?.folder] | join(", ")' "$PKG/manifest.json") +[ "$_sk_src" = "$_sk_out" ] || echo " NOTE: stale skills[].folder '$_sk_src' in $MANIFEST — bundled as '$_sk_out'" # Ship only backend assemblies the host does NOT already provide, plus each kept DLL's sidecars. Native # runtimes/ are host-provided too (Mongo/AWS natives load via the host's Default-ALC copies), so skip them. kept=0; dropped=0