* feat(store-guardrails): enforce per-component description quality
Two-tier hard guardrail on flea-market submissions. Empty / placeholder /
single-word descriptions now block before any LLM call; vague-but-passes-
floor descriptions block on the substantive LLM review layer.
Tier 1 — inline mechanical check (src/store_guardrails/content_check.py).
Walks the baked plugin tree, evaluates each component (plugin manifest,
agents, skills, commands) plus the submission-level form description
against a 60-char / 25-char (commands) / 5-distinct-word / 200-char-body
floor with a placeholder denylist (TODO, TBD, {{var}}, etc.). Floors
calibrated against real ecosystem norms: Claude / superpowers /
compound-engineering skill packs cluster 150–220 chars, npm / Docker /
VS Code at 100–120. InlineResult.passed now ANDs in content.status.
Tier 2 — LLM review extension (prompts.py + llm_review.py). System
prompt gains a content-quality criterion; REVIEW_JSON_SCHEMA carries a
content_quality {verdict, issues[]} object alongside the existing
security findings. is_safe() requires content_quality.verdict == 'pass'.
Single LLM call covers both dimensions. MAX_RESPONSE_TOKENS bumped
2000 → 2500 for the extra payload. Verdicts missing content_quality
treated as pass (backwards compat with already-recorded rows).
Submitter UX:
- /store/new wizard now carries a "Before you upload — what passes
review" collapsible disclosure on both step 1 and step 2 with the
bar + patterns that work. Live char counter on the description
field. Per-component preview table (green/red dots from the new
summarize_for_preview helper) renders after the ZIP /preview round
trip, scoping each finding to its file.
- New /store/examples page with rejected/passes pairs for skill /
agent / plugin / command plus a "Why these limits" research table.
Anchored sections (#skill / #agent / #plugin / #command) so the
rejection banner can deep-link by component_type.
- Quarantine banner _content_findings.html groups findings by file
(one "See <type> example ↗" per component, not per field) and
translates field codes (frontmatter.description / body / etc.) to
plain-English labels. _content_howto_fix.html surfaces a static
"Re-upload as new version" + "See examples" action row beneath any
content failure on the entity detail page.
- _parse_frontmatter moved to src/store_guardrails/_frontmatter.py so
the new check module shares the parser without inverting the
app → src dependency direction.
Tests:
- New tests/test_store_guardrails_content.py (29 cases) covering
every failure code per component type plus submission-level checks
and the summarize_components / summarize_for_preview helpers.
- Extended test_store_guardrails_inline.py for the new
InlineResult.content field + aggregate behaviour.
- Extended test_store_guardrails_llm.py for the new
content_quality verdict pathways (fail blocks, missing field passes).
- Backfilled fixture descriptions across test_store_api.py,
test_store_entity_versions.py, test_store_put_atomic.py,
test_admin_store_submissions.py, test_marketplace_api.py,
test_marketplace_v32_endpoints.py so existing happy-path tests
clear the new 60-char floor.
* fix(content-guardrail): align agents walker with preview + drop import-time .format()
Two cleanups from the takeover review on #276 (vr/guardrails-content).
1) `_iter_components` for agents now skips files lacking frontmatter
(no `name` AND no `description`). Pre-fix the walker greedily
evaluated every `*.md` under `agents/` — `agents/README.md` and
helper docs got flagged as "frontmatter.description empty"
rejections. Worse: `summarize_for_preview` for `type=agent` ALREADY
filters the same shape, so the upload preview gave a green dot
while the post-bake check gave a red rejection on submit. Two new
regression tests in TestAgentsWalkerSkipsNonAgentFiles pin both
shapes (README + _NOTES.md) so the preview/check parity stays
aligned.
2) `body_too_short` hints now use the same runtime-kwarg substitution
pattern as every other hint in the table. Pre-fix the skill +
agent body_too_short hints called `.format(min_chars=_MIN_BODY_CHARS)`
at module-load time, but the call site `_hint_for(type_,
"body_too_short")` didn't pass `min_chars=`, so the format() was
just baking the constant at import. Cosmetic inconsistency; pass
`min_chars=_MIN_BODY_CHARS` at the call site instead and let
`_hint_for` do the substitution like it does for `too_short`.
Verified end-to-end:
- New TestAgentsWalkerSkipsNonAgentFiles cases fail on the unfixed
walker (verified by reverting to the pre-fix file and re-running);
pass cleanly after the fix.
- Full content-guardrail suite: 25/25 (23 existing + 2 new).
- Full pytest: 4189 passed, 25 skipped.
* release: 0.53.5 — content guardrail (flea-market submitter UX) + catalog ENTITY column + BQ hint dispatch
Bundles three threads landed in [Unreleased]:
- Vojta's flea-market content guardrail (two-tier mechanical + LLM)
- Zdeněk's `agnes catalog` ENTITY column replacement for FLAVOR
- Zdeněk's `/api/query` remote_estimate_failed hint dispatch fix
Plus the takeover hygiene from #276 review (agents walker preview/check
parity + body_too_short hint runtime kwarg consistency) and the
backslash-escape fix follow-up to v0.53.4 #275.
No DB migration; no API change. Patch upgrade lands transparently.
Upload form's new "Before you upload" disclosure + per-component preview
table appear on the next dev-VM auto-pull. Quarantine banner now groups
findings by file with "See <type> example ↗" deep-links to the new
/store/examples reference page.
---------
Co-authored-by: ZdenekSrotyr <zdenek.srotyr@keboola.com>
39 lines
2.8 KiB
HTML
39 lines
2.8 KiB
HTML
{# Static "How to fix" guidance rendered below content-quality findings.
|
|
|
|
Shown when either the inline content check or the LLM content-quality
|
|
verdict reports issues. Holds the pattern cheatsheet and the "what
|
|
good enough means" copy so the same guidance is visible from both
|
|
rejection tiers.
|
|
#}
|
|
<div style="margin-top: 14px; padding: 12px 14px; background: rgba(0,0,0,0.04); border-radius: 8px; font-size: 13px;">
|
|
<div style="font-weight: 600; margin-bottom: 6px;">How to fix — write a strong description</div>
|
|
<div style="margin-bottom: 8px;">
|
|
Each component description doubles as the trigger the model reads to
|
|
decide whether to invoke it. Vague descriptions block routing more
|
|
often than they block security — that's why this is enforced.
|
|
</div>
|
|
<details>
|
|
<summary style="cursor: pointer; font-weight: 500;">Pattern cheatsheet</summary>
|
|
<ul style="margin: 6px 0 0 0; padding-left: 22px;">
|
|
<li><strong>Skills</strong>: <code>Use when <trigger condition> — <what it does></code>. The description IS the trigger string Claude reads to decide whether to invoke the skill, so name the condition first.</li>
|
|
<li><strong>Agents</strong>: <code><What the agent does>. Use for <invocation context></code>. The routing layer uses this string; describe the dispatch criterion.</li>
|
|
<li><strong>Plugins</strong>: one-sentence marketplace pitch — <code><verb> <noun> for <audience></code>. Treat it like the elevator pitch on the marketplace tile.</li>
|
|
<li><strong>Commands</strong>: one-verb summary of the action — <em>"Run the test suite and print failures grouped by module."</em></li>
|
|
</ul>
|
|
</details>
|
|
<details style="margin-top: 6px;">
|
|
<summary style="cursor: pointer; font-weight: 500;">What "good enough" means</summary>
|
|
<ul style="margin: 6px 0 0 0; padding-left: 22px;">
|
|
<li>At least 30 characters (20 for commands)</li>
|
|
<li>At least 4 distinct words</li>
|
|
<li>Action-oriented; names the trigger condition</li>
|
|
<li>No <code>TODO</code> / <code>TBD</code> / template placeholders</li>
|
|
<li>Specific (names the domain, tech, or scenario)</li>
|
|
</ul>
|
|
</details>
|
|
<div style="margin-top: 10px;">
|
|
<a href="/marketplace/flea/{{ entity.id }}/edit" style="display: inline-block; padding: 5px 12px; border-radius: 6px; background: rgba(0,0,0,0.08); color: inherit; text-decoration: none; font-weight: 500;">Re-upload as new version →</a>
|
|
<a href="/store/examples" target="_blank" rel="noopener" style="display: inline-block; padding: 5px 12px; border-radius: 6px; color: inherit; text-decoration: none;">See submission examples ↗</a>
|
|
<a href="/store/new#guidelines" style="display: inline-block; padding: 5px 12px; border-radius: 6px; color: inherit; text-decoration: none;">Read full guidelines</a>
|
|
</div>
|
|
</div>
|