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:
2026-09-28 20:50:22 +00:00
parent df28351d3e
commit 965208bddd
29 changed files with 462 additions and 156 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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. -->

View File

@@ -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

View File

@@ -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:

View File

@@ -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.

View File

@@ -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

View File

@@ -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
# ---------------------------------------------------------------------------