17 Commits

Author SHA1 Message Date
aff5b6c4c8 chore(plugins): patch-bump bin, kyberforge and lint for shipped content changes
The round-3 fixes changed shipped skill content in three plugins without
touching their manifests, so installed copies would keep serving the old
content from cache. plugin-author requires a patch bump for exactly this
reason: consumers use the version to detect changes.

It matters most for lint — anyone installed at 1.1.2 has a cached
vale-run/SKILL.md stating that Vale exits non-zero on warnings, which is
backwards and would lead them to build a gate that passes everything.

- bin        1.1.0 -> 1.1.1  (caveman: suppression comments removed)
- kyberforge 1.2.3 -> 1.2.4  (skill-audit/agent-audit: Vale step reworked)
- lint       1.1.2 -> 1.1.3  (vale-run: exit-code and suppression-syntax fixes)

Marketplace entries carry no per-plugin version, so both marketplace.json
files are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCQ648fLSFXPHGZdQ8gn58
2026-08-08 20:45:13 +00:00
149d564f6a fix(lint): make the Vale gate actually gate, drop VagueQualifier
Round-3 review of PR #85 found the "enforcing" pre-commit hook enforced
nothing. Vale's exit code keys on error-level alerts alone: five of the
six rules were level: warning, so they exited 0, and pre-commit hides
output from a passing hook — the alerts were invisible and blocked
nothing. ADR-0013 rejected a report-only trial tier and then shipped one
by accident.

Flatten every rule to level: error. Vale's own exit code is then correct,
so the hook entry drops to a bare vale-wrap.sh call and the graded
error->FAIL / warning->SUGGESTION mapping disappears from both audit
skills: every alert is a FAIL, in the gate and the audit alike. No
ignorable tier, matching shellcheck, the test suite and
conventional-pre-commit.

Delete Kyberforge.VagueQualifier. Measured against the 41 skill/agent
files as they stood before the rule ever ran: 2 hits. One marginal
("very different" -> "fundamentally different"), one an unfixable false
positive — caveman/SKILL.md quotes "of course" as an example of filler,
a mention not a use — which forced the only Vale suppression comments in
the repo. Those four lines go with it; two of them were dead anyway,
suppressing a frontmatter-scoped rule on a body line. Held-out prose (273
files) fired 15 times, 9 inside out-of-scope research examples and the
rest one word in two idioms in a single doc. SentenceOpenerThereIs
survives: 22 held-out hits, both in-corpus hits clean rewrites, zero
suppressions.

Widen .vale.ini's globs to [**/SKILL.md], [**/agents/*.md] and
[**/*.agent.md]. The plugins/*/-prefixed globs scoped nothing — Vale's *
crosses /, so they already matched docs/research/examples/**/agents/*.md
and assets/templates/SKILL.md, the two paths CONTEXT.md claimed they
excluded. Scoping is and was the hook's files: regex. The old globs also
hid a silent false negative: a skill outside plugins/ matched no section,
so Vale reported 0 files and exited 0, which both audits read as clean.
They now treat a 0-file run as NOT RUN and fall back to full judgment.

Also:
- vale-wrap.sh resolves relative --config values and file arguments
  against the caller's cwd, as vale does, instead of the repo root, which
  hard-errored from a subdirectory and silently skipped flattening for
  file args that did not resolve from the root. Absolute paths inside the
  cwd are relativized so reports cite resolvable paths, not scratch ones.
- vale-run's exit-code model was documented backwards ("exits non-zero
  whenever it finds an alert at or above MinAlertLevel") and would have
  led anyone following it to build a gate that passes everything. Its
  Markdown suppression syntax was MDX-only and does not suppress in .md;
  corrected in the skill and its troubleshooting reference, with
  backtick/fence exemption documented as the first resort.
- skill-size-check.sh fails only above 500 lines, agreeing with
  skill-audit's validate.sh <= 500 pass.
- ADR-0013 and CONTEXT.md amended to match, recording why graded
  severities cannot gate.

Verified: 9 test scripts / 15 vale-wrap cases pass; vale-audit-prefilter,
skill-size-check and shellcheck pass --all-files; check-manifests and
claude plugin validate --strict clean. New tests fail against the old
script (3 of them) and pass against the new one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCQ648fLSFXPHGZdQ8gn58
2026-08-08 20:42:15 +00:00
210b192613 docs(lint): add docs index for Vale research docs
plugins/lint/docs/research/docs/vale/ had no top-level index pointing
into it, unlike plugins/kyberforge/docs/README.md which indexes its
own research directories. Add plugins/lint/docs/README.md mirroring
that convention: one line per file describing what it covers, plus a
provenance note tying the directory back to plugins/lint/sources.md
and the vale-config/vale-run skills that consume it.

Closes out a follow-up item from PR #85's review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCQ648fLSFXPHGZdQ8gn58
2026-08-08 20:21:21 +00:00
792d3e1852 fix(lint): resolve round-1 and round-2 review findings on the Vale prefilter
Addresses PR #85's outstanding review items after grilling the open
questions against ADR-0013/CONTEXT.md/ADR-0010:

Blocking fixes:
- vale-wrap.sh: replace json.dumps() escaping (which silently defeated
  Vale's frontmatter scope on any description containing a quote,
  backslash, or non-ASCII char — ~58% of the corpus) with a single-quoted
  YAML scalar, substituting a Unicode right single quote for embedded
  apostrophes rather than '' doubling (Vale's frontmatter scanner isn't a
  full YAML parser and silently truncates on '' too).
- vale-wrap.sh: fix a blank-line-inside-a-folded-description truncation
  bug via indentation-based, blank-line-tolerant body capture; narrow
  flattening to `>`-style scalars only (`|` already works unflattened).
- skill-audit/agent-audit Step 1: make the vale-wrap.sh invocation
  cwd-independent via git rev-parse --show-toplevel, fixing a bug where
  no single cwd satisfied all three Step 1 commands.
- styles/Kyberforge/VagueQualifier.yml: prune 17 tokens verified
  false-positive-dominated on this repo's own voice via a real corpus
  sweep (obvious, clearly, usually, several, simple, easy, completely,
  simply, tiny, etc.), keep 13 with real or unattested noise. Revert the
  28 prose "fixes" those tokens drove across 14 skill files back to their
  original, correct wording, including a functional regression to
  caveman/SKILL.md's own filler-word list (a mention, not a use) — now
  guarded with vale-off comments against recurrence.

Gaps:
- --minAlertLevel=warning on the pre-commit hook and Step 1 invocation
  so warning-level rules actually surface, without collapsing the
  FAIL/SUGGESTION severity mapping skill-audit/agent-audit rely on.
- vale-wrap.sh: fix --config=<path> equals-form, absolute-path silent
  no-op, and a zero-file-argument stdin hang.
- Route vale-run and lint-runner through a documented wrapper script
  when a target repo has one, instead of unconditionally recommending
  bare `vale`.
- Wire Kyberforge.VagueQualifier/SentenceOpenerThereIs into skill-audit/
  agent-audit's dimension-mapping prose (Body discipline).
- Add plugins/lint/sources.md provenance for lint-runner (ADR-0010).
- Sync both marketplace.json lint-entry descriptions with plugin.json.
- Retune skill-size-check.sh's MAX_WORDS 5000->2900 (measured ~1.6-1.7
  tokens/word on this repo's corpus, the old value gated at ~8,500
  tokens against a stated 5,000 ceiling); fix the >/>= line-count
  boundary and wc -l undercount on files with no trailing newline.
- Document the vale binary as a Setup prerequisite in AGENTS.md.
- Fix SentenceOpenerThereIs's dead regex alternative and add a real
  sentence-start anchor/scope.
- Fix a stale docs/research/docs/vale/ index pointer in kyberforge's
  docs README (moved to plugins/lint/ in e1a5403).
- Rewrite ADR-0013's Consequences section past-tense to describe what
  actually landed, and record the styles-portability limitation
  (repo-root placement stays intentional; deferred to a separate
  session per this PR's review).

Test coverage: 9 new vale-wrap.sh fixtures (quotes, backslash/unicode,
blank-line paragraphs, --config= form, zero-arg/absolute-path handling,
literal-block no-regression) and boundary-pair tests for
skill-size-check.sh's line/word ceilings.

bash tests/run-tests.sh: 9 scripts + 125 bats assertions, all passing.
scripts/check-manifests.sh and claude plugin validate --strict: clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCQ648fLSFXPHGZdQ8gn58
2026-08-08 20:21:21 +00:00
3324a73225 feat(lint): expand Vale audit prefilter into a broader plugin-content harness
Deferred item from PR #85 review. Per ADR-0013: cherry-picks two low-noise
rules from trialing write-good/alex against the real corpus (VagueQualifier,
SentenceOpenerThereIs) into styles/Kyberforge rather than adopting either
package wholesale (both are tuned for blog prose and were noisy on this
repo's terse, imperative instruction files - see the ADR's rejected-rule
list). Adds a new skill-size-check pre-commit hook enforcing agentskills.io's
500-line/5,000-token SKILL.md ceiling, currently unenforced. Fixes the 28
resulting violations across 20 existing SKILL.md/agent files so the
enforcing pre-commit hook lands clean.

governance.md/CONTROLS.md were evaluated and excluded as rule sources -
they're org/CI-infrastructure controls, not prose patterns Vale can express.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUDczvw1H3eEeMD29Q9Lbi
2026-08-08 20:20:58 +00:00
544392be98 refactor(lint): genericize lint-runner dispatch and manifest wording
lint-runner's description already promised other linters could be added
without changing its own contract, but Process hardcoded vale-config/
vale-run and .vale.ini by name. Switch to <linter>-config/<linter>-run
naming-convention dispatch so the promise holds. Drop the explicit
Vale callout from the plugin manifests' description/keywords to match.

Addresses a deferred item from PR #85 review.
2026-08-08 20:20:58 +00:00
bbb0dcd21a fix(lint): flatten multi-line frontmatter descriptions before Vale runs
Vale's text.frontmatter.description scope silently stops matching once
the description is a YAML block scalar spanning 2+ physical lines —
the style used by most skills/agents in this repo. scripts/vale-wrap.sh
flattens the description to one line in a scratch copy (preserving the
repo-relative path and total line count) before invoking real vale, and
both audit skills plus the pre-commit hook now call it instead of vale
directly. Also tightens the pre-commit hook's file glob to single path
segments so it can't cross into docs/research examples or asset
templates the way the audit skills' scoped invocations already avoid.

Addresses PR #85 review feedback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
8d56290414 feat(lint): wire Vale as commit-stage pre-commit hook
Adds vale-audit-prefilter as a local pre-commit hook scoped to skill/agent
markdown files, matching the invocation pattern skill-audit/agent-audit
already use. Runs at commit-stage only since it's a fast deterministic
prefilter; push-stage already covers the full test suite and manifest checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
cbc33d952e feat(kyberforge): wire Vale as deterministic prefilter for skill-audit/agent-audit
Adds repo-root .vale.ini plus a custom Kyberforge style (description-opener,
vague-wording, and generic reference-pointer padding rules) and a
KyberforgeCopilot style scoped to .agent.md files (Use proactively check).
skill-audit and agent-audit Step 1 now run vale against the specific file(s)
being audited and defer the corresponding Description/Patterns/Body checks
to its output instead of re-deriving them by LLM judgment, per the split
proposed in issue #84.

Closes #84

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
f326df4861 chore(lint): register lint plugin in marketplace and document scope
Adds the lint plugin entry to both marketplace manifests and records
the resolved scope/structure decisions from grilling in CONTEXT.md:
standalone repo-agnostic plugin, split vale-config/vale-run skills,
report-only lint-runner agent, audit-pipeline wiring deferred.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
57bdfa92e8 fix(lint): resolve audit findings on vale skills
Merge duplicate gotcha in vale-config (Packages vs BasedOnStyles was
stated twice) and align vale-run's category field with vale-config's
(lint, not linting) so sibling skills in the plugin agree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
59ad2a3cbd feat(lint): add lint-runner agent
Report-only agent that composes vale-config/vale-run to run a lint
sweep over a scope and return normalized findings — no Edit tool, it
flags issues rather than fixing them. Also lands the plugin manifest
scaffold (plugin.json, .claude-plugin/plugin.json) that the earlier
vale-config/vale-run skill commits assumed but didn't carry, bumped
to 1.1.0 for the new agent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
8b00728374 feat(lint): add vale-run skill
Covers invoking the vale CLI and interpreting its output — output
formats, severity filtering, exit-code handling, and false-positive
triage — for an already-configured project.
2026-08-08 20:20:58 +00:00
d1afdbeff7 feat(lint): add vale-config skill
Covers Vale install and .vale.ini setup — StylesPath, built-in/
third-party/custom styles, BasedOnStyles activation. Setup half of
Vale support; vale-run (running/interpreting) is a separate skill.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
2026-08-08 20:20:58 +00:00
5e22672189 docs(kyberforge): add vale.sh research docs
Prep work for issue #84 - gathers Vale (vale.sh) config, styles/rules,
CLI, installation, and troubleshooting reference material into
plugins/kyberforge/docs/research/docs/vale/ alongside the existing
research topics.

Refs #84
2026-08-08 20:20:58 +00:00
0ba8a95188 Merge pull request 'docs(agents-md): shrink AGENTS.md and prefer plugin skills over shell' (#86) from refactor/agents-md-prefer-plugin-skills into main
Reviewed-on: https://git.dev.rkdr.net/Defame1297/holocron/pulls/86
Reviewed-by: Defame1297 <gitea@rkdr.net>
2026-07-24 21:24:28 +00:00
533364029a docs(agents-md): shrink AGENTS.md and prefer plugin skills over shell
AGENTS.md had grown to duplicate content owned elsewhere: behavioral
rules already active globally via ~/.agents/AGENTS.md, a VISION.md
read-on-demand entry CONTEXT.md already covers at session start, and
setup/testing/commit instructions that explained hook mechanics the
git plugin's pc-run/git-commits skills already own. It also gave no
explicit steer toward using installed plugin skills over raw shell
commands, so agents defaulted to shelling out to git directly.

- Added a "Prefer plugin skills over raw shell" section mapping
  operations (commits, branches, hooks, issues/PRs, linting, AGENTS.md
  itself) to the skill that owns them.
- Collapsed Setup/Testing/Commit-conventions into one section, keeping
  only the two genuinely non-obvious gotchas (missing
  default_install_hook_types, bats submodule auto-init).
- Removed the "Subagent orchestration" section: its content was mostly
  universal Agent/Task/worktree-tool facts, not specific to working in
  this repo, so it moves to core/instructions/subagent-orchestration.md
  (deployed globally via install.sh, referenced from core/AGENTS.md's
  content index) rather than staying repo-local.
- Removed agentsmd-author's "not this repo's own" scope exclusion in
  CONTEXT.md (ADR-0012 never mandated it) so this task could route
  through it, and folded the forge-routing rule it left behind into
  CONTEXT.md's existing Skill composition entry.

AGENTS.md: 50 -> 40 lines. Full test suite and manifest check pass.
2026-07-24 21:19:17 +00:00
27 changed files with 299 additions and 169 deletions

View File

@@ -111,7 +111,7 @@ repos:
stages: ['pre-commit']
name: Vale audit prefilter
description: Run Vale against skill/agent markdown files as a deterministic prefilter for skill-audit/agent-audit
entry: scripts/vale-wrap.sh --config .vale.ini --minAlertLevel=warning
entry: scripts/vale-wrap.sh --config .vale.ini
language: script
files: '^plugins/[^/]+/(skills/[^/]+/SKILL\.md|agents/[^/]+\.md)$'
pass_filenames: true

View File

@@ -1,11 +1,10 @@
StylesPath = styles
MinAlertLevel = suggestion
[plugins/*/skills/*/SKILL.md]
[**/SKILL.md]
BasedOnStyles = Kyberforge
[plugins/*/agents/*.md]
[**/agents/*.md]
BasedOnStyles = Kyberforge
[plugins/*/agents/*.agent.md]
[**/*.agent.md]
BasedOnStyles = Kyberforge, KyberforgeCopilot

