- Add knowledge/wiki/hosts/index.md (the one missing section index) and point knowledge/index.md at it. - Add .agents/skills/docs-lint/ (SKILL.md + lint.py) enforcing the mechanical parts of writing-style.md: banned vocabulary and broken relative links. The style guide and this skill are exempt from the banned-word check since they enumerate the list. - Record the restructure + lint in knowledge/log.md. Verification: banned-vocabulary scan of knowledge/ is clean (the few remaining repo-wide hits are false positives — the literal '_' character — or historical append-only plans quoting the vocabulary, which the standard does not restyle). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Lint committed docs against .agents/shared/writing-style.md.
|
|
|
|
Checks two mechanical rules:
|
|
1. Banned vocabulary (significance puffers, analytical verbs, poetic nouns,
|
|
promotional adjectives, opening crutches).
|
|
2. Broken relative markdown links.
|
|
|
|
Prose-voice rules are not machine-checkable; this covers the parts that are.
|
|
Run from the repo root: python3 .agents/skills/docs-lint/lint.py [paths...]
|
|
Exit 1 if any violation is found.
|
|
"""
|
|
import os, re, sys
|
|
|
|
BANNED = [
|
|
"pivotal", "crucial", "vital", "groundbreaking", "transformative", "testament",
|
|
"paramount", "invaluable", "delve", "leverage", "utilize", "facilitate", "foster",
|
|
"showcase", "underscore", "streamline", "harness", "tapestry", "realm", "paradigm",
|
|
"nexus", "cornerstone", "robust", "seamless", "innovative", "cutting-edge",
|
|
"meticulous", "holistic", "comprehensive", "in today's world",
|
|
"it's worth noting", "it is important to note",
|
|
]
|
|
BAN_RE = re.compile(r'(?<![\w-])(' + "|".join(re.escape(w) for w in BANNED) + r')(?![\w-])', re.I)
|
|
LINK = re.compile(r'\]\(([^)]+)\)')
|
|
|
|
def iter_md(paths):
|
|
for p in paths:
|
|
if os.path.isfile(p) and p.endswith(".md"):
|
|
yield p
|
|
for root, dirs, files in os.walk(p):
|
|
dirs[:] = [d for d in dirs if d not in (".git", "node_modules")]
|
|
for f in files:
|
|
if f.endswith(".md"):
|
|
yield os.path.join(root, f)
|
|
|
|
def main(argv):
|
|
paths = argv or ["knowledge", ".agents", "operations", "investigations", "plans"]
|
|
violations = 0
|
|
# The style guide and this skill enumerate the banned words by definition.
|
|
ban_exempt = ("shared/writing-style.md", "skills/docs-lint/")
|
|
for f in sorted(set(iter_md(paths))):
|
|
check_banned = not any(x in f for x in ban_exempt)
|
|
fence = False
|
|
with open(f) as fh:
|
|
for ln, line in enumerate(fh, 1):
|
|
if line.lstrip().startswith("```"):
|
|
fence = not fence; continue
|
|
if fence:
|
|
continue
|
|
if check_banned:
|
|
for m in BAN_RE.finditer(line):
|
|
print(f"{f}:{ln}: banned word '{m.group(1)}'")
|
|
violations += 1
|
|
for m in LINK.finditer(line):
|
|
link = m.group(1)
|
|
if re.match(r'^(https?:|mailto:|#|/)', link):
|
|
continue
|
|
path = re.split(r'[#?]', link)[0]
|
|
if not path:
|
|
continue
|
|
tgt = os.path.normpath(os.path.join(os.path.dirname(f), path))
|
|
if not os.path.exists(tgt):
|
|
print(f"{f}:{ln}: broken link -> {link}")
|
|
violations += 1
|
|
print(f"\n{violations} violation(s)")
|
|
return 1 if violations else 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|