Fix catalog parser (fenced-block extraction) and drop overly strict local markdownlint config

This commit is contained in:
Oleks
2026-07-29 15:46:39 +03:00
parent bf88742bf4
commit caa2a8ccb4
5 changed files with 77 additions and 86 deletions
-9
View File
@@ -1,9 +0,0 @@
{
"default": true,
"MD013": {
"tables": false
},
"MD024": {
"siblings_only": true
}
}
+34 -23
View File
@@ -29,35 +29,46 @@ CACHE_GLOB = os.path.expanduser("~/.claude/plugins/cache/*/{name}/*/")
# signals-catalog.md loader
# --------------------------------------------------------------------------
KNOWN_SECTIONS = {"script-verbs", "judgment-verbs", "mechanical-tools", "thresholds"}
def load_catalog(path):
sections = {}
current = None
"""Reads only the fenced code block immediately following each
recognized `## <section>` heading; everything else is prose for humans
and is never parsed (see references/signals-catalog.md's own header for
why: markdown and a hand-rolled comment syntax collide on bare `#`)."""
with open(path, encoding="utf-8") as f:
for raw in f:
stripped = raw.strip()
if not stripped:
continue
# Section headers look like comments (start with #) but must be
# checked before generic comment-stripping discards them.
m = re.match(r"^#{0,2}\s*\[([a-z-]+)\]$", stripped)
if m:
current = m.group(1)
sections[current] = {} if current == "thresholds" else set()
continue
if stripped.startswith("#"):
continue
line = raw.split("#", 1)[0].strip()
if not line or current is None:
continue
if current == "thresholds":
text = f.read()
sections = {}
heading_re = re.compile(r"^##\s+([a-z-]+)\s*$", re.MULTILINE)
for m in heading_re.finditer(text):
name = m.group(1)
if name not in KNOWN_SECTIONS:
continue
block = re.search(r"```\n(.*?)```", text[m.end():], re.DOTALL)
if not block:
continue
body = block.group(1)
if name == "thresholds":
values = {}
for raw in body.split("\n"):
line = raw.split("#", 1)[0].strip()
if ":" in line:
k, v = line.split(":", 1)
sections[current][k.strip()] = float(v.strip())
else:
for item in line.split(","):
values[k.strip()] = float(v.strip())
sections[name] = values
else:
items = set()
for raw in body.split("\n"):
for item in raw.split(","):
item = item.strip()
if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", item):
sections[current].add(item)
items.add(item)
sections[name] = items
missing = KNOWN_SECTIONS - sections.keys()
if missing:
sys.exit(f"error: {path} is missing section(s): {', '.join(sorted(missing))}")
return sections
+39 -52
View File
@@ -2,106 +2,93 @@
Loaded at runtime by `bin/plugin-inventory`. Kept out of the script body so a
lexicon or threshold change is a reviewable diff, not a code change — per
`design/plan.md` §3.
`design/plan.md` §3. `plugin-inventory` parses this file itself — a genuine
`pure-script` read (T1T5 all pass: closed format, one right parse,
deterministic, no missing context, bounded grammar), so it is not a
candidate for anything more than what it already is.
Format: one `key: value` per line, grouped under `[section]` headers. Lines
starting with `#` are comments. Whitespace-insensitive; commas separate list
values. `plugin-inventory` parses this file itself — a genuine `pure-script`
read (T1T5 all pass: closed format, one right parse, deterministic, no
missing context, bounded grammar), so it is not a candidate for anything more
than what it already is.
Format: each `## <section>` heading below is followed by exactly one fenced
code block, which is the only part the parser reads — comma-separated lists
for the verb/tool sections, `key: value` lines (with `#` trailing comments)
for `## thresholds`. Everything outside a fenced block is prose for humans
and is ignored by the parser, so it's free to use normal markdown, including
literal `#` at the start of a line.
## [script-verbs]
## script-verbs
Occurrences count toward the script-verb signal. Matched case-insensitively,
whole-word, against skill/agent body prose (frontmatter excluded).
```
count, sum, rank, sort, diff, parse, validate, enumerate, dedupe, dedup,
deduplicate, format, compare, aggregate, glob, grep, filter, normalize,
normalise, tabulate, checksum, hash, lint, tally, group, join, merge, split,
truncate, paginate, timestamp, serialize, serialise, deserialize, encode,
decode
```
## [judgment-verbs]
## judgment-verbs
Occurrences count toward the judgment-verb signal. Same matching rule.
```
decide, judge, explain, prioritize, prioritise, name, write, weigh,
interpret, recommend, assess, evaluate, justify, persuade, negotiate,
empathize, empathise, phrase, rephrase, summarize, summarise, synthesize,
synthesise, disambiguate, reconcile, adjudicate, arbitrate, narrate
```
# Note: summarize/synthesize sit in judgment, not script — compressing text
Note: summarize/synthesize sit in judgment, not script — compressing text
into a good summary is not a total function of its inputs (T4), even though
"summarize" sounds mechanical. If a step turns out to be a fixed-format
extraction (e.g. "summarize" really means "pull these five fields"), that is
a digest problem — flag it as low/medium, not high, until a human confirms
the extraction is exhaustive.
# into a good summary is not a total function of its inputs (T4), even though
# "summarize" sounds mechanical. If a step turns out to be a fixed-format
# extraction (e.g. "summarize" really means "pull these five fields"), that is
# a digest problem — flag it as low/medium, not high, until a human confirms
# the extraction is exhaustive
## [mechanical-tools]
## mechanical-tools
Tools whose presence in an agent's `tools:` list leans that agent toward
"built to fetch." Used for `mechanical_tool_share`.
```
Bash, Read, Grep, Glob, WebFetch, WebSearch, LS, NotebookRead,
ListMcpResourcesTool, ReadMcpResourceTool, ReadMcpResourceDirTool, TaskGet,
TaskList, TaskOutput, Monitor
```
# Deliberately excluded (judgment-implying, even when mechanical-sounding)
Deliberately excluded (judgment-implying, even when mechanical-sounding):
Edit, Write, NotebookEdit (produce durable state — a mistake here is
consequential, T5/P4), AskUserQuestion (is itself an escalation), Agent
(spawns judgment, doesn't replace it).
# Edit, Write, NotebookEdit (produce durable state — a mistake here is
# consequential, T5/P4), AskUserQuestion (is itself an escalation), Agent
# (spawns judgment, doesn't replace it)
## [thresholds]
## thresholds
```
verb_ratio_high: 0.6
# verb_ratio (script / (script+judgment)) above this with zero bin/ script
# coverage is the loudest static smell per spec.md §5.1
# coverage is the loudest static smell per spec.md §5.1.
script_coverage_low: 0.15
# script LOC / (skill+agent body words) below this, combined with
# verb_ratio_high, is the target profile for a real candidate
# verb_ratio_high, is the target profile for a real candidate.
procedural_density_high: 0.35
# fraction of a skill body that is numbered/imperative steps containing a
# literal command, above which the skill reads as a recipe being re-derived
# every invocation rather than prose reasoning
# every invocation rather than prose reasoning.
mechanical_tool_share_high: 0.75
# fraction of an agent's declared tools that are in [mechanical-tools]
# above which the agent's own tool allowlist argues it was built to fetch
# fraction of an agent's declared tools that are in mechanical-tools, above
# which the agent's own tool allowlist argues it was built to fetch.
duplicate_block_min_skills: 2
# a fenced command block (after normalization — see FR-3.5 masking rules
# a fenced command block (after normalization — see FR-3.5 masking rules,
# applied identically here) appearing in at least this many distinct skills
# counts as "a shared script that was never written."
hook_prose_drift_min_shared_tokens: 2
# a hook script and a skill body sharing at least this many identifier-shaped
# tokens (snake_case, SCREAMING_CASE, or a literal filename — NOT plain
# English words, which overlap constantly between topically-related prose)
# counts as the hook's logic being restated in prose — two sources of truth
# counts as the hook's logic being restated in prose — two sources of truth.
```
+2
View File
@@ -5,4 +5,6 @@ model: sonnet
tools: Bash, Read, Grep, Glob, Edit
---
# fetcher
Fetches and greps things.
+2 -2
View File
@@ -17,5 +17,5 @@ git status --short
```
| Finding | Action |
|---|---|
| foo | bar |
| ------- | ------ |
| foo | bar |