View File

@@ -1,27 +1,31 @@
# Working in this repo
This repo is the global AI development configuration repository — the authoritative source for agent definitions, skills, workflows, and prompts across all projects.
This repo is the global AI development configuration repository — the authoritative source for agent definitions, skills, workflows, and prompts across all projects. Built as a homelab tool intended to scale to professional environments.
## Structure
- `plugins/` — installable plugin units; each is self-contained (skills, agents, hooks, MCP servers, bundled assets); install separately via `claude plugin install <name>@holocron`
- `providers/claude-code/` — Claude Code adapter (deployed to `~/.claude/` via `install.sh`)
## Setup
## Prefer plugin skills over raw shell
- Install git hooks: `pre-commit install -t pre-commit -t pre-push -t commit-msg`. `.pre-commit-config.yaml` uses all three stages and has no `default_install_hook_types` set, so a plain `pre-commit install` only wires the `pre-commit` stage and silently skips `commit-msg` (Conventional Commits check) and `pre-push` (test suite, manifest check).
- Install the `vale` binary — required by the `vale-audit-prefilter` pre-commit hook, which runs on every commit touching a `SKILL.md` or agent `.md` file. Without it, the hook fails with a bare "command not found" and no install pointer. Install via a package manager (`brew install vale` on macOS, `snap install vale` on Linux, `choco install vale` on Windows) or see https://vale.sh/docs/vale-cli/installation/; then run `vale sync` to pull the styles declared in `.vale.ini`.
This repo dogfoods its own plugins. Before shelling out to git, gitea, or lint tooling directly, check whether an installed skill already owns the operation — it usually does:
## Testing instructions
- Commits, branches, history, worktrees, remotes → `git:git-commits`, `git:git-branches`, `git:git-history`, `git:git-worktrees`, `git:git-remotes`
- Pre-commit hook install/config/troubleshooting → `git:pc-run` / `git:pc-author`
- Issues, PRs, labels, milestones → `bin:gitea`
- Vale prose linting → `lint:vale-config` / `lint:vale-run`
- This repo's own AGENTS.md → `core:agentsmd-author` / `core:agentsmd-audit`
- Run `bash tests/run-tests.sh` before considering any change done, and fix failures — it runs every `test-*.sh` script in the repo plus the bats suite (`tests/run-bats.sh`).
- `bash tests/run-tests.sh --bats-only` runs just the bats suite.
- The bats suite auto-initializes its submodules (`tests/bats`, `tests/test_helper/bats-support`, `tests/test_helper/bats-assert`) on first run if the `bats` binary is missing — no manual `git submodule update` needed.
Fall back to raw shell only when no skill covers it.
## Commit / PR conventions
## Setup and testing
- Commit messages must follow Conventional Commits — enforced by the `conventional-pre-commit` hook at the `commit-msg` stage.
- Pushing runs the full test suite (`tests/run-tests.sh`) and `scripts/check-manifests.sh` (validates `marketplace.json`/`plugin.json` paths resolve) via pre-push hooks — run both locally first so a failing push isn't a surprise.
- Install git hooks via `git:pc-run`, wiring all three stages — this repo's `.pre-commit-config.yaml` has no `default_install_hook_types`, so a plain install silently skips `commit-msg` (Conventional Commits) and `pre-push` (tests, manifest check).
- Install the `vale` binary — required by the `vale-audit-prefilter` pre-commit hook, which runs on every commit touching a `SKILL.md` or agent `.md` file. Without it the hook fails with a bare "command not found" and no install pointer. `brew install vale` (macOS), `snap install vale` (Linux), `choco install vale` (Windows), or see https://vale.sh/docs/vale-cli/installation/. No `vale sync` needed — the `Kyberforge` styles are committed under `styles/`, not downloaded packages.
- Run `bash tests/run-tests.sh` before considering any change done — it runs every `test-*.sh` script in the repo plus the bats suite (`--bats-only` for just bats). First run auto-initializes the bats submodules; no manual `git submodule update` needed.
- Pushing re-runs the full suite plus `scripts/check-manifests.sh` via the pre-push hook — same commands, so run them locally first.
- Author commits with `git:git-commits` — it validates Conventional Commits (enforced at `commit-msg`) for you.
## Key documents
@@ -29,23 +33,9 @@ Read CONTEXT.md at the start of every session in this repo.
Read these on demand:
- `docs/VISION.md` — purpose, goals, and long-term Management Application vision
- `docs/spec/architecture.md` — current directory structure, install pipeline, provider model
- `docs/adr/` — architectural decisions; read before answering design questions or proposing structural changes
- `docs/ai-constitution.md` — full governance evidence base; read when a governance decision needs justification
- `docs/research/ai-coding-factory/ai-coding-factory-principles.md` — factory design rationale; read when implementing, auditing, or reviewing skills or factory structure
- `docs/notes/factory-integration-decisions.md` — decisions from the factory integration grill; read when making skill authoring or factory design decisions
- Governance rules are always in effect — `core/instructions/governance.md` (agent rules); `docs/research/governance_principles/CONTROLS.md`
## Working context
This repo is built by a junior developer as a homelab tool intended to scale to professional environments. Challenge ideas and reference industry standards rather than validate assumptions. Explain the why behind decisions — assume the user is learning, not just executing. Flag significant actions before taking them.
## Subagent orchestration
- **Forks stop when their assigned task is done.** A `fork` inherits the coordinator's full context, including visibility into any shared TaskList. That visibility is not license to keep going: once a fork's assigned task is reported complete, it must stop rather than autonomously picking up further items from the list. Forks that keep pulling work race against the coordinator's own orchestration and can duplicate or conflict with tasks the coordinator has separately delegated.
- **Don't hand a fork a TaskList that includes governance-gated actions** (push, publish, merge) unless you are prepared for it to act on those items without a fresh confirmation round. A fork acting on its own initiative is not party to any pending human confirmation the coordinator is mid-flow on, so it can bypass a gate that was meant to hold.
- **`TaskGet`/`TaskUpdate`/`TaskList` only work for forks.** Fresh (non-fork) subagents cannot discover or call these tools. When delegating to a fresh subagent, the coordinator owns all task-list bookkeeping itself — claim and complete the entry on the agent's behalf — rather than instructing the fresh agent to self-claim or self-complete.
- **Worktree/branch cleanup is part of closing out the PR, not a separate step.** When a coordinator creates a worktree (`Agent(isolation: "worktree")` or `git worktree add` directly) to land a PR, merging that PR is not the end of the task. Immediately after verifying the merge: run `git worktree remove --force --force <path>` (the double `-f` is required whenever the worktree initialized submodules to run tests — assume it did, this repo has several), then `git branch -d` both the feature branch and any `worktree-agent-<id>` isolation branch the `Agent` tool auto-created for that worktree — `git worktree remove` deletes neither branch on its own. Do this without waiting for the user to notice stale branches/worktrees and ask.
- **`Agent(isolation: "worktree")` may fork from `main`, not the branch you were on.** Don't assume the isolated worktree is based on your current branch just because that's what you asked for — in practice it has forked from a stale `main` (missing commits the task depended on) even when the coordinator was on a feature branch at call time. Every affected agent has to notice (missing files, unexpected diff base) and self-correct with `git merge --ff-only <target-branch>` or a reset onto `origin/<target-branch>` before it can safely edit. Don't leave this to chance: tell the agent explicitly in the prompt which branch its worktree must be based on and to verify/rebase onto it as a first step before editing anything, and check for this yourself when reviewing a worktree agent's report.
- **Don't route already-fully-specified corrective edits through `kyberforge:forge`.** `forge` exists to classify ambiguous "what should I build" intent before routing to an author skill — it isn't needed when the coordinator already knows the exact file, line, and fix. Sending fully-specified fixes through `forge` adds a grill-and-delegate layer that can itself spawn further sub-delegation (forked sub-subagents), which has been observed to lose track of hard constraints handed down the chain (e.g. "don't commit yet," "edit in this worktree") because each hop re-derives instructions from a shorter brief. Call the target author skill (`skill-author`, `plugin-author`, etc.) directly for known fixes; reserve `forge` for genuinely undecided "which artifact type is this" questions.

