fix(kyberforge): resolve PR #144 review and audit round 2
- factory-audit: ./ and bare/absolute script checks scoped to command position (no false FAILs on ./src or printf); hook sources limited to .apm/hooks or package-root hooks/; Kiro-aware lowercase events; unfilled template placeholders FAIL; repo-only instructions FAIL at any scope; Vale description FAIL documented; bats 367 -> 378 - primitive-author: split-quote/spaced paths and handler-less entries promoted to Must; Step 4.2 renders into a scratch consumer instead of a no-op dry run; dispatch and gate hand-off trimmed - apm-workflow 1.0.2: mutual boundary with primitive-author - forge: no double package bump; gotcha wording - skill-author: create keeps seeded 0.1.0 (ADR-0022); portable, retry-safe new-skill.sh; template and flow consistency fixes - hook docs: cite the ADR-0019 correction; guard caveat Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
@@ -21,9 +21,8 @@ metadata:
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The word gates are two measurements, not two tiers of one rule: the 2,770-word / 500-line spec backstop counts the whole file, Step 3's gate the body alone. Never unify them.
|
||||
- The 2,770-word / 500-line spec backstop counts the whole file, frontmatter included — a separate measurement from Step 3's body-only gate. Never unify them.
|
||||
- Never spawn a subagent to audit or recheck your own work — run `/factory-audit` inline, in the same context as the edits. Clean-context recheck belongs to `/forge`'s outer loop, and a self-spawned subagent's worktree can be torn down by concurrent cleanup, destroying an uncommitted draft.
|
||||
- Do not create new scripts unless a signal explicitly calls for it. Writing one from scratch requires out-of-scope transcript analysis — flag the opportunity as a suggestion instead.
|
||||
|
||||
## Step 1 — Dispatch
|
||||
|
||||
@@ -58,6 +57,6 @@ Gates `/factory-audit` enforces in both flows:
|
||||
|
||||
Run `/factory-audit` on the resolved skill directory; resolve every FAIL before reporting done. It checks name-to-directory match, placeholders, both size budgets, boundary-target resolution and script hygiene — do not hand-check those. Hand-check the one thing it misses: an empty body reports `PASS SKILL.md body word count 0 (ADR-0020 target: 600)`, so confirm at least one non-empty section exists.
|
||||
|
||||
Bump `metadata.version`: the **minor** version on create (new skills start at `0.1.0`) and the **patch** version on improve.
|
||||
Versioning: on create, keep the scaffold's `0.1.0` — do not bump it (ADR-0022); on improve, bump the **patch** version.
|
||||
|
||||
**Commit verification.** Inside a git worktree: once the audit is clean, run `rtk git add` and `rtk git commit` — do not stop at staging. Re-run `rtk git log --oneline -1` and confirm the hash changed from Step 1's. A non-empty `git diff --stat` is not proof: staged-but-uncommitted work is part of no commit and is silently lost if the tree is cleaned up. Report done only once the hash has changed. Outside a worktree (a skill under `~/.claude/skills/`, say) nothing is committable — report done on a clean audit, naming that as the reason.
|
||||
|
||||
@@ -16,14 +16,12 @@ description: >
|
||||
Not FILL IN: near-miss case -> FILL IN: real sibling skill.
|
||||
# Required. Preloaded into EVERY session whether or not the skill is invoked.
|
||||
# Exactly three parts, in this order: trigger clause, at most one capability
|
||||
# clause, boundary clause. Drop the boundary line if no near-miss skill exists.
|
||||
# clause, boundary clause. Write one boundary clause per genuine near-miss; at least one.
|
||||
# Trigger clause: when should an agent activate this skill? Describe the user's
|
||||
# intent, not the skill's internal mechanics.
|
||||
# Budget: 250 characters target, 400 hard ceiling (counting this value only,
|
||||
# with YAML folding resolved). This scaffold sits at 214 — keep the fill-in
|
||||
# under the target rather than growing past it.
|
||||
# Boundary clauses may be plural: write one per genuine near-miss, and none
|
||||
# where no sibling could steal activations.
|
||||
# with YAML folding resolved). Keep the fill-in under the target rather
|
||||
# than growing past it.
|
||||
# Never let a hyphenated skill name wrap across two lines of this folded block
|
||||
# — folding turns the break into a space and the routing target stops resolving.
|
||||
# Banned here: capability lists, output-format detail, composition notes,
|
||||
@@ -89,7 +87,7 @@ metadata:
|
||||
after the mistake is worthless.
|
||||
|
||||
Each entry states a fact that CONTRADICTS a reasonable default:
|
||||
something the agent gets wrong by acting sensibly. Maximum 5 entries.
|
||||
something the agent gets wrong by acting sensibly. Aim for at most five; more is a SUGGESTION.
|
||||
An entry that paraphrases a step below it is a failure, not a gotcha.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sources
|
||||
|
||||
<!-- Populated at Step 5 of skill authoring, after all skill files are written.
|
||||
<!-- Populated at Step 6 of skill authoring (`references/create.md`), after all skill files are written.
|
||||
For each research source with status `extracted`, record which skill files
|
||||
it contributed to under Contributing files.
|
||||
Delete this file if no research sources were provided as input. -->
|
||||
|
||||
@@ -126,16 +126,16 @@ blocks, rationale prose, and any content only one branch reaches. Each reference
|
||||
self-contained for its concern, and every one is wired from the body with the literal conditional
|
||||
form:
|
||||
|
||||
````markdown
|
||||
If <condition>, read `references/<file>.md`.
|
||||
````
|
||||
|
||||
**The one exception, stated once so it is not re-litigated:** an output schema stays in the body
|
||||
only when it applies to *every* flow and is short — roughly 50 words or less, which is the "Output
|
||||
format template" pattern below. An output schema that is longer than that, or that only one flow
|
||||
produces, moves to `references/` like any other schema. No third option exists, and the two rules
|
||||
do not disagree.
|
||||
|
||||
````markdown
|
||||
If <condition>, read `references/<file>.md`.
|
||||
````
|
||||
|
||||
A generic pointer ("see references/ for details") is a Vale error — the agent cannot act on it.
|
||||
|
||||
**A dispatch table is the wiring.** Where the body dispatches, a row already pairs a condition with
|
||||
|
||||
@@ -100,8 +100,9 @@ plain sentence and `disable-model-invocation: true` instead.
|
||||
|
||||
**`metadata.version`** — required on every skill (ADR-0022), not a per-skill or per-plugin choice,
|
||||
and enforced by the `skill-size-check` pre-commit hook. The scaffold seeds a new skill at
|
||||
`"0.1.0"`; leave that value alone here and let `SKILL.md` Step 4 bump it. (`"1.0.0"` is the seed
|
||||
for a pre-existing skill retrofitted into the rule, and never applies to a skill created here.)
|
||||
`"0.1.0"`; leave that value alone — `SKILL.md` Step 4 leaves it at `"0.1.0"` too, which ADR-0022
|
||||
reserves for "created and never yet revised". (`"1.0.0"` is the seed for a pre-existing skill
|
||||
retrofitted into the rule, and never applies to a skill created here.)
|
||||
|
||||
**Optional frontmatter** — uncomment and fill in, or remove entirely:
|
||||
|
||||
|
||||
@@ -10,13 +10,9 @@ source_keys:
|
||||
Return to `SKILL.md` Step 4 once Step 4 below is done — validation, versioning and commit
|
||||
verification are shared with the create flow and are not repeated here.
|
||||
|
||||
## Step 1 — Verify inputs
|
||||
## Step 1 — Signal sources
|
||||
|
||||
Confirm the skill directory path exists and that at least one improvement signal is present in the
|
||||
conversation or a referenced file.
|
||||
|
||||
If the skill directory is missing, ask for it. If no signals are present, stop: "This skill applies
|
||||
existing signals to a skill. For a blind review without signals, use `/factory-audit` instead."
|
||||
The dispatch in `SKILL.md` Step 1 has already confirmed the directory and at least one signal.
|
||||
|
||||
Signals can come from anywhere in the conversation or referenced files:
|
||||
|
||||
@@ -25,8 +21,6 @@ Signals can come from anywhere in the conversation or referenced files:
|
||||
- Human feedback (feedback.json, inline in conversation, PR or issue comments)
|
||||
- Session context describing what went wrong
|
||||
|
||||
Also verify the `name` field in frontmatter matches the skill's directory name exactly.
|
||||
|
||||
## Step 2 — Gather and group signals
|
||||
|
||||
Read the current skill files (SKILL.md and any files in `scripts/`, `references/`, `assets/`,
|
||||
@@ -102,6 +96,9 @@ exactly one flow reaches it, otherwise in the body's common-gates section.
|
||||
If a signal points to a script or reference file, edit that file directly rather than adding a
|
||||
workaround in SKILL.md.
|
||||
|
||||
**Do not create a new script unless a signal explicitly calls for it.** Writing one from scratch
|
||||
requires out-of-scope transcript analysis — flag the opportunity as a suggestion instead.
|
||||
|
||||
**A skill carrying no `metadata.version` is seeded at `"1.0.0"`, not bumped** — `SKILL.md` Step 4's
|
||||
patch bump presumes a version to bump, and ADR-0022 reserves `"0.1.0"` for a newly created skill.
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ Output:
|
||||
Standalone mode: <path>/<skill-name>/
|
||||
|
||||
Exit codes:
|
||||
0 Scaffold created successfully, or destination already exists (no-op)
|
||||
0 Scaffold created, destination already complete (no-op), or a partial
|
||||
scaffold from an earlier failed run repaired
|
||||
1 Invalid arguments, missing path, or templates not found
|
||||
EOF
|
||||
}
|
||||
@@ -152,20 +153,56 @@ else
|
||||
TARGET="$TARGET_INPUT/$SKILL_NAME"
|
||||
fi
|
||||
|
||||
# Destination already exists — treat as a no-op so retries are safe
|
||||
# Files carrying the SKILL_NAME placeholder token.
|
||||
SUBST_FILES=("SKILL.md" "tests/README.md")
|
||||
|
||||
# Replace SKILL_NAME in each placeholder file under dir $1. `sed -i` is not
|
||||
# portable — GNU takes an optional attached suffix, BSD/macOS requires a
|
||||
# separate suffix argument and reads the expression as one — so write to a
|
||||
# temp file and move it over the original instead.
|
||||
substitute_name() {
|
||||
local dir="$1" rel f
|
||||
for rel in "${SUBST_FILES[@]}"; do
|
||||
f="$dir/$rel"
|
||||
[[ -f "$f" ]] || continue
|
||||
sed "s/SKILL_NAME/$SKILL_NAME/g" "$f" > "$f.tmp"
|
||||
mv "$f.tmp" "$f"
|
||||
done
|
||||
}
|
||||
|
||||
# True if any placeholder file under dir $1 still carries the SKILL_NAME token.
|
||||
has_placeholder() {
|
||||
local dir="$1" rel
|
||||
for rel in "${SUBST_FILES[@]}"; do
|
||||
if [[ -f "$dir/$rel" ]] && grep -q 'SKILL_NAME' "$dir/$rel"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ -d "$TARGET" ]]; then
|
||||
# A scaffold left half-built by an earlier failed run still carries the
|
||||
# placeholder token; finish it instead of reporting a silent no-op.
|
||||
if has_placeholder "$TARGET"; then
|
||||
substitute_name "$TARGET"
|
||||
echo "Repaired partial scaffold at '$TARGET' — substituted SKILL_NAME." >&2
|
||||
exit 0
|
||||
fi
|
||||
echo "Scaffold already exists at '$TARGET' — nothing to do." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$TARGET")"
|
||||
|
||||
# Copy templates to destination
|
||||
cp -r "$TEMPLATES_DIR" "$TARGET"
|
||||
|
||||
# Set skill name in templates
|
||||
sed -i "s/SKILL_NAME/$SKILL_NAME/g" "$TARGET/SKILL.md"
|
||||
sed -i "s/SKILL_NAME/$SKILL_NAME/g" "$TARGET/tests/README.md"
|
||||
# Build in a sibling staging directory and rename it into place only once
|
||||
# complete, so a failure mid-build never leaves a half-built $TARGET behind.
|
||||
STAGING="$TARGET.partial.$$"
|
||||
trap 'rm -rf "$STAGING"' EXIT
|
||||
cp -r "$TEMPLATES_DIR" "$STAGING"
|
||||
substitute_name "$STAGING"
|
||||
mv "$STAGING" "$TARGET"
|
||||
trap - EXIT
|
||||
|
||||
if [[ "$MODE" == "package" ]]; then
|
||||
echo "Mode: package — type-bearing apm.yml found at '$PKG_ROOT'" >&2
|
||||
|
||||
@@ -107,6 +107,31 @@ teardown() {
|
||||
assert_output --partial "nothing to do"
|
||||
}
|
||||
|
||||
@test "no SKILL_NAME placeholder remains anywhere in a fresh scaffold" {
|
||||
bash "$SCRIPT" my-tool "$DEST"
|
||||
run grep -r "SKILL_NAME" "$DEST/my-tool"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "leaves no staging directory behind after a successful run" {
|
||||
bash "$SCRIPT" my-tool "$DEST"
|
||||
run bash -c "ls -d '$DEST'/my-tool.partial.* 2>/dev/null"
|
||||
assert_output ""
|
||||
}
|
||||
|
||||
@test "retry repairs a half-built scaffold that still carries SKILL_NAME" {
|
||||
# Simulate an earlier run that copied the templates but died before the
|
||||
# name substitution: the retry must finish the job, not no-op.
|
||||
cp -r "$BATS_TEST_DIRNAME/../assets/templates" "$DEST/my-tool"
|
||||
run bash "$SCRIPT" my-tool "$DEST"
|
||||
assert_success
|
||||
assert_output --partial "Repaired partial scaffold"
|
||||
run grep -r "SKILL_NAME" "$DEST/my-tool"
|
||||
assert_failure
|
||||
run grep -E '^name: my-tool$' "$DEST/my-tool/SKILL.md"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mode detection: package vs standalone
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user