Compare commits

...
4 Commits
Author SHA1 Message Date
mathias d4f0416dc5 chore(release): plugin.json version 0.6.3
Skip-Release: true
2026-08-26 05:54:12 +00:00
mathiasandClaude Opus 5 abd81f939a fix(doctor): report a real harness directory as unmanaged, not as "outside"
release / tag (push) Successful in 1s
Found on flamingo during the first Mac install. ~/.config/crush/skills held
real directories from a pre-plugin install, and doctor said:

  FAIL outside: ~/.config/crush/skills resolves to ~/.config/crush/skills,
  outside the active checkout ~/.local/share/skills/skills

Naming a directory as its own resolution target is true and useless. Worse, it
pointed at the wrong fix: "outside" means a link into a foreign checkout, which
you repair by re-pointing the link. A real directory is repaired by moving it
aside and re-running install.sh — install.sh already refuses to delete it,
correctly, since it did not create it and cannot know what is in there.

Now reported as its own class, with the skill count so it is obvious whether
the directory holds a stale copy of the library or something unique that
belongs in the repo:

  FAIL unmanaged: <dir> is a real directory, not a link into the checkout —
  it holds N unmanaged skill(s); move it aside and re-run install.sh

The distinction matters because content in that directory reaches exactly one
harness and cannot receive new skills — the failure mode is silent divergence,
which is the same family as everything else this tool exists to catch.