View File

@@ -49,7 +49,7 @@ The provider-agnostic always-on instruction entry point. Two files:
Contains always-on rules in plain markdown with no provider-specific syntax (no `@import`). Provider-specific files (`CLAUDE.md`) are thin adapters that import the relevant `AGENTS.md` and add only Claude Code-specific syntax. This pattern means a single source of truth can serve multiple providers without duplication. See ADR-0003.
### Skill composition
A skill calling another skill by name to delegate a sub-task. The calling skill focuses on the orchestration decision ("when to do X"); the called skill owns the mechanics ("how to do X"). Established compositions: `grill-me` calls `write-adr` when a decision crystallises; `implement-feature` calls `tdd` as its implementation methodology; `forge` calls `grill-with-docs` to refine intent, classifies the target artifact type (skill / agent / plugin / marketplace entry), then routes to the matching `*-author` skill — which owns its own create/improve logic and, where applicable, its own inline audit closeout (`skill-author` runs `/skill-audit`, `agent-author` runs `kyberforge:agent-audit`, both in the same context as the authoring work). `forge` additionally runs its own independent recheck after a skill/agent route finishes: a clean-context subagent (not forked, no inherited context) re-runs the same audit skill against the finished artifact, as a distinct verification layer from the author skill's inline audit — the two can share blind spots since the inline audit runs in the same context as the work it checks. If the clean audit surfaces any unresolved finding, `forge` loops — re-invoke the author skill to resolve it, re-run the clean audit — until the clean audit comes back with nothing unresolved; only then is the route done. `plugin-author` and `marketplace-author` have no audit counterpart and get no recheck; their terminal check is `claude plugin validate`.
A skill calling another skill by name to delegate a sub-task. The calling skill focuses on the orchestration decision ("when to do X"); the called skill owns the mechanics ("how to do X"). Established compositions: `grill-me` calls `write-adr` when a decision crystallises; `implement-feature` calls `tdd` as its implementation methodology; `forge` calls `grill-with-docs` to refine intent, classifies the target artifact type (skill / agent / plugin / marketplace entry), then routes to the matching `*-author` skill — which owns its own create/improve logic and, where applicable, its own inline audit closeout (`skill-author` runs `/skill-audit`, `agent-author` runs `kyberforge:agent-audit`, both in the same context as the authoring work). Reserve `forge` for genuinely undecided "which artifact type is this" questions — an already-fully-specified corrective edit (exact file, line, and fix already known) should call the target author skill directly instead (`skill-author`, `plugin-author`, `agentsmd-author`, etc.); routing a known fix through `forge`'s grill-and-classify layer adds unnecessary indirection and, in practice, has been observed to lose track of hard constraints handed down the chain (e.g. "don't commit yet," "edit in this worktree") because each hop re-derives instructions from a shorter brief. `forge` additionally runs its own independent recheck after a skill/agent route finishes: a clean-context subagent (not forked, no inherited context) re-runs the same audit skill against the finished artifact, as a distinct verification layer from the author skill's inline audit — the two can share blind spots since the inline audit runs in the same context as the work it checks. If the clean audit surfaces any unresolved finding, `forge` loops — re-invoke the author skill to resolve it, re-run the clean audit — until the clean audit comes back with nothing unresolved; only then is the route done. `plugin-author` and `marketplace-author` have no audit counterpart and get no recheck; their terminal check is `claude plugin validate`.
### Provider-agnostic issue tracker
Skills and workflows reference "linked issue" generically rather than a specific provider. Gitea is the canonical issue tracker for this repo (see ADR-0017). "Issue" is the cross-provider term (GitHub, GitLab, Gitea all use it).
@@ -61,7 +61,7 @@ The three-stage traceability record linking a skill back to its research inputs:
Files that reference other files should declare those references explicitly. The referencing file carries the forward reference (e.g. content index in `CLAUDE.md`, `references:` in frontmatter). The referenced file carries a `when:` field describing when it is loaded. Both sides should agree — divergence signals staleness. The reverse map ("what files reference this file?") is derived by a reference scanner script, not maintained manually. This principle applies to instruction files, skills, and workflow documents.
### agentsmd-author / agentsmd-audit
A skill pair in the `core` plugin for writing, updating, and reviewing a target repo's `AGENTS.md` file(s) (the generic open-standard file — see the `AGENTS.md` entry above — not this repo's own). `agentsmd-author` creates/updates AGENTS.md content, supports nested monorepo placement (per the standard's nearest-file-wins precedence), and closes out by invoking `agentsmd-audit` inline. `agentsmd-audit` runs a single combined pass checking three mandatory baselines: secrets/credentials (governance.md hard prohibition — AGENTS.md is committed content), structural completeness (common-sections checklist from the agents.md spec), and accuracy/drift (do referenced commands and paths actually resolve against the repo). `agentsmd-audit` never inspects provider adapter files (see `provider-adapter-author`) — its scope is AGENTS.md content only. Chosen over folding this into `kyberforge` because kyberforge's scope is meta-tooling for the holocron marketplace itself, not generic target-repo documentation; `core` is the intended home for cross-cutting, repo-agnostic utility skills.
A skill pair in the `core` plugin for writing, updating, and reviewing a repo's `AGENTS.md` file(s) — the generic open-standard file (see the `AGENTS.md` entry above), including this repo's own. `agentsmd-author` creates/updates AGENTS.md content, supports nested monorepo placement (per the standard's nearest-file-wins precedence), and closes out by invoking `agentsmd-audit` inline. `agentsmd-audit` runs a single combined pass checking three mandatory baselines: secrets/credentials (governance.md hard prohibition — AGENTS.md is committed content), structural completeness (common-sections checklist from the agents.md spec), and accuracy/drift (do referenced commands and paths actually resolve against the repo). `agentsmd-audit` never inspects provider adapter files (see `provider-adapter-author`) — its scope is AGENTS.md content only. Chosen over folding this into `kyberforge` because kyberforge's scope is meta-tooling for the holocron marketplace itself, not generic target-repo documentation; `core` is the intended home for cross-cutting, repo-agnostic utility skills.
### provider-adapter-author
A companion skill (`core` plugin) that detects a target repo's provider-specific instruction file (`CLAUDE.md`, `.cursor/rules/*.mdc`, `copilot-instructions.md`, etc.) and, where it duplicates content AGENTS.md should own, converts it into a thin adapter that imports AGENTS.md — mirroring this repo's own ADR-0002/ADR-0003 two-tier adapter pattern. Self-validates via its own bundled deterministic script (`scripts/validate-adapter.sh`: checks for an import reference, no duplicated headings, size threshold) rather than a separate paired audit skill — the check is mechanical, so a script suffices per governance.md's "prefer deterministic code for repeatable tasks." `agentsmd-author` calls this skill via skill composition when it detects an existing provider file with overlapping content.
@@ -70,11 +70,11 @@ A companion skill (`core` plugin) that detects a target repo's provider-specific
A standalone, repo-agnostic plugin (`plugins/lint/`) for configuring and running linters — not scoped to kyberforge's own meta-tooling. First linter is Vale (prose style linting), split into two skills per the git/gitea per-concern pattern: `vale-config` (setup — `.vale.ini`, `StylesPath`, styles) and `vale-run` (invoke Vale, interpret/report findings). A `lint-runner` agent composes these for isolated-context lint sweeps; it is report-only (no `Edit` tool) — it flags findings, it does not rewrite prose. Vale's research docs (`docs/research/docs/vale/`) moved from `plugins/kyberforge/` to `plugins/lint/` to keep the provenance chain same-plugin.
### Vale audit prefilter (skill-audit / agent-audit)
Wiring Vale as a deterministic prefilter for `skill-audit`/`agent-audit`'s Description dimension (ADR motivation: issue #84) is repo-specific, not part of the generic `lint` plugin, so its config lives at the repo root rather than inside `plugins/lint/`: `.vale.ini` plus a custom `Kyberforge` style (`styles/Kyberforge/`) covering description-opener banning ("This skill/agent..."), vague-capability wording ("helps with", "utilize", ...), and generic "see references/ for details" padding — and a `KyberforgeCopilot` style (`styles/KyberforgeCopilot/`) scoped only to `.agent.md` files for the Copilot-only "Use proactively has no effect" check. `error` alerts map to FAIL, `warning`/`suggestion` map to SUGGESTION. Vale covers the pattern-matchable sub-checks named in issue #84 (imperative opener, vague filler, `Use proactively`, generic reference-pointer padding) plus, per ADR-0013, two body-wide prose-pattern checks (vague-qualifier filler, "There is/are" sentence openers) — everything else about body discipline (defaults-vs-menus, why-rationale, non-pattern-matchable judgment calls), near-miss exclusion strength, and control calibration stays LLM judgment.
Wiring Vale as a deterministic prefilter for `skill-audit`/`agent-audit`'s Description dimension (ADR motivation: issue #84) is repo-specific, not part of the generic `lint` plugin, so its config lives at the repo root rather than inside `plugins/lint/`: `.vale.ini` plus a custom `Kyberforge` style (`styles/Kyberforge/`) covering description-opener banning ("This skill/agent..."), vague-capability wording ("helps with", "utilize", ...), and generic "see references/ for details" padding — and a `KyberforgeCopilot` style (`styles/KyberforgeCopilot/`) scoped only to `.agent.md` files for the Copilot-only "Use proactively has no effect" check. Every rule is `level: error` and every alert is a FAIL — no ignorable tier, same as shellcheck, the test suite, and conventional-pre-commit. Graded severities do not work here: Vale's exit code keys on `error` alerts alone, so `warning`/`suggestion` rules exit 0 and pre-commit swallows the output of a passing hook, leaving them invisible and blocking nothing. `MinAlertLevel` and `--minAlertLevel` are correspondingly absent from `.vale.ini` and the hook, being no-ops under this model. Vale covers the pattern-matchable sub-checks named in issue #84 (imperative opener, vague filler, `Use proactively`, generic reference-pointer padding) plus, per ADR-0013, one body-wide prose-pattern check ("There is/are" sentence openers) — everything else about body discipline (defaults-vs-menus, why-rationale, non-pattern-matchable judgment calls), near-miss exclusion strength, and control calibration stays LLM judgment.
Both skills' Step 1, and the `vale-audit-prefilter` pre-commit hook, call `scripts/vale-wrap.sh` rather than `vale` directly — a workaround for a confirmed Vale 3.15.2 limitation (see `vale-config`'s Gotchas): `text.frontmatter.description` silently stops matching once the description is a YAML block scalar (`>`/`|`) spanning 2+ physical lines, which is how most skills/agents in this repo write it. The wrapper flattens the description to one physical line in a scratch copy (padding with blank lines so every other line number is unchanged) before handing off to real `vale`; single-line descriptions pass through untouched. `tests/test-vale-wrap.sh` regression-tests this. Both call sites still scope every invocation to the specific file(s) being audited, never a repo-wide sweep — Vale's glob matching crosses directory boundaries (`plugins/*/agents/*.md` matches nested `docs/research/examples/**/agents/*.md` too), so scoping is what keeps research-example files out of the audit's lint pass. The pre-commit hook's own glob is tightened to `^plugins/[^/]+/(skills/[^/]+/SKILL\.md|agents/[^/]+\.md)$` (single-segment, not `.*`) for the same reason, since pre-commit invokes it automatically against whatever staged files match rather than a manually-scoped target.
Both skills' Step 1, and the `vale-audit-prefilter` pre-commit hook, call `scripts/vale-wrap.sh` rather than `vale` directly — a workaround for a confirmed Vale 3.15.2 limitation (see `vale-config`'s Gotchas): `text.frontmatter.description` silently stops matching once the description is a YAML block scalar (`>`/`|`) spanning 2+ physical lines, which is how most skills/agents in this repo write it. The wrapper flattens the description to one physical line in a scratch copy (padding with blank lines so every other line number is unchanged) before handing off to real `vale`; single-line descriptions pass through untouched; a relative `--config` path resolves against the caller's cwd, matching bare `vale`, not against the repo root. `tests/test-vale-wrap.sh` regression-tests this. `.vale.ini`'s section globs are path-agnostic (`[**/SKILL.md]`, `[**/agents/*.md]`, `[**/*.agent.md]`) and do no scoping: Vale's `*` crosses `/`, so the older `plugins/*/`-prefixed globs already matched `plugins/*/docs/research/examples/**/agents/*.md` and `plugins/*/skills/*/assets/templates/SKILL.md`. Scoping comes from the pre-commit hook's `files:` regex, `^plugins/[^/]+/(skills/[^/]+/SKILL\.md|agents/[^/]+\.md)$` (single-segment, not `.*`, since pre-commit invokes it automatically against whatever staged files match rather than a manually-scoped target), and from the audit skills passing one explicit file per invocation. The path-agnostic globs also close a silent false negative: a skill outside `plugins/` (e.g. project-scope `.claude/skills/foo/SKILL.md`) matched no section, so Vale reported 0 files and exited 0 — which the audits read as clean. Both audits now treat a 0-file Vale run as NOT RUN and fall back to full LLM judgment.
This scope expands per ADR-0013: cherry-picked low-noise `write-good`/`alex` rules landed in `styles/Kyberforge` as two new rule files, `Kyberforge.VagueQualifier` and `Kyberforge.SentenceOpenerThereIs`, plus a new sibling pre-commit hook, `skill-size-check` (`scripts/skill-size-check.sh`), enforcing agentskills.io's 500-line/5,000-token `SKILL.md` ceiling — scoped to `^plugins/[^/]+/skills/[^/]+/SKILL\.md$` only, same as `vale-audit-prefilter`, so it never lints `docs/research/examples/` reference skills. File scope (`SKILL.md` + agent files) and enforcement model (rules land directly in `styles/Kyberforge`, blocking immediately, no trial tier) stay unchanged; governance.md/CONTROLS.md were evaluated and excluded as rule sources (nothing prose-pattern-matchable to mine).
This scope expands per ADR-0013: one cherry-picked low-noise `write-good`/`alex` rule landed in `styles/Kyberforge`, `Kyberforge.SentenceOpenerThereIs` (22 held-out hits, both in-corpus hits clean rewrites, zero suppressions). A second, `Kyberforge.VagueQualifier`, was cherry-picked and then deleted: 2 hits across the 41 skill/agent files, one marginal and one an unfixable false positive (`caveman/SKILL.md` quotes `of course` as an example of filler — a mention, not a use) that forced the repo's only Vale suppression comments. Also new is a sibling pre-commit hook, `skill-size-check` (`scripts/skill-size-check.sh`), enforcing agentskills.io's 500-line/5,000-token `SKILL.md` ceiling — failing only above 500 lines, matching `skill-audit/scripts/validate.sh`'s `<= 500` pass — scoped to `^plugins/[^/]+/skills/[^/]+/SKILL\.md$` only, same as `vale-audit-prefilter`, so it never lints `docs/research/examples/` reference skills. File scope (`SKILL.md` + agent files) and enforcement model (rules land directly in `styles/Kyberforge`, blocking immediately, no trial tier) stay unchanged; governance.md/CONTROLS.md were evaluated and excluded as rule sources (nothing prose-pattern-matchable to mine). House convention: banned phrasing that must be mentioned rather than used goes in backticks or a fenced code block — Vale skips code spans and fences, so no suppression is needed; inline `<!-- vale Rule = NO -->` (HTML-comment form; the MDX `{/* */}` form does not work in plain Markdown) is the fallback only where backticking is impossible.
### LESSONS.md
Long-loop feedback log for patterns observed across sessions. Three or more entries on the same pattern graduate to the relevant standing file (e.g. a coding convention, a governance rule). Updated by the session-handoff skill or directly by the human. Lives at the repo root.

