Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .claude/scripts/agent-role-delivery-contract.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,77 @@ grep -Fq '### Authority model' "${constitution}" ||
grep -Fq 'plugins/agentic-engineering/agents/agent-improver.agent.md' "${constitution}" ||
fail "consumer does not name the upstream Agent Improver source"

# The bundled SKILL.md is SYNCED from devantler-tech/agent-skills (it carries
# metadata.github-repo and the update-agent-skills workflow re-pulls it), so an edit there
# is silently reverted. The consumer listed it as an authoring surface until 2026-07-25,
# which would route a generic fix into a file that discards it.
#
skill_path='plugins/agentic-engineering/skills/agent-improvement/SKILL.md'

# Owner, path, provenance value and the non-authoring rule must all appear in ONE bullet.
# Asserting any of them against the whole contract is a scope hole β€” verified: changing the
# real owner to agent-plugins and appending an unrelated copy of the expected phrase
# elsewhere satisfied a global check. Extraction therefore starts at the OWNER line (the
# bullet's first line), not at the path line, so the owner declaration is bound to this skill.
# Stop at the next SIBLING BULLET as well as at a blank line. Markdown bullets are normally
# consecutive with no blank line between them, so a blank-line-only terminator swallowed the
# following bullet too β€” and the "same bullet" binding this guard claims could then be
# satisfied by text that had moved into that sibling.
skill_bullet="$(awk '
!inb && /\*\*`devantler-tech\/agent-skills`\*\* authors `agent-improvement\/`/ { inb = 1; print; next }
inb {
if ($0 ~ /^[[:space:]]*$/) exit
if ($0 ~ /^[[:space:]]*[-*+] /) exit
print
}
' "${constitution}" | tr '\n' ' ' | tr -s '[:space:]' ' ')"
Comment thread
devantler marked this conversation as resolved.
[ -n "${skill_bullet}" ] ||
fail "consumer does not name agent-skills as the owner of bundled skills"
assert_bullet() {
case "${skill_bullet}" in
*"$1"*) ;;
*) fail "$2" ;;
esac
}
assert_bullet "${skill_path}\` carries" \
"the agent-skills owner bullet does not name the bundled agent-improvement/SKILL.md"
assert_bullet 'github-repo: https://github.com/devantler-tech/agent-skills' \
"the agent-skills owner bullet does not name devantler-tech/agent-skills as the upstream"
assert_bullet 'It is a synced artifact, **not** an authoring surface' \
"the agent-skills owner bullet does not mark that copy a non-authoring surface"

# Provenance is a per-FILE question: the same plugin directory holds synced skills and
# locally-authored agents, so a per-directory rule is wrong in one direction or the other.
#
# An UNINITIALISED submodule is a normal local state, not contract drift. Detect it first, or
# a fresh checkout reports "the skill is missing upstream" and hides the actionable fix.
plugin_root="${repo_root}/libraries/agent-plugins/plugins/agentic-engineering"
[ -d "${plugin_root}" ] ||
fail "libraries/agent-plugins is not initialised, so the bundled skill cannot be checked. Initialise it with
.claude/scripts/submodule-init.sh libraries/agent-plugins"

bundled_skill="${plugin_root}/skills/agent-improvement/SKILL.md"
[ -f "${bundled_skill}" ] ||
fail "bundled agent-improvement/SKILL.md is missing at the pinned plugin revision β€” AGENTS.md routes generic skill edits through this path, so its absence invalidates the contract text"
Comment thread
devantler marked this conversation as resolved.

# Query the frontmatter STRUCTURALLY, at the exact YAML path `metadata.github-repo`.
#
# A line-oriented grep cannot express this and kept failing in new ways: it accepted the value
# under a different mapping (`examples.github-repo`), accepted a body example after the real
# field was deleted, and treated frontmatter with no closing delimiter as valid. yq resolves
# the real path or returns null, which is the property actually wanted β€” and it is SHORTER than
# the hand-rolled extraction it replaces, so this closes the hole while cutting complexity.
command -v yq >/dev/null ||
fail "yq is required to verify the bundled skill's provenance structurally. Install it (brew install yq; it is preinstalled on GitHub ubuntu runners)"
# `|| skill_upstream=''` is load-bearing under `set -e`: yq exits non-zero on unparseable
# frontmatter (e.g. no closing delimiter), which would abort the script SILENTLY β€” a failing
# test with no message, indistinguishable from a crash. Capture the failure and let the
# assertion below report it with the actionable text.
skill_upstream="$(yq --front-matter=extract '.metadata.github-repo // ""' "${bundled_skill}" 2>/dev/null)" ||
skill_upstream=''
[ "${skill_upstream}" = 'https://github.com/devantler-tech/agent-skills' ] ||
fail "bundled agent-improvement/SKILL.md does not declare metadata.github-repo = https://github.com/devantler-tech/agent-skills (got: '${skill_upstream:-<none or unparseable frontmatter>}') β€” re-check the owning repository before trusting the contract text"