Bump-Type: patch

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-26 07:53:14 +02:00
mathias e42d2ba9cb chore(release): plugin.json version 0.6.2
Skip-Release: true
2026-08-25 08:54:05 +00:00
mathiasandClaude Opus 5 68cebc0bbb fix(gitea-ci): hand out the call, not a copy of the build implementation (infra#309)
release / tag (push) Successful in 1s
This skill is the third factory. Two repo templates were fixed when the
buildah->buildctl migration went through, and this one kept handing out
the broken step for weeks afterwards -- because it is what gets consulted
when adding CI to an EXISTING repo, which the templates never touch.

Pasting an implementation is the actual defect. The estate now has one
(mathias/infra ci-templates/build-image.sh) with byte-identical copies
held in place by a drift gate, so the skill teaches the four adoption
steps and the input list instead of a block to copy.

Step 1 of those four -- registering in adopters.txt -- is called out
explicitly, because a copy nobody registered works today and falls
silently behind on the next canonical change. That is the failure this
whole issue exists to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
2026-08-25 10:54:02 +02:00
4 changed files with 56 additions and 39 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "skills", "name": "skills",
"description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.", "description": "Mathias's engineering skills library, plus the hooks and MCP servers that make it enforceable: TDD, clean code, debugging, specs, discovery, CI, and secret hygiene.",
"version": "0.6.1", "version": "0.6.3",
"author": { "author": {
"name": "Mathias", "name": "Mathias",
"url": "https://git.d-ma.be/mathias" "url": "https://git.d-ma.be/mathias"
+9
View File
@@ -109,6 +109,15 @@ do
continue continue
fi fi
# A real directory where a link belongs holds content nothing manages: it
# cannot receive new skills and silently shadows the checkout. Distinguished
# from the foreign-checkout case below because the fix differs — move it
# aside and re-run install.sh, rather than re-point a link.
if [ ! -L "$dir" ] && [ -d "$dir" ]; then
add FAIL unmanaged "$dir is a real directory, not a link into the checkout — it holds $(count_skills "$dir") unmanaged skill(s); move it aside and re-run install.sh"
continue
fi
# Resolve to a physical path and confirm it lands inside THIS checkout. # Resolve to a physical path and confirm it lands inside THIS checkout.
# A link that resolves to a different checkout is the dangerous case: it # A link that resolves to a different checkout is the dangerous case: it
# works, so nothing complains, while serving stale content. # works, so nothing complains, while serving stale content.
+33 -38
View File
@@ -1,6 +1,6 @@
--- ---
name: gitea-ci name: gitea-ci
description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, buildctl image build, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere. description: End-to-end Gitea CI/CD setup with self-hosted act_runner, quality gate pipeline, shared buildctl image-build script, k3s deploy, and GitHub mirror. Covers act_runner-specific gotchas that are not documented anywhere.
--- ---
# Gitea CI/CD Pipeline Setup # Gitea CI/CD Pipeline Setup
@@ -133,44 +133,39 @@ jobs:
echo "version-tag=${REF#refs/tags/}" >> "$GITHUB_OUTPUT" echo "version-tag=${REF#refs/tags/}" >> "$GITHUB_OUTPUT"
fi fi
# infra#309: do NOT paste a build implementation here. The estate has
# exactly one, mathias/infra's ci-templates/build-image.sh, and each
# repo carries a byte-identical copy at .gitea/build-image.sh that a
# drift gate keeps current.
#
# Copying the implementation is what made the buildah->buildctl
# migration (infra#241) an eleven-repo sprint, and what left this very
# skill handing out the broken version for weeks after the app repos
# were fixed. Per-repo differences are INPUTS, not edits to the copy.
#
# To adopt in a new repo:
# 1. add the repo to mathias/infra ci-templates/adopters.txt
# 2. run `task ci:sync` in mathias/infra -- it writes the copy
# 3. commit .gitea/build-image.sh in your repo
# 4. call it as below
#
# Skipping step 1 leaves the copy unmanaged: it works today and falls
# silently behind the next time the canonical file changes.
#
# Inputs: IMAGE (required, usually the workflow-level env), SHA_TAG,
# VERSION_TAG, GIT_SHA, IMAGE_SOURCE, BUILD_ARGS, EXTRA_LABELS,
# CONTEXT, DOCKERFILE_DIR, DOCKERFILE_NAME, PUSH_LATEST. Read the
# canonical file's header for the full list.
#
# `bash <script>`, not a direct exec: a file written through the Gitea
# API lands 0644, and CI that depends on a file mode is a trap.
- name: Build and push to local registry - name: Build and push to local registry
run: | env:
set -e SHA_TAG: ${{ steps.meta.outputs.sha-tag }}
# buildctl, NOT buildah (infra#241). The runner is a pod with VERSION_TAG: ${{ steps.meta.outputs.version-tag }}
# `capabilities: drop [ALL]` and `allowPrivilegeEscalation: false`, GIT_SHA: ${{ github.sha }}
# so rootless buildah cannot remap UID/GID for a fresh base-image IMAGE_SOURCE: ${{ github.repositoryUrl }}
# pull and the pod has no OCI runtime -- every `RUN` step fails. run: bash .gitea/build-image.sh
# buildctl is a thin client: it hands the build to the host's
# buildkitd over a socket, so the privileged work happens on the
# host and the pod's securityContext is not in the picture.
# The failure is LATENT -- warm base-image layers mask it, so a
# buildah step can look fine for weeks and then fail on a cold pull.
BUILDKIT_HOST="unix:///run/buildkit/buildkitd.sock"
REGISTRY="localhost:5000"
REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}"
TAR_FILE="$(mktemp)"
trap 'rm -f "$TAR_FILE"' EXIT
buildctl --addr "$BUILDKIT_HOST" build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt label:org.opencontainers.image.revision="${{ github.sha }}" \
--opt label:org.opencontainers.image.source="${{ github.repositoryUrl }}" \
--output type=oci,dest="$TAR_FILE"
# buildctl has no registry-push equivalent to `buildah push`; skopeo
# copies the OCI archive up, once per tag.
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" docker://${REF}
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" docker://${REGISTRY}/${{ env.IMAGE }}:latest
if [ -n "${{ steps.meta.outputs.version-tag }}" ]; then
skopeo copy --dest-tls-verify=false \
oci-archive:"$TAR_FILE" \
docker://${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.version-tag }}
fi
echo "✓ Image pushed to ${REF}"
# No smoke test step. The one this skill used to recommend ran # No smoke test step. The one this skill used to recommend ran
# `sudo k3s ctr run` against the host's containerd -- that worked when # `sudo k3s ctr run` against the host's containerd -- that worked when
+13
View File
@@ -112,6 +112,19 @@ mkdir -p "$f/home/.claude/skills"
ln -sfn "$f/ck/skills/alpha" "$f/home/.claude/skills/alpha" ln -sfn "$f/ck/skills/alpha" "$f/home/.claude/skills/alpha"
assert_fails "$f" 'legacy' 'leftover per-skill Claude symlinks are reported' assert_fails "$f" 'legacy' 'leftover per-skill Claude symlinks are reported'
# --- 7b. harness dir is a REAL directory, not a link into the checkout -------
# Found on flamingo 2026-08-26: ~/.config/crush/skills held real directories
# from a pre-plugin install. The old message read "resolves to
# <path>, outside the active checkout" — naming the directory as its own
# resolution target, which is true but useless. It must say the dir is not a
# link at all, because the fix (move it aside, re-run install.sh) is different
# from the fix for a link into a foreign checkout.
f=$(new_fixture realdir)
mkdir -p "$f/home/.agents" "$f/home/.config/crush/skills/alpha"
ln -sfn "$f/ck/skills" "$f/home/.agents/skills"
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/home/.config/crush/skills/alpha/SKILL.md"
assert_fails "$f" 'not a link' 'real directory is reported as unmanaged, not as "outside"'
# --- 7. dangling link inside a harness dir (not the canonical link itself) -- # --- 7. dangling link inside a harness dir (not the canonical link itself) --
# Exercises the per-entry scan rather than the canonical-link check: this is # Exercises the per-entry scan rather than the canonical-link check: this is
# the shape the 243 dead links actually had. # the shape the 243 dead links actually had.