View File

@@ -23,3 +23,4 @@ Read these files on demand:
- **Coding conventions** (`~/.claude/core/instructions/coding.md`) — when writing, editing, or reviewing code
- **Testing conventions** (`~/.claude/core/instructions/testing.md`) — when writing or running tests
- **Subagent orchestration** (`~/.claude/core/instructions/subagent-orchestration.md`) — when spawning or coordinating subagents/forks

View File

@@ -0,0 +1,6 @@
# Subagent orchestration
- A fork stops when its assigned task is done. It inherits the coordinator's full context, including any shared TaskList — that visibility is not license to keep pulling further items after its assigned task is reported complete; doing so races the coordinator's own orchestration and can duplicate or conflict with separately-delegated work.
- Don't hand a fork a TaskList containing governance-gated actions (push, publish, merge) unless prepared for it to act on those without a fresh confirmation round. A fork acting on its own initiative is not party to any pending human confirmation the coordinator is mid-flow on.
- `TaskGet`/`TaskUpdate`/`TaskList` only work for forks. Fresh (non-fork) subagents cannot discover or call these tools — when delegating to a fresh subagent, the coordinator owns all task-list bookkeeping itself.
- `Agent(isolation: "worktree")` may fork from `main`, not the branch the coordinator was on. Verify and self-correct (`git merge --ff-only <target-branch>` or reset onto `origin/<target-branch>`) before editing. When removing such a worktree afterward, use `git worktree remove --force --force <path>` if the repo has submodules (double `-f` required), then `git branch -d` both the feature branch and the auto-created `worktree-agent-<id>` isolation branch.

View File