# Every machine-readable entrypoint pointer must resolve to an agent the pinned plugin
# actually BUNDLES. Derived from the submodule rather than hard-coded, so the next upstream
# rename cannot leave this consumer pointing at a file that no longer exists β€” which is
Expand Down
54 changes: 49 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,55 @@ definition surface, and an installed/cache copy is never an authoring target.
script, the provider-neutral desired state, plugin settings, and the Cursor loader source. The local
Agent Improver agent/skill forks are retired, and so is the standalone FinOps agent fork β€” the
reviewed plugin is the source for both roles.
- The generic upstream source in `devantler-tech/agent-plugins`, specifically
`plugins/agentic-engineering/agents/agent-improver.agent.md`,
`plugins/agentic-engineering/skills/agent-improvement/SKILL.md`, the plugin README/desired state,
and their manifest/contract validation. Change generic behaviour there first, merge it, then update
this consumer's `libraries/agent-plugins` gitlink and copied desired state.
- The generic upstream source, which is **NOT one repository**. **Check the file's own provenance
before editing it β€” the question is per-FILE, never per-directory**, because one plugin directory
mixes locally-authored files with copies synced from *several different* upstreams:
- **`devantler-tech/agent-plugins`** authors
`plugins/agentic-engineering/agents/agent-improver.agent.md`, the plugin README/desired state, and
their manifest/contract validation. These carry **no** `metadata.github-repo`.
- **`devantler-tech/agent-skills`** authors `agent-improvement/`, **and that one skill is the only
bundled skill this grant covers** β€” no other skill in that repository is a named surface.
πŸ”΄ The copy at `plugins/agentic-engineering/skills/agent-improvement/SKILL.md` carries
`metadata.github-repo: https://github.com/devantler-tech/agent-skills` and is re-pulled by the
`update-agent-skills` workflow, so editing it there is **silently reverted** β€” no conflict, no CI
failure, no signal. It is a synced artifact, **not** an authoring surface.

⚠️ **The following is INFORMATIONAL ROUTING GUIDANCE, not part of the grant.** It exists so a fix is
not sent to the wrong repository; it names **no** additional definition surface, and every skill in
it is **out of scope** for autonomous change. Other bundled skills come from third-party upstreams
entirely β€” measured 2026-07-25: `find-skills` from `vercel-labs/skills`, `git-commit`/`refactor`
from `github/awesome-copilot`, `test-driven-development` from `obra/superpowers`, `astro` from
`astrolicious/agent-skills`. Each is a third party, so the *Ask before upstream creates* rule and the
*Professional-work repository boundary* both apply before any interaction. **Read the value to learn
who owns a file; never read it as permission to change that file.**

Verify **from the monorepo root**, and **query the frontmatter structurally** β€” a `grep` for the
string is not good enough here. It reports a file whose *body* merely mentions the URL, accepts a
prefix-extended rename (`agent-skills-v2`), and misses that the value sits under some other mapping
than `metadata`. Ask for the exact YAML path instead:

```sh
# who owns each bundled skill (empty/null β‡’ authored in agent-plugins, safe to edit there):
for f in libraries/agent-plugins/plugins/*/skills/*/SKILL.md; do
printf '%s\t%s\n' "$(yq --front-matter=extract '.metadata.github-repo // "LOCAL"' "$f")" "$f"
done
```

Anything printing `https://github.com/devantler-tech/agent-skills` is **synced** β€” edit it upstream
in that repo. `LOCAL` means it is authored in `agent-plugins`.

Change generic behaviour in the **owning** repository first. The rollout then differs by owner, and
**the skills path has an extra hop that is easy to skip**:
- *Authored in `agent-plugins`* (agents, README, desired state): merge there, then bump this
consumer's `libraries/agent-plugins` gitlink.
- *Authored in `agent-skills`* (bundled skills): merge there, **then wait for `update-agent-skills`
to re-pull it into `agent-plugins` and for THAT generated PR to merge**, and only then bump the
gitlink. Bumping straight after the `agent-skills` merge pins a revision that still carries the
**old** skill β€” the change is real upstream and absent here, which reads as a completed rollout
while nothing has actually shipped to this deployment. Confirm by reading the skill's content at
the pinned revision, never by the upstream PR being merged.

Finally, update the copied desired state.

**Runtime-local surfaces β€” back up before editing, verify in place, and record before/after in native
memory and the run report:**
Expand Down
Loading