@@ -9,8 +9,8 @@ deferred PR #85 review item to broaden that coverage, retroactively captures #84
(since it was never recorded as a decision in its own right), and layers the expansion on top
without reversing or weakening the original four rules.
**File scope stays the same.** `SKILL.md` plus agent files (`plugins/*/agents/*.md`,
`plugins/*/agents/*.agent.md`) only — matching the existing prefilter's globs. Skill-level
**File scope stays the same.** `SKILL.md` plus agent files (`**/agents/*.md`,
`**/*.agent.md`) only — matching the existing prefilter's globs. Skill-level
`README.md` files and `plugin.json` manifests are not added: README.md files are navigational, not
spec-governed content, and `plugin.json` is JSON, not prose Vale can meaningfully lint.
@@ -51,7 +51,13 @@ length ceiling, not a text pattern, so it isn't a Vale rule — it becomes a new
and pre-commit hook, sibling to the existing `skill-frontmatter` hook.
**Rules land directly in `styles/Kyberforge`, enforcing immediately.** No trial/report-only tier
is introduced (see Considered Options). The implementation pass finalizes the cherry-picked
is introduced (see Considered Options). "Enforcing immediately" holds only because every rule in
both styles is `level: error`: Vale's exit code keys on `error`-level alerts alone, so a
`warning`- or `suggestion`-level rule prints an alert and still exits 0, and pre-commit suppresses
output from hooks that pass — such a rule is invisible and blocks nothing. Every Vale alert is
therefore a FAIL, in the audit skills and in the blocking pre-commit hook alike, with no ignorable
tier; that matches every other gate in this repo (shellcheck, the test suite,
conventional-pre-commit). The implementation pass finalizes the cherry-picked
`write-good`/`alex` rules and any new spec-derived rule wording, runs the full set against the
existing SKILL.md/agent-file corpus, fixes any resulting violations across that corpus, and lands
the rule changes and the corpus fixes as one atomic commit — the same enforcement model as the
@@ -68,14 +74,29 @@ under that directory automatically — there's no partial/opt-in application wit
rule dropped straight into `styles/Kyberforge` goes live in the blocking pre-commit hook
immediately. Rejected in favor of finalizing rules directly and fixing violations via subagent
before committing: simpler, no new trial-config machinery to build or maintain — at the cost of no
standing report-only tier for future candidate rules.
standing report-only tier for future candidate rules. Note that the first implementation shipped
graded severities (`error`/`warning`/`suggestion`) and thereby recreated the rejected option by
accident: the five non-`error` rules never affected an exit code and never surfaced output through
a passing pre-commit hook, so they were a report-only tier that reported to nobody. Flattening
every rule to `level: error` is what actually implements this decision.
## Consequences
- `styles/Kyberforge/` gained two new rule files, cherry-picked from `write-good`/`alex` as
low-noise against this repo's corpus: `VagueQualifier.yml` and `SentenceOpenerThereIs.yml`.
- `styles/Kyberforge/` gained one new rule file, cherry-picked from `write-good`/`alex` as
low-noise against this repo's corpus: `SentenceOpenerThereIs.yml` (22 hits across 273 held-out
markdown files; both in-corpus hits were clean rewrites, needing no suppression).
- A second candidate, `VagueQualifier.yml`, was cherry-picked and then dropped. Against the 41
skill/agent files it hit twice: one marginal real finding (`prototype/SKILL.md`, "very different"
→ "fundamentally different") and one false positive (`caveman/SKILL.md`, which *quotes* `of
course` as an example of filler — a mention, not a use) that no rewrite could clear, forcing the
repo's only Vale suppression comments. Of its 15 held-out hits, 9 were in `docs/research/examples/`
(out-of-scope upstream material) and the remaining 6 were the word "very" in two idioms in a
single research doc, each already adjacent to the hard number carrying the fact. One marginal
catch does not pay for a permanent suppression, so the rule is deleted and this ADR's
"cherry-picked rules" is one rule, not two.
- A new pre-commit hook, `skill-size-check` (`scripts/skill-size-check.sh`), enforces the
500-line/5,000-token `SKILL.md` ceiling, sibling to `skill-frontmatter`.
500-line/5,000-token `SKILL.md` ceiling, sibling to `skill-frontmatter`. It fails only *above*
500 lines, matching `skill-audit/scripts/validate.sh`'s long-standing `<= 500` pass.
- `styles/KyberforgeTrial/` and `.vale.trial.ini` were deliberately not created — noted here so a
future reader doesn't wonder if a trial tier was forgotten.
- The styles-portability question — whether `styles/` and `.vale.ini` should move into
@@ -87,7 +108,8 @@ standing report-only tier for future candidate rules.
not silently forgotten.
**What this ADR's implementation pass did:** synced and trialed `write-good`/`alex` against the
existing SKILL.md/agent-file corpus, cherry-picked the two low-noise rules above into
existing SKILL.md/agent-file corpus, cherry-picked the one low-noise rule above into
`styles/Kyberforge`, wrote `scripts/skill-size-check.sh` and its pre-commit hook, fixed the
resulting corpus violations, and landed the rule changes and corpus fixes as one atomic commit —
matching the enforcement model described above (no partial or opt-in state).
matching the enforcement model described above (no partial or opt-in state), with every rule at
`level: error` so that model is real rather than nominal.

View File

@@ -8,5 +8,5 @@
"keywords": [],
"license": "MIT",
"name": "bin",
"version": "1.1.0"
"version": "1.1.1"
}

View File

@@ -11,5 +11,5 @@
"skills": [
"skills/"
],
"version": "1.1.0"
"version": "1.1.1"
}

View File

@@ -15,12 +15,7 @@ ACTIVE EVERY RESPONSE once triggered. No revert after many turns. No filler drif
## Rules
<!-- vale Kyberforge.VagueQualifier = NO -->
<!-- vale Kyberforge.VagueWording = NO -->
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging.
<!-- vale Kyberforge.VagueQualifier = YES -->
<!-- vale Kyberforge.VagueWording = YES -->
Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Abbreviate common terms (DB/auth/config/req/res/fn/impl). Strip conjunctions. Use arrows for causality (X -> Y). One word when one word enough.
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Abbreviate common terms (DB/auth/config/req/res/fn/impl). Strip conjunctions. Use arrows for causality (X -> Y). One word when one word enough.
Technical terms stay exact. Code blocks unchanged. Errors quoted exact.

View File

@@ -8,5 +8,5 @@
"keywords": [],
"license": "MIT",
"name": "kyberforge",
"version": "1.2.3"
"version": "1.2.4"
}

View File

@@ -13,5 +13,5 @@
"skills": [
"skills/"
],
"version": "1.2.3"
"version": "1.2.4"
}

View File

@@ -41,7 +41,7 @@ bash scripts/validate-provenance.sh <path-to-agent-file>
The script accepts either the CC file or the Copilot file. It detects provider from extension, derives the counterpart, and runs all structural checks. Note FAILs and SUGGESTIONs for the `### Structure` and `### Provider safety` report dimensions. Findings about missing fields, bad name format, empty body, or missing frontmatter → `### Structure`. Findings about CC-only fields in a Copilot file, Copilot-only fields in a CC file, plugin-silently-ignored fields, body length, or subagent-unavailable tools → `### Provider safety`. A missing counterpart file → `### Pair consistency`.
`vale-wrap.sh` resolves its own path and `.vale.ini` via `git rev-parse --show-toplevel`, so it runs correctly regardless of the caller's cwd. Run it against both files of the pair (not just the one passed in). `Kyberforge` applies to both files; `KyberforgeCopilot` applies to the `.agent.md` file only, since its one rule (`Use proactively`) flags CC-specific phrasing that's meaningless in a Copilot description — there's nothing to flag in the CC file, so it isn't scoped there. Map `error` → `FAIL` and `warning`/`suggestion` → `SUGGESTION` in the `### Description` / `### Body` dimensions, citing the rule ID (e.g. `KyberforgeCopilot.ProactivePhrase`). Skip and fall back to Step 2 judgment if vale or `.vale.ini` is unavailable.
`vale-wrap.sh` resolves its own path and `.vale.ini` via `git rev-parse --show-toplevel`; pass the absolute paths shown above so the invocation holds from any cwd. Run it against both files of the pair (not just the one passed in). `Kyberforge` applies to both files; `KyberforgeCopilot` applies to the `.agent.md` file only, since its one rule (`Use proactively`) flags CC-specific phrasing that's meaningless in a Copilot description — there's nothing to flag in the CC file, so it isn't scoped there. Every Vale alert is a `FAIL` — all rules are graded `error` — so report each one in the `### Description` / `### Body` dimensions citing its rule ID (e.g. `KyberforgeCopilot.ProactivePhrase`). Skip and fall back to Step 2 judgment if vale or `.vale.ini` is unavailable. If Vale reports `0 files` scanned, treat the pass as NOT RUN — not as clean — and fall back to full Step 2 judgment for the dimensions it would have covered.
`validate-provenance.sh` validates the provenance chain between the agent pair's `source_keys` and the plugin-scoped `sources.md` (plugin root — see ADR-0010). It exits 0 silently for non-plugin-scope agents and when no provenance data exists. Note FAILs from this script for the `### Provenance` dimension — surface them verbatim with Why and Fix.
@@ -53,7 +53,7 @@ Read both agent files. Work through each dimension internally. Collect findings
**Description (both files):**
- Action-verb opening: description starts with a verb ("Reviews...", "Analyzes...", "Generates...") — FAIL if absent. Vale's `Kyberforge.DescriptionOpener` alert flags the specific known-bad "This agent..." opener directly; verifying an arbitrary opening word is genuinely a strong verb still requires judgment.
- Specificity: is the trigger condition stated precisely? — SUGGESTION if vague. Vale's `Kyberforge.VagueWording` alert covers known filler ("helps with", "utilize", ...) directly; report those without re-deriving by judgment.
- Specificity: is the trigger condition stated precisely? — SUGGESTION if vague. Vale's `Kyberforge.VagueWording` alert covers known filler ("helps with", "utilize", ...) directly; report those as FAILs without re-deriving by judgment.
- `Use proactively` in a Copilot description: Vale's `KyberforgeCopilot.ProactivePhrase` alert (Copilot file only) flags this directly — report it without re-deriving by judgment.
If a description finding is borderline, read `references/description-quality.md`.
@@ -62,7 +62,7 @@ If a description finding is borderline, read `references/description-quality.md`
- Direct role instruction: system prompt opens with `You are a [role]. When invoked, [action].` — SUGGESTION if absent
- One job per agent: system prompt describes a single bounded task — SUGGESTION if scope appears unbounded
- Generic, non-specific reference pointers to the `references/` directory: Vale's `Kyberforge.PaddingPhrase` alert flags this directly — report it without re-deriving by judgment
- Vague filler wording and sentences that open with "There is"/"There are": Vale's `Kyberforge.VagueQualifier` and `Kyberforge.SentenceOpenerThereIs` alerts flag this directly — report them without re-deriving by judgment
- Sentences that open with "There is"/"There are": Vale's `Kyberforge.SentenceOpenerThereIs` alert flags this directly — report it without re-deriving by judgment
**Body/Frontmatter comments:**
- Inspect each comment block in the YAML frontmatter. For each comment, apply: *"Would the agent get this wrong without this comment?"* Flag any that answer "no" as padding.

View File

@@ -41,7 +41,7 @@ Note any structural FAILs — they will appear in the report as a `### Structure
Note any Provenance FAILs and INFO findings from `validate-provenance.sh` — they surface in the report as a `### Provenance` dimension (separate from `### Structure`). The script embeds full FAIL/INFO format with Why and Fix per finding; surface them verbatim.
`vale-wrap.sh` resolves its own path and `.vale.ini` via `git rev-parse --show-toplevel`, so it runs correctly regardless of the caller's cwd, using `.vale.ini`'s `Kyberforge` style — a deterministic prefilter for a subset of the Description/Patterns dimensions below, not a replacement for Step 3. Map `error` → `FAIL` and `warning`/`suggestion` → `SUGGESTION` in those dimensions, citing the rule ID (e.g. `Kyberforge.DescriptionOpener`). Skip and fall back to Step 3 judgment if vale or `.vale.ini` is unavailable.
`vale-wrap.sh` resolves its own path and `.vale.ini` via `git rev-parse --show-toplevel`; pass the absolute paths shown above so the invocation holds from any cwd. It applies `.vale.ini`'s `Kyberforge` style — a deterministic prefilter for a subset of the Description/Patterns/Body dimensions below, not a replacement for Step 3. Every Vale alert is a `FAIL` — all rules are graded `error` — so report each one citing its rule ID (e.g. `Kyberforge.DescriptionOpener`). Skip and fall back to Step 3 judgment if vale or `.vale.ini` is unavailable. If Vale reports `0 files` scanned, treat the pass as NOT RUN — not as clean — and fall back to full Step 3 judgment for the dimensions it would have covered.
## Step 2 — Read all skill files
@@ -53,7 +53,7 @@ Work through each dimension internally. Collect findings only; report them in St
### Description
Vale's `Kyberforge.DescriptionOpener` (FAIL — "This skill..." openers) and `Kyberforge.VagueWording` (SUGGESTION — filler like "helps with", "utilize") alerts from Step 1 cover imperative phrasing and known vague-wording filler directly; report them as findings without re-deriving by judgment. The rest is still a judgment call:
Vale's `Kyberforge.DescriptionOpener` ("This skill..." openers) and `Kyberforge.VagueWording` (filler like "helps with", "utilize") alerts from Step 1 — both FAILs — cover imperative phrasing and known vague-wording filler directly; report them as findings without re-deriving by judgment. The rest is still a judgment call:
- **Specificity beyond the filler blocklist**: are capabilities stated precisely ("parses OpenAPI specs") or genuinely vaguely ("handles files")?
- **Indirect triggers**: does it cover cases where the user doesn't name the domain directly?
@@ -70,7 +70,7 @@ For each sentence in the body, apply: *"Would the agent get this wrong without t
- **Why rationale**: include/exclude rules explain why, not just what
- **Control calibration**: prescriptive for fragile or critical sequences (e.g. a script invocation where flag order or exact arguments must not change); flexible where multiple approaches are valid
Vale's `Kyberforge.VagueQualifier` (SUGGESTION — vague filler like "clearly", "obviously") and `Kyberforge.SentenceOpenerThereIs` (SUGGESTION — sentences starting with "There is"/"There are") alerts from Step 1 cover pattern-matchable body-wide filler directly; report them as findings without re-deriving by judgment.
Vale's `Kyberforge.SentenceOpenerThereIs` alert from Step 1 (FAIL — sentences starting with "There is"/"There are") covers pattern-matchable body-wide filler directly; report it as a finding without re-deriving by judgment.
If uncertain whether a sentence is padding or whether a control decision is correctly calibrated, read `references/body-discipline.md`.

View File

@@ -13,5 +13,5 @@
],
"license": "MIT",
"name": "lint",
"version": "1.1.2"
"version": "1.1.3"
}

View File

@@ -18,5 +18,5 @@
"skills": [
"skills/"
],
"version": "1.1.2"
"version": "1.1.3"
}

View File

@@ -19,9 +19,9 @@ metadata:
## Gotchas
- Vale exits non-zero whenever it finds an alert at or above `MinAlertLevel` — that's what makes it usable as a CI gate, not a sign the invocation failed. Read the output before concluding the command errored.
- Vale's exit code is driven by `error`-level alerts only. `warning` and `suggestion` alerts are reported but still exit `0`. `MinAlertLevel` and `--minAlertLevel` control display, never the exit code — no flag makes warnings fail. A rule that must gate CI or a commit hook has to be `level: error`. This is the single most common way a Vale gate silently passes everything.
- `vale ls-config` prints the fully-resolved, currently active configuration as JSON — the fastest way to check why a rule "isn't applying" is what's actually active, not what's written in `.vale.ini`.
- Inline suppression syntax is format-specific: Markdown/MDX uses `{/* vale off */}` / `{/* vale on */}`, Org mode uses `# vale off` / `# vale on`. Don't assume one syntax works across formats.
- Inline suppression syntax is format-specific: Markdown uses HTML comments `<!-- vale off -->` / `<!-- vale on -->`, MDX uses `{/* vale off */}` / `{/* vale on */}`, Org mode uses `# vale off` / `# vale on`. The MDX form does nothing in a plain `.md` file — the alert still fires. Don't assume one syntax works across formats.
- Before calling the `vale` binary directly, check whether the target repo documents its own wrapper script for Vale (look in its README, CONTRIBUTING docs, pre-commit config, or a `scripts/` directory). Some projects wrap `vale` to work around real bugs — e.g. a scope that silently stops matching multi-line YAML block-scalar frontmatter fields — and calling bare `vale` in a repo that has such a wrapper silently skips whatever the wrapper works around. If a wrapper is documented, invoke it with the same arguments instead of calling `vale` directly; otherwise fall back to the default below.
## Running vale
@@ -37,8 +37,8 @@ Key flags:
| Flag | Purpose |
|---|---|
| `--output=<style>` | Output format/template: `CLI` (default, human-readable), `line` (compact, one alert per line, good for grep/piping), `JSON` (for programmatic parsing), or a custom template. |
| `--minAlertLevel=<suggestion\|warning\|error>` | Overrides `MinAlertLevel` from `.vale.ini` for this run only, without editing config. |
| `--no-exit` | Forces exit code `0` regardless of findings. Use in CI stages that should surface lint output without hard-failing the build. |
| `--minAlertLevel=<suggestion\|warning\|error>` | Overrides `MinAlertLevel` from `.vale.ini` for this run only, without editing config. Filters what is displayed; does not affect the exit code. |
| `--no-exit` | Suppresses the nonzero exit that `error`-level alerts would otherwise cause; a no-op when no rule is `error`-level. Use in CI stages that should surface lint output without hard-failing the build. |
| `--ignore-syntax` | Treats input as plain text, skipping format-aware parsing — use when a file's syntax-aware parser produces noisy or wrong results. |
`vale sync` downloads the packages/styles declared in `.vale.ini` — that's a one-time-per-change setup step (vale-config's territory), not part of a normal lint run. If a run behaves as though no styles are active, that's a sign `vale sync` hasn't been run yet, not a `vale-run` problem.
@@ -49,14 +49,15 @@ Prefer `--output=JSON` whenever the caller (a script, a CI step, another agent)
Scope the fix as narrowly as possible, in this order:
1. **One-off**: inline-suppress the specific text run with the format's `vale off`/`vale on` markup.
2. **Recurring known-exception string, one rule**: disable that specific rule for that specific match inline (e.g. `{/* vale Style.Redundancy["ACT test","OTHER"] = NO */}` ... `= YES`), rather than the whole rule.
3. **Known project term failing spell check**: add it to the style's `ignore` list, not an inline suppression.
1. **Mentioning banned phrasing rather than using it**: wrap it in backticks or a fenced code block. Vale skips code spans and fences, so no suppression is needed at all. Try this before any suppression markup.
2. **One-off**: inline-suppress the specific text run with the format's `vale off`/`vale on` markup.
3. **Recurring known-exception string, one rule**: disable that specific rule for that specific match inline (e.g. `<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->` ... `= YES`), rather than the whole rule.
4. **Known project term failing spell check**: add it to the style's `ignore` list, not an inline suppression.
Never disable a rule project-wide to fix one false positive — editing `.vale.ini`/`BasedOnStyles` is vale-config's job, and it silences the rule everywhere, not just the false-positive case.
If output looks wrong because Vale mis-parsed a file's format, rerun with `--ignore-syntax` before assuming the rule itself is broken.
For CI that fails solely because Vale returned non-zero on found alerts — not because the content is wrong for that pipeline stage — add `--no-exit` rather than disabling the rule.
For CI that fails solely because Vale returned non-zero on `error`-level alerts — not because the content is wrong for that pipeline stage — add `--no-exit` rather than disabling the rule. If the failing alerts are warnings or suggestions, Vale is not what failed the build; look elsewhere.
If setting up Vale as a pre-commit hook or need the full inline-suppression/spelling-ignore syntax reference, read `references/troubleshooting.md`.

View File

@@ -7,7 +7,14 @@ source_keys:
## Inline suppression syntax by format
Markdown/MDX:
Markdown uses HTML comments — the MDX `{/* */}` form does not suppress anything in a plain `.md` file:
```markdown
<!-- vale off -->
This text will be ignored.
<!-- vale on -->
```
MDX:
```mdx
{/* vale off */}
This text will be ignored.
@@ -25,6 +32,14 @@ This text will be ignored.
Targets one rule and specific known-exception strings, then re-enables — the preferred fix for a recurring false positive on a specific term, since it keeps the rule active everywhere else:
Markdown:
```markdown
<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->
This is some text ACT test
<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->
```
MDX:
```mdx
{/* vale Style.Redundancy["ACT test","OTHER"] = NO */}
This is some text ACT test
@@ -50,7 +65,7 @@ If a file's syntax-aware parsing produces noisy or incorrect results (an unsuppo
## CI failing unexpectedly
If a CI job fails solely because Vale returns a non-zero exit code on found alerts — not because the content is actually wrong for that pipeline stage — add `--no-exit` rather than suppressing the rule itself. This preserves the lint output while not gating the build on it.
Only `error`-level alerts make Vale exit non-zero; `warning` and `suggestion` alerts are printed but exit `0`. If a CI job fails solely because of `error`-level alerts — not because the content is actually wrong for that pipeline stage — add `--no-exit` rather than suppressing the rule itself. This preserves the lint output while not gating the build on it. If the alerts are warnings or suggestions, Vale did not fail the job — look elsewhere.
## pre-commit integration

View File

@@ -1,11 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
# Enforces agentskills.io's skill-authoring.md guidance: keep SKILL.md under 500
# lines and roughly 5,000 tokens, so the full body doesn't crowd out conversation
# history and other active skills once loaded into context. Vale can't express
# a whole-file length ceiling (its checks operate on text patterns, not raw
# file size), so this is a plain script instead of a Vale rule.
# Enforces agentskills.io's skill-authoring.md guidance: keep SKILL.md within
# 500 lines and roughly 5,000 tokens, so the full body doesn't crowd out
# conversation history and other active skills once loaded into context. Vale
# can't express a whole-file length ceiling (its checks operate on text
# patterns, not raw file size), so this is a plain script instead of a Vale
# rule.
#
# Both ceilings are inclusive: a file at exactly MAX_LINES or MAX_WORDS passes,
# and only one past it fails. That matches skill-audit/scripts/validate.sh,
# which has always used `line_count <= 500` as its pass condition — the two
# previously disagreed at exactly 500 lines, so a SKILL.md could pass its own
# audit and still be blocked by the commit hook.
#
# Token counts aren't computed exactly here — word count (`wc -w`) is used as
# a proxy. This repo's own SKILL.md corpus measures ~5.7-6.5 characters per
@@ -26,8 +33,8 @@ for f in "$@"; do
# Python's splitlines() semantics (used by skill-audit/scripts/validate.sh
# for its own line count) — `wc -l` undercounts by 1 in that case.
lines=$(awk 'END{print NR}' "$f")
if (( lines >= MAX_LINES )); then
echo "ERROR: $f has $lines lines, at or over the $MAX_LINES-line ceiling (agentskills.io skill-authoring.md)" >&2
if (( lines > MAX_LINES )); then
echo "ERROR: $f has $lines lines, exceeding the $MAX_LINES-line ceiling (agentskills.io skill-authoring.md)" >&2
FAIL=1
fi

View File

@@ -8,56 +8,104 @@ set -euo pipefail
# (padding with blank lines so every other line number is unchanged), then runs
# the real `vale` binary against the copies. Drop-in replacement for calling
# `vale` directly: same args, same exit code.
#
# "Same args" means relative paths — `--config` values and file arguments alike
# — resolve against the caller's current directory, exactly as bare `vale`
# resolves them. (An earlier version resolved them against the repo root, an
# invented convention that hard-errored on `--config ../../.vale.ini` from a
# subdirectory and, worse, silently dropped file arguments that didn't happen to
# resolve from the repo root — skipping the flattening this script exists for.)
#
# Vale prints each file path exactly as it was handed to it, so the scratch tree
# mirrors the caller's absolute cwd: a relative file argument is passed through
# verbatim and resolves to its flattened copy, keeping the report byte-identical
# to bare `vale`'s. An absolute file argument inside the cwd is relativized to
# keep that property. Only an absolute path outside the cwd is rewritten to its
# scratch copy and so reports a scratch path — unavoidable, since a file can
# only be read from where it actually is.
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
cwd="$(pwd -P)"
vale_args=()
files=()
file_args=()
config_next=false
for arg in "$@"; do
if [[ "$config_next" == true ]]; then
config_next=false
if [[ "$arg" == /* ]]; then
vale_args+=("$arg")
else
vale_args+=("$repo_root/$arg")
vale_args+=("$cwd/$arg")
fi
config_next=false
continue
fi
if [[ "$arg" == "--config" ]]; then
vale_args+=("$arg")
config_next=true
continue
fi
if [[ "$arg" == --config=* ]]; then
cfg="${arg#--config=}"
if [[ "$cfg" == /* ]]; then
vale_args+=("--config=$cfg")
case "$arg" in
--config)
vale_args+=("$arg")
config_next=true
continue
;;
--config=/*)
vale_args+=("$arg")
continue
;;
--config=*)
vale_args+=("--config=$cwd/${arg#--config=}")
continue
;;
esac
# `-f` resolves relative paths against the caller's cwd, same as vale does.
if [[ "$arg" != -* && -f "$arg" ]]; then
# An absolute path inside the caller's cwd is relativized so the report cites
# a path that resolves against the real tree. Left absolute, it would be
# rewritten to its scratch copy and printed as `/tmp/tmp.XXXX/...` — a real
# path to a file that is deleted on exit, which reads as a bug in any report
# quoting it. Absolute paths outside the cwd have no relative form and keep
# the scratch-path behaviour documented above.
if [[ "$arg" == "$cwd"/* ]]; then
file_args+=("${arg#"$cwd"/}")
else
vale_args+=("--config=$repo_root/$cfg")
file_args+=("$arg")
fi
continue
fi
if [[ "$arg" != -* && -f "$repo_root/$arg" ]]; then
files+=("$arg")
elif [[ "$arg" == /* && -f "$arg" && "$arg" == "$repo_root"/* ]]; then
files+=("${arg#"$repo_root"/}")
else
vale_args+=("$arg")
fi
done
if [[ ${#files[@]} -eq 0 ]]; then
if [[ ${#file_args[@]} -eq 0 ]]; then
# Nothing to flatten. Hand off directly, with stdin closed so vale doesn't
# block waiting on a pipe that will never carry content.
exec vale "${vale_args[@]}" < /dev/null
fi
tmpdir="$(mktemp -d)"
tmpdir="$(realpath -m "$(mktemp -d)")"
trap 'rm -rf "$tmpdir"' EXIT
for rel in "${files[@]}"; do
dest="$tmpdir/$rel"
# Mirror of the caller's cwd inside the scratch tree; relative file arguments
# are resolved from here.
mirror="$tmpdir$cwd"
mkdir -p "$mirror"
argv_files=()
for arg in "${file_args[@]}"; do
if [[ "$arg" == /* ]]; then
dest="$tmpdir$arg"
else
dest="$mirror/$arg"
fi
dest="$(realpath -m "$dest")"
# A file argument with enough leading `..` to climb past the mirror root would
# write outside the scratch dir. The real filesystem clamps such a path at
# `/`; the mirror can't, so refuse rather than scribble outside the sandbox.
case "$dest" in
"$tmpdir"/*) ;;
*)
echo "vale-wrap.sh: refusing to lint '$arg': its scratch copy would land outside $tmpdir" >&2
exit 2
;;
esac
mkdir -p "$(dirname "$dest")"
python3 - "$repo_root/$rel" "$dest" <<'PYTHON'
python3 - "$arg" "$dest" <<'PYTHON'
import re
import sys
@@ -118,7 +166,12 @@ if fm_match:
with open(dest, 'w') as fh:
fh.write(content)
PYTHON
if [[ "$arg" == /* ]]; then
argv_files+=("$dest")
else
argv_files+=("$arg")
fi
done
cd "$tmpdir"
vale "${vale_args[@]}" "${files[@]}"
cd "$mirror"
vale "${vale_args[@]}" "${argv_files[@]}"

View File

@@ -1,6 +1,6 @@
extends: existence
message: "Generic reference pointer: '%s' — use the specific 'If X, read `references/file.md`' form instead"
level: warning
level: error
scope: text
ignorecase: true
raw:

View File

@@ -1,6 +1,6 @@
extends: existence
message: "Don't start a sentence with '%s' — name the subject directly"
level: warning
level: error
scope: sentence
ignorecase: false
raw:

View File

@@ -1,19 +0,0 @@
extends: existence
message: "'%s' is vague filler wording — state the point precisely instead"
level: warning
scope: text
ignorecase: true
tokens:
- easily
- everyone knows
- exceedingly
- excellent
- extremely
- huge
- interestingly
- of course
- quite
- remarkably
- surprisingly
- vast
- very

View File

@@ -1,6 +1,6 @@
extends: existence
message: "Vague capability wording: '%s' — state the capability precisely instead"
level: warning
level: error
scope: text.frontmatter.description
ignorecase: true
tokens:

View File

@@ -1,6 +1,6 @@
extends: existence
message: "'%s' is CC-specific phrasing with no effect in Copilot descriptions — remove it"
level: warning
level: error
scope: text.frontmatter.description
ignorecase: true
tokens:

View File

@@ -83,31 +83,33 @@ make_line_fixture() {
echo "$file"
}
# The line ceiling is exclusive of the limit itself ("stay under $MAX_LINES
# lines", per skill-authoring.md), enforced via `>=` — so $((MAX_LINES - 1))
# must pass and $MAX_LINES itself must already fail.
# The line ceiling is inclusive of the limit itself, enforced via `>` — so
# exactly $MAX_LINES must pass and $((MAX_LINES + 1)) must fail. This matches
# skill-audit/scripts/validate.sh's `line_count <= 500` pass condition; the two
# previously disagreed at exactly $MAX_LINES lines, so a SKILL.md could pass its
# own audit and still be blocked by the commit hook.
echo ""
echo "--- passes a file at $((MAX_LINES - 1)) lines, just under the $MAX_LINES-line boundary ---"
AT_LINES="$(make_line_fixture at-line-limit "$((MAX_LINES - 1))")"
echo "--- passes a file at exactly the $MAX_LINES-line boundary ---"
AT_LINES="$(make_line_fixture at-line-limit "$MAX_LINES")"
ACTUAL_LINES=$(awk 'END{print NR}' "$AT_LINES")
if [[ "$ACTUAL_LINES" -ne "$((MAX_LINES - 1))" ]]; then
fail "fixture has $ACTUAL_LINES lines, expected exactly $((MAX_LINES - 1))"
if [[ "$ACTUAL_LINES" -ne "$MAX_LINES" ]]; then
fail "fixture has $ACTUAL_LINES lines, expected exactly $MAX_LINES"
elif "$SCRIPT" "$AT_LINES"; then
pass "file at $((MAX_LINES - 1)) lines exits 0"
pass "file at exactly $MAX_LINES lines exits 0"
else
fail "file at $((MAX_LINES - 1)) lines should have exited 0"
fail "file at exactly $MAX_LINES lines should have exited 0 (the off-by-one this test guards against)"
fi
echo ""
echo "--- fails a file at exactly the $MAX_LINES-line boundary ---"
OVER_LINES="$(make_line_fixture over-line-limit "$MAX_LINES")"
echo "--- fails a file one line over the $MAX_LINES-line boundary ---"
OVER_LINES="$(make_line_fixture over-line-limit "$((MAX_LINES + 1))")"
ACTUAL_OVER_LINES=$(awk 'END{print NR}' "$OVER_LINES")
if [[ "$ACTUAL_OVER_LINES" -ne "$MAX_LINES" ]]; then
fail "fixture has $ACTUAL_OVER_LINES lines, expected exactly $MAX_LINES"
if [[ "$ACTUAL_OVER_LINES" -ne "$((MAX_LINES + 1))" ]]; then
fail "fixture has $ACTUAL_OVER_LINES lines, expected exactly $((MAX_LINES + 1))"
elif "$SCRIPT" "$OVER_LINES" 2>/dev/null; then
fail "file at exactly $MAX_LINES lines should have exited non-zero (>= ceiling, the boundary bug this test guards against)"
fail "file at $((MAX_LINES + 1)) lines should have exited non-zero"
else
pass "file at exactly $MAX_LINES lines exits non-zero"
pass "file at $((MAX_LINES + 1)) lines exits non-zero"
fi
# make_word_fixture builds a file with an exact total word count (frontmatter

View File

@@ -39,13 +39,24 @@ make_fixture() {
echo "$dir"
}
# Every Kyberforge rule is `level: error`, so vale exits non-zero whenever a
# fixture trips one — which is the expected outcome for nearly every case here.
# run_wrap therefore captures output and swallows the exit status; assertions
# are made on the report text. Cases that genuinely care about the exit code
# capture it explicitly instead.
run_wrap() {
local dir="$1"
shift
(cd "$dir" && bash "$SCRIPT" "$@" 2>&1) || true
}
# --- 1. A known-bad single-line description is caught (sanity check on Vale itself) ---
echo ""
echo "--- catches vague wording in a single-line description ---"
FIXTURE1="$(make_fixture 1)"
trap 'rm -rf "$FIXTURE1"' EXIT
if (cd "$FIXTURE1" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md) | grep -q "VagueWording"; then
if run_wrap "$FIXTURE1" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
pass "flags vague wording when description is a single physical line"
else
fail "did not flag known-bad single-line description"
@@ -56,8 +67,8 @@ echo ""
echo "--- catches vague wording in a multi-line folded description ---"
FIXTURE2="$(make_fixture 2)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2"' EXIT
if (cd "$FIXTURE2" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md) | grep -q "VagueWording"; then
if run_wrap "$FIXTURE2" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
pass "flags vague wording when description spans 2+ physical lines"
else
fail "silently missed known-bad wording in a multi-line description — the bug this test guards against"
@@ -69,8 +80,8 @@ echo "--- preserves total line count when flattening ---"
FIXTURE3="$(make_fixture 3)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3"' EXIT
ORIG_LINES=$(wc -l < "$FIXTURE3/plugins/testplugin/skills/zzzskill/SKILL.md")
OUT=$(cd "$FIXTURE3" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md 2>&1 || true)
OUT=$(run_wrap "$FIXTURE3" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md)
MAX_LINE=$(echo "$OUT" | grep -oE '^[[:space:]]*[0-9]+:[0-9]+' | tr -d '[:space:]' | cut -d: -f1 | sort -n | tail -1)
if [[ -n "$MAX_LINE" ]] && (( MAX_LINE <= ORIG_LINES )); then
pass "reported line numbers stay within the original file's line count"
@@ -107,8 +118,8 @@ Body.
EOF
)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4"' EXIT
if (cd "$FIXTURE4" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md) | grep -q "VagueWording"; then
if run_wrap "$FIXTURE4" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
pass "flags vague wording when the description contains a double quote"
else
fail "silently missed vague wording in a description containing a double quote — the bug this test guards against"
@@ -129,8 +140,8 @@ Body.
EOF
)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5"' EXIT
OUT5=$(cd "$FIXTURE5" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md 2>&1)
OUT5=$(run_wrap "$FIXTURE5" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md)
if echo "$OUT5" | grep -q "VagueWording"; then
pass "flags vague wording when the description contains an apostrophe"
else
@@ -157,8 +168,8 @@ Body.
EOF
)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6"' EXIT
if (cd "$FIXTURE6" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md) | grep -q "VagueWording"; then
if run_wrap "$FIXTURE6" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
pass "flags vague wording when the description has a backslash and non-ASCII text"
else
fail "silently missed vague wording in a description with a backslash and non-ASCII text"
@@ -180,8 +191,8 @@ Body.
EOF
)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7"' EXIT
OUT7=$(cd "$FIXTURE7" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md 2>&1)
OUT7=$(run_wrap "$FIXTURE7" --config "$REPO_ROOT/.vale.ini" \
plugins/testplugin/skills/zzzskill/SKILL.md)
if echo "$OUT7" | grep -q "Traceback"; then
fail "crashed while flattening a description with a blank line between paragraphs"
elif echo "$OUT7" | grep -q "VagueWording"; then
@@ -190,9 +201,16 @@ else
fail "silently missed vague wording in the second paragraph after a blank line — the bug this test guards against"
fi
# --- 8. --config=<path> (equals form) resolves the same as the two-argv form ---
# --- 8. Relative paths resolve against the caller's cwd, exactly as bare vale
# resolves them. Every path below is deliberately relative to $SUBDIR8, not to
# the fixture's repo root: an earlier version of the wrapper resolved relative
# paths against the git toplevel instead, which (a) hard-errored on a
# `--config ../../..` that bare vale accepts and (b) silently dropped file
# arguments that didn't resolve from the repo root, skipping the flattening the
# wrapper exists to perform. The old tests only ever passed repo-root-relative
# paths from a subdirectory, so neither failure mode was caught.
echo ""
echo "--- --config=<path> equals form resolves from a subdirectory like the two-argv form ---"
echo "--- resolves a cwd-relative --config from a subdirectory (equals and two-argv forms) ---"
FIXTURE8="$(mktemp -d)"
(cd "$FIXTURE8" && git init -q)
cp "$REPO_ROOT/.vale.ini" "$FIXTURE8/.vale.ini"
@@ -210,13 +228,53 @@ mkdir -p "$FIXTURE8/plugins/testplugin/skills/zzzskill"
} > "$FIXTURE8/plugins/testplugin/skills/zzzskill/SKILL.md"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8"' EXIT
SUBDIR8="$FIXTURE8/plugins/testplugin/skills/zzzskill"
REL8="plugins/testplugin/skills/zzzskill/SKILL.md"
OUT_EQ=$(cd "$SUBDIR8" && bash "$SCRIPT" --config=.vale.ini "$REL8" 2>&1)
OUT_TWO=$(cd "$SUBDIR8" && bash "$SCRIPT" --config .vale.ini "$REL8" 2>&1)
# Both paths are relative to $SUBDIR8 (four levels below the fixture root).
REL_CFG8="../../../../.vale.ini"
REL_FILE8="SKILL.md"
OUT_EQ=$(run_wrap "$SUBDIR8" "--config=$REL_CFG8" "$REL_FILE8")
OUT_TWO=$(run_wrap "$SUBDIR8" --config "$REL_CFG8" "$REL_FILE8")
if echo "$OUT_EQ" | grep -q "VagueWording" && [[ "$OUT_EQ" == "$OUT_TWO" ]]; then
pass "--config=<path> from a subdirectory resolves and matches the two-argv form"
pass "cwd-relative --config resolves from a subdirectory in both argv forms"
else
fail "--config=<path> equals form did not resolve the same as the two-argv form"
fail "cwd-relative --config did not resolve from a subdirectory (equals form vs two-argv form)"
fi
# --- 8b. A cwd-relative --config matches the equivalent absolute invocation ---
# Regression for failure mode (a): resolving --config against the repo root made
# `--config ../../../../.vale.ini` expand to a path above the toplevel, and vale
# hard-errored with "does not exist" (exit 2) on args bare vale handles fine.
echo ""
echo "--- a cwd-relative --config produces the same result as the absolute-path form ---"
set +e
OUT_REL_CFG=$(cd "$SUBDIR8" && bash "$SCRIPT" --config "$REL_CFG8" "$REL_FILE8" 2>&1)
RC_REL_CFG=$?
OUT_ABS_CFG=$(cd "$SUBDIR8" && bash "$SCRIPT" --config "$FIXTURE8/.vale.ini" "$REL_FILE8" 2>&1)
RC_ABS_CFG=$?
set -e
if echo "$OUT_REL_CFG" | grep -qi "does not exist"; then
fail "cwd-relative --config hard-errored ('does not exist') — the bug this test guards against"
elif [[ "$OUT_REL_CFG" == "$OUT_ABS_CFG" && "$RC_REL_CFG" -eq "$RC_ABS_CFG" ]]; then
pass "cwd-relative --config matches the absolute-path invocation (output and exit code)"
else
fail "cwd-relative --config (rc=$RC_REL_CFG) diverged from the absolute-path form (rc=$RC_ABS_CFG)"
fi
# --- 8c. A cwd-relative FILE argument is still flattened, not silently skipped ---
# Regression for failure mode (b): a relative file path that didn't resolve from
# the repo root failed the wrapper's file test, fell through to the vale flag
# list, and left the file list empty — so the wrapper exec'd bare vale and
# silently skipped the flattening. Bare vale reports nothing here, so asserting
# on the alert (not just the exit code) is what makes the silence detectable.
echo ""
echo "--- flattens a cwd-relative file argument passed from a subdirectory ---"
WRAPPED_REL=$(run_wrap "$SUBDIR8" --config "$FIXTURE8/.vale.ini" "$REL_FILE8")
BARE_REL=$(cd "$SUBDIR8" && vale --config "$FIXTURE8/.vale.ini" "$REL_FILE8" 2>&1 || true)
if ! echo "$WRAPPED_REL" | grep -q "VagueWording"; then
fail "cwd-relative file argument produced no alert — flattening was silently skipped, the bug this test guards against"
elif echo "$BARE_REL" | grep -q "VagueWording"; then
fail "bare vale already flags this fixture, so the test can't detect a silently-skipped flattening"
else
pass "cwd-relative file argument is flattened and flagged where bare vale reports nothing"
fi
# --- 9. Zero file args (or a file list that filters to nothing) exits promptly ---
@@ -252,10 +310,10 @@ echo "--- lints an absolute path to a skill file instead of silently skipping it
FIXTURE10="$(make_fixture 2)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10"' EXIT
ABS_FILE10="$FIXTURE10/plugins/testplugin/skills/zzzskill/SKILL.md"
if (cd "$FIXTURE10" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" "$ABS_FILE10") | grep -q "VagueWording"; then
pass "an absolute path under repo_root is linted, not silently skipped"
if run_wrap "$FIXTURE10" --config "$REPO_ROOT/.vale.ini" "$ABS_FILE10" | grep -q "VagueWording"; then
pass "an absolute path is linted, not silently skipped"
else
fail "an absolute path under repo_root was silently skipped — the bug this test guards against"
fail "an absolute path was silently skipped — the bug this test guards against"
fi
# --- 11. A literal (|) block scalar passes through unflattened (no regression) ---
@@ -274,7 +332,7 @@ EOF
)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11"' EXIT
REL11="plugins/testplugin/skills/zzzskill/SKILL.md"
WRAPPED_OUT=$(cd "$FIXTURE11" && bash "$SCRIPT" --config "$REPO_ROOT/.vale.ini" "$REL11" 2>&1 || true)
WRAPPED_OUT=$(run_wrap "$FIXTURE11" --config "$REPO_ROOT/.vale.ini" "$REL11")
BARE_OUT=$(cd "$FIXTURE11" && vale --config "$REPO_ROOT/.vale.ini" "$REL11" 2>&1 || true)
if [[ "$WRAPPED_OUT" == "$BARE_OUT" ]]; then
pass "literal (|) block scalar output matches bare vale exactly — untouched by flattening"