From bf8420e4abb8219b1a3a52a6a5b83b8994846408 Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 9 Jul 2026 23:19:12 -0700 Subject: [PATCH] update --- .agents/skills/grill-me/SKILL.md | 7 + .agents/skills/peer-consult/SKILL.md | 95 +++++ .../skills/peer-consult/agents/openai.yaml | 7 + .../__pycache__/peer_consult.cpython-314.pyc | Bin 0 -> 18274 bytes .../peer-consult/scripts/memo.schema.json | 30 ++ .../peer-consult/scripts/peer_consult.py | 340 ++++++++++++++++ .../test_peer_consult.cpython-314.pyc | Bin 0 -> 21985 bytes .../peer-consult/tests/test_peer_consult.py | 366 ++++++++++++++++++ .../generate_openai_yaml.cpython-314.pyc | Bin 0 -> 9120 bytes .agents/skills/stop-slop/LICENSE | 21 + .agents/skills/stop-slop/README.md | 62 +++ .agents/skills/stop-slop/SKILL.md | 68 ++++ .../skills/stop-slop/references/examples.md | 59 +++ .../skills/stop-slop/references/phrases.md | 128 ++++++ .../skills/stop-slop/references/structures.md | 134 +++++++ 15 files changed, 1317 insertions(+) create mode 100644 .agents/skills/grill-me/SKILL.md create mode 100644 .agents/skills/peer-consult/SKILL.md create mode 100644 .agents/skills/peer-consult/agents/openai.yaml create mode 100644 .agents/skills/peer-consult/scripts/__pycache__/peer_consult.cpython-314.pyc create mode 100644 .agents/skills/peer-consult/scripts/memo.schema.json create mode 100755 .agents/skills/peer-consult/scripts/peer_consult.py create mode 100644 .agents/skills/peer-consult/tests/__pycache__/test_peer_consult.cpython-314.pyc create mode 100644 .agents/skills/peer-consult/tests/test_peer_consult.py create mode 100644 .agents/skills/skill-creator/scripts/__pycache__/generate_openai_yaml.cpython-314.pyc create mode 100644 .agents/skills/stop-slop/LICENSE create mode 100644 .agents/skills/stop-slop/README.md create mode 100644 .agents/skills/stop-slop/SKILL.md create mode 100644 .agents/skills/stop-slop/references/examples.md create mode 100644 .agents/skills/stop-slop/references/phrases.md create mode 100644 .agents/skills/stop-slop/references/structures.md diff --git a/.agents/skills/grill-me/SKILL.md b/.agents/skills/grill-me/SKILL.md new file mode 100644 index 0000000..9470cfc --- /dev/null +++ b/.agents/skills/grill-me/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-me +description: A relentless interview to sharpen a plan or design. +disable-model-invocation: true +--- + +Run a `/grilling` session. diff --git a/.agents/skills/peer-consult/SKILL.md b/.agents/skills/peer-consult/SKILL.md new file mode 100644 index 0000000..01e6f3a --- /dev/null +++ b/.agents/skills/peer-consult/SKILL.md @@ -0,0 +1,95 @@ +--- +name: peer-consult +description: Request an independent, read-only repository review or second opinion from the opposite model provider, using Claude from Codex or Codex from Claude. Use only when the user explicitly asks to consult, ask, review with, or get an opinion from Claude or Codex about an issue, feature, design, plan, diagnosis, or decision. Never invoke proactively. Never use for implementation or file changes. +--- + +# Peer Consult + +Get one independent opinion from the opposite provider. Preserve the repository unchanged and return a standardized decision memo. + +## Preconditions + +- Require an explicit user request for cross-provider consultation. A normal request for review, planning, or advice is insufficient unless it names Claude, Codex, a peer model, or a second opinion. +- Run Claude only when the active agent is Codex. Run Codex only when the active agent is Claude. +- Do not use this skill from any other caller. +- Do not edit files, implement the recommendation, or perform follow-on changes unless the user separately requests them after seeing the memo. + +## Route the request + +Classify the smallest adequate tier: + +| Tier | Signals | Codex peer | Claude peer | +|---|---|---|---| +| Quick | Narrow, bounded, one component | `gpt-5.6-terra`, medium | `claude-sonnet-4-6`, medium | +| Quick, context-heavy | Still bounded; more context or nuance | `gpt-5.6-terra`, high | `claude-sonnet-5`, high | +| Standard | Multi-file feature, ordinary design/debugging, meaningful trade-offs | `gpt-5.6-sol`, medium | `claude-opus-4-8`, medium | +| Deep | Architecture, concurrency, security, broad ambiguity, high-impact choice | `gpt-5.6-sol`, high | `claude-opus-4-8`, high | + +Honor an explicit model override only when it is allowed for the target provider: + +- Codex: `gpt-5.6-terra`, `gpt-5.6-sol` +- Claude: `claude-sonnet-4-6`, `claude-sonnet-5`, `claude-opus-4-8` + +Reject GPT-5.6 Luna, pre-5.6 Codex models, Claude Haiku, every other Claude model, aliases, and cross-provider model names. Never silently fall back. + +## Prepare independent context + +Write a self-contained request containing: + +- The question or decision to assess. +- Relevant constraints and success criteria. +- Relevant paths, symbols, errors, or raw plan text. +- Specific uncertainties the peer should resolve. + +Pass raw evidence. Avoid telling the peer the expected answer or presenting the active agent's conclusion as fact. Let the peer inspect the current repository independently. + +## Run the broker + +Resolve `` to the directory containing this `SKILL.md`. Send the request on stdin; do not interpolate it into a shell command. + +From Codex: + +```bash +python3 /scripts/peer_consult.py \ + --caller codex \ + --complexity standard \ + --repo "$PWD" <<'PEER_REQUEST' + +PEER_REQUEST +``` + +From Claude: + +```bash +python3 /scripts/peer_consult.py \ + --caller claude \ + --complexity standard \ + --repo "$PWD" <<'PEER_REQUEST' + +PEER_REQUEST +``` + +Adjust only these routing flags when needed: + +- `--complexity quick|standard|deep` +- `--context-heavy` for the context-heavy quick tier +- `--model ` for an explicit allowed override +- `--timeout ` when the default 600 seconds is unsuitable +- `--dry-run` to inspect routing and command construction without calling a provider + +The broker enforces pinned models, opposite-provider routing, nonpersistent execution, tool restrictions, structured output, and read-only repository access. + +## Return the result + +Return the broker's memo without changing its meaning. Keep its sections: + +- Verdict +- Evidence +- Risks +- Alternatives +- Recommendation +- Confidence + +Identify it as the peer provider's opinion. If useful, add a short, separately labeled synthesis explaining how the feedback affects the active discussion. Do not implement anything. + +On failure, report the selected provider, model, and exact failure. Do not retry with another model unless the user explicitly requests it. diff --git a/.agents/skills/peer-consult/agents/openai.yaml b/.agents/skills/peer-consult/agents/openai.yaml new file mode 100644 index 0000000..963f465 --- /dev/null +++ b/.agents/skills/peer-consult/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Peer Consult" + short_description: "Independent cross-provider second opinions" + default_prompt: "Use $peer-consult to get an independent read-only second opinion on this decision." + +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/peer-consult/scripts/__pycache__/peer_consult.cpython-314.pyc b/.agents/skills/peer-consult/scripts/__pycache__/peer_consult.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4f87993887af6921055c27aaddaa9a1691e2516 GIT binary patch literal 18274 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 literal 0 HcmV?d00001 diff --git a/.agents/skills/peer-consult/scripts/memo.schema.json b/.agents/skills/peer-consult/scripts/memo.schema.json new file mode 100644 index 0000000..4520b98 --- /dev/null +++ b/.agents/skills/peer-consult/scripts/memo.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "verdict": { "type": "string", "minLength": 1 }, + "evidence": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "risks": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "alternatives": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "recommendation": { "type": "string", "minLength": 1 }, + "confidence": { "type": "string", "enum": ["low", "medium", "high"] } + }, + "required": [ + "verdict", + "evidence", + "risks", + "alternatives", + "recommendation", + "confidence" + ], + "additionalProperties": false +} diff --git a/.agents/skills/peer-consult/scripts/peer_consult.py b/.agents/skills/peer-consult/scripts/peer_consult.py new file mode 100755 index 0000000..c2eff8b --- /dev/null +++ b/.agents/skills/peer-consult/scripts/peer_consult.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +"""Run a read-only consultation with the opposite model provider.""" + +import argparse +import json +import os +import shutil +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path + + +ALLOWED_MODELS = { + "codex": frozenset({"gpt-5.6-terra", "gpt-5.6-sol"}), + "claude": frozenset( + {"claude-sonnet-4-6", "claude-sonnet-5", "claude-opus-4-8"} + ), +} + + +def detect_caller(env: dict[str, str]) -> str: + is_codex = bool(env.get("CODEX_THREAD_ID")) + is_claude = env.get("CLAUDECODE") == "1" or bool( + env.get("CLAUDE_CODE_ENTRYPOINT") + ) + if is_codex and is_claude: + raise ValueError("Ambiguous caller environment") + if is_codex: + return "codex" + if is_claude: + return "claude" + raise ValueError("Cannot detect caller; pass --caller claude or --caller codex") + + +@dataclass(frozen=True) +class Route: + provider: str + model: str + effort: str + + +def select_route(caller: str, complexity: str, context_heavy: bool = False) -> Route: + if complexity not in {"quick", "standard", "deep"}: + raise ValueError(f"Unknown complexity: {complexity}") + effort = ( + "high" + if complexity == "deep" or (complexity == "quick" and context_heavy) + else "medium" + ) + if caller == "claude": + model = "gpt-5.6-terra" if complexity == "quick" else "gpt-5.6-sol" + return Route("codex", model, effort) + if caller == "codex": + if complexity == "quick": + model = "claude-sonnet-5" if context_heavy else "claude-sonnet-4-6" + else: + model = "claude-opus-4-8" + return Route("claude", model, effort) + raise ValueError(f"Unknown caller: {caller}") + + +def apply_model_override(route: Route, model: str) -> Route: + if model not in ALLOWED_MODELS[route.provider]: + raise ValueError(f"Model {model!r} is not allowed for {route.provider}") + return Route(route.provider, model, route.effort) + + +def build_command( + route: Route, + repo: Path, + schema_path: Path, + output_path: Path | None = None, +) -> list[str]: + if route.provider == "codex": + if output_path is None: + raise ValueError("Codex requires an output path") + return [ + "codex", + "exec", + "--ignore-user-config", + "--model", + route.model, + "--config", + f'model_reasoning_effort="{route.effort}"', + "--config", + 'approval_policy="never"', + "--sandbox", + "read-only", + "--ephemeral", + "--skip-git-repo-check", + "--output-schema", + str(schema_path), + "--output-last-message", + str(output_path), + "--json", + "--cd", + str(repo), + "-", + ] + + schema = json.dumps(json.loads(schema_path.read_text()), separators=(",", ":")) + return [ + "claude", + "--print", + "--safe-mode", + "--model", + route.model, + "--effort", + route.effort, + "--permission-mode", + "plan", + "--tools", + "Read,Glob,Grep", + "--disable-slash-commands", + "--no-session-persistence", + "--prompt-suggestions", + "false", + "--output-format", + "json", + "--json-schema", + schema, + ] + + +def build_peer_prompt(question: str, route: Route) -> str: + return f"""You are an independent {route.provider} reviewer. Inspect the current repository in read-only mode and give a second opinion on the request below. + +Hard constraints: +- Do not modify, create, rename, or delete files. +- Do not implement code or run commands that can change state. +- Do not commit, branch, push, open pull requests, or contact external systems. +- Do not delegate to subagents or invoke Claude, Codex, or peer-consult recursively. +- Base claims on repository evidence. Cite file paths and line numbers when available. +- Call out uncertainty and missing evidence. Do not pretend the caller's proposal is correct. +- Return only the structured decision memo required by the response schema. + +Request: +{question.strip()} +""" + + +MEMO_FIELDS = { + "verdict", + "evidence", + "risks", + "alternatives", + "recommendation", + "confidence", +} + + +def validate_memo(value: object) -> dict[str, object]: + if not isinstance(value, dict) or set(value) != MEMO_FIELDS: + raise ValueError("Invalid decision memo fields") + if not isinstance(value["verdict"], str) or not value["verdict"].strip(): + raise ValueError("Invalid decision memo verdict") + if not isinstance(value["recommendation"], str) or not value["recommendation"].strip(): + raise ValueError("Invalid decision memo recommendation") + for field in ("evidence", "risks", "alternatives"): + items = value[field] + if not isinstance(items, list) or any( + not isinstance(item, str) or not item.strip() for item in items + ): + raise ValueError(f"Invalid decision memo {field}") + if value["confidence"] not in {"low", "medium", "high"}: + raise ValueError("Invalid decision memo confidence") + return value + + +def parse_claude_output(output: str) -> object: + try: + envelope = json.loads(output) + except json.JSONDecodeError as error: + raise ValueError("Claude returned malformed JSON") from error + if isinstance(envelope, dict) and "structured_output" in envelope: + return envelope["structured_output"] + if isinstance(envelope, dict) and isinstance(envelope.get("result"), str): + try: + return json.loads(envelope["result"]) + except json.JSONDecodeError as error: + raise ValueError("Claude result did not contain structured JSON") from error + return envelope + + +def _bullets(items: object) -> str: + values = items if isinstance(items, list) else [] + return "\n".join(f"- {item}" for item in values) or "- None identified." + + +def render_memo(memo: dict[str, object], route: Route) -> str: + provider = route.provider.capitalize() + return f"""## Peer consultation + +{provider} · {route.model} · {route.effort} + +### Verdict + +{memo['verdict']} + +### Evidence + +{_bullets(memo['evidence'])} + +### Risks + +{_bullets(memo['risks'])} + +### Alternatives + +{_bullets(memo['alternatives'])} + +### Recommendation + +{memo['recommendation']} + +### Confidence + +{memo['confidence']} +""" + + +def run_consultation( + route: Route, + repo: Path, + prompt: str, + schema_path: Path, + *, + timeout: int = 600, + env: dict[str, str] | None = None, +) -> dict[str, object]: + process_env = dict(os.environ if env is None else env) + if shutil.which(route.provider, path=process_env.get("PATH")) is None: + raise RuntimeError(f"Required CLI is not installed: {route.provider}") + + with tempfile.TemporaryDirectory(prefix="peer-consult-") as temp_dir: + output_path = ( + Path(temp_dir) / "memo.json" if route.provider == "codex" else None + ) + command = build_command(route, repo, schema_path, output_path) + try: + result = subprocess.run( + command, + cwd=repo, + env=process_env, + input=prompt, + text=True, + capture_output=True, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired as error: + raise RuntimeError(f"{route.provider} consultation timed out") from error + if result.returncode != 0: + detail = (result.stderr or result.stdout).strip() or "no error output" + raise RuntimeError( + f"{route.provider} consultation failed ({result.returncode}): {detail}" + ) + if route.provider == "codex": + if output_path is None or not output_path.is_file(): + raise RuntimeError("Codex did not produce a decision memo") + try: + value = json.loads(output_path.read_text()) + except json.JSONDecodeError as error: + raise RuntimeError("Codex returned malformed JSON") from error + else: + value = parse_claude_output(result.stdout) + return validate_memo(value) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Get a read-only second opinion from the opposite model provider." + ) + parser.add_argument("--caller", choices=("claude", "codex")) + parser.add_argument( + "--complexity", + choices=("quick", "standard", "deep"), + default="standard", + ) + parser.add_argument("--context-heavy", action="store_true") + parser.add_argument("--model", help="Approved target-model override") + parser.add_argument("--repo", type=Path, default=Path.cwd()) + parser.add_argument("--timeout", type=int, default=600) + parser.add_argument("--dry-run", action="store_true") + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + try: + question = sys.stdin.read().strip() + if not question: + raise ValueError("Consultation request must be provided on stdin") + repo = args.repo.expanduser().resolve() + if not repo.is_dir(): + raise ValueError(f"Repository directory does not exist: {repo}") + caller = args.caller or detect_caller(dict(os.environ)) + route = select_route(caller, args.complexity, args.context_heavy) + if args.model: + route = apply_model_override(route, args.model) + prompt = build_peer_prompt(question, route) + schema_path = Path(__file__).with_name("memo.schema.json") + output_path = ( + Path(tempfile.gettempdir()) / "peer-consult-dry-run-output.json" + if route.provider == "codex" + else None + ) + command = build_command(route, repo, schema_path, output_path) + if args.dry_run: + print( + json.dumps( + { + "provider": route.provider, + "model": route.model, + "effort": route.effort, + "command": command, + "prompt": prompt, + }, + indent=2, + ) + ) + return 0 + memo = run_consultation( + route, + repo, + prompt, + schema_path, + timeout=args.timeout, + ) + print(render_memo(memo, route), end="") + return 0 + except (OSError, RuntimeError, ValueError) as error: + print(f"peer-consult: error: {error}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.agents/skills/peer-consult/tests/__pycache__/test_peer_consult.cpython-314.pyc b/.agents/skills/peer-consult/tests/__pycache__/test_peer_consult.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20d6fafec5919e3d53eb20a83a588ef5d7d8474d GIT binary patch literal 21985 zcmeHvd2kz7nqPysFA^ZZOQb}I)J2K_b>BBd>Yzl^uuREgN>~VJk+47j?glLh$!?UX z$_};bT}k$&B+qzaI@yY;R8pf!O=3+pm9adTT4%D^046CSZKdK#CfPX2RydNf+T}mV z?|XgVAS6-~*HluqE%EE_*YE1r?|sMb`ySR;I&B<|>%TYf#?7r9_n+uSJ&el5<3G`H z+*R%z7tkH!QaX07NL8@2KBZ@8L(0I;#*~qrO(_#Qn^R_-D~?%?T2s~*?yPkschT#;2^XEaW7fIb9H&B^>qFxQS)vNq@P^b32g&V|}wpXN= zl?4?N-V3N_XJcz$wYZUyh0?QK~#BioB4DCgMGUkPswmbTpX| zQnBcH$uO3R#-w^N$%lg%gv4m@LNvw)V~KExvP-VfL}UyJ>=9*>OtC~L!V8j}zlOM%!w;Qx*Uw!$H|y-oIy)y%O*uDR?adi1-|G9DeY1w9 ztfA?4@3f&UXLnyc`53ixMUzuuD%|58coIU>XThuj92ix7N?*q%F!8}I41o$zV?YmT z3K&4m0b|NC$fc|S6F#I_as(0>^Z4*7df~8ID>diBA11oxaYUZ}+B0>kCv^dRzz{G7 zOaXJi60inr0eip^a0V&^RRLGP9jNZIBr66hp0`|FT<=AWyM!+!Y{7)omJ4-2J_hvx z-FBtz-MXM|tYS(wOaG(>eZlefQbV2UOpog%eM4D zYMF(ySwCn{mm*kA1^rW*ZwuEq%5he11=8C%FSnl4*aF_PnTuAmaLc(C_OI`o9JgD) zoQvw(Ya~-R78;B2U{t1PJUNyU``h(G13Dxb-yDmEFZQ|mQ9cqK8&$FkBvnq~rU_?(#DruRi4KpbwOPbeC>{w35mdQ~z-R0w^ zITPnxn^|`v=UAOtdo1T@&#XVmu8%M}kaMictm(@+)@3?gmapgBR<~{3I9JE5T=S9r zL;K%%W*yDry*YR7t>&A}6N5LJXWgAM?#^7pirI$spEsFd@2-Aohw!+Ef(k)!k&B9YAwh+E?NIG({XsEv{% zpu-AQN?}Fo>0f?D2lR`rWXhqeWJ9|8)%eAD;!@ljPK+jFkbJ2tY@tbJ{&Gs-NBRDC zgFHvw@+=I5qL@|zewe>3*#<+gF`h|BVGVkDr$ShdlR)|olV)p_>D4076#flNLNPcd zHwk@4ZBptiGq0YR+fUW+1<@wdmrnPrvn}gv`-9_|4Too)Ju}XpoTGZy(UNtvOqeGd zryZN0>bc5Z9iHL;=gyvak@S~4H3v<``zHIrdTY_ArS-u+?Qwk6tK18GT1_+zLd>b3 zDWFrYm>?+gQ6V16uzxUXy;?G$v2h41-N4f8fLPV)FsWBbJ*H+E4Gv7>dg3Aa3nA!X)#%jFkz4xt9EE-|TRDs6E7%~$XGJj{x zns{k{yIG#2HhGSo62^GRJQ5N^sgxk>LJl@Pg|&3m4uUzYO;pLw1eL;T9WuLDSRt)b zVv{nH+{k8eC>-XYlLr%VyhbV{4D+d=%9Bf8OO*C4oYyZ>o>Um|eGqM?=PZuvRoAK> zI%;n8W|}umIX34Uo4~`YwmAz|zkIfCW43N%e(GB$*G@S$yemWAN5lX6k3i<4YqN|oQ<3Hm=V$uFv@P zJ+SN-Jm_H2>LHbdt)91kO{=GHog^y)W~giDIF8NnGOG%zn&G*sTG<&sq^*m9QC&sP zVA@r9|B~Z$n_31d!9@QGSa!*M-)|^N8C2irD`h8E$c+5?x%$R6)y*QWhTZ60el@l0 z*rJ%X#}g@UC>Bdx;v-=D^A1(lijBoXX|sH{RcJ-wv`bNoMnchxW6*ZCw#%xFu!pK9 z<}5F{)pSMESOAd0vDOP0pU_3Av?RsiwYIZJVnm{%(yw4b2woBr@!@B$x!#3qPEcgY zi`3Igo%$}w($=24e%95Nb+t_#ogAKWZ5uZ}HW=${<2`c@uDXsXDLtg5bj-Lra;K{Ld(PR~-kI9ooM+jrrz`8}x@(;F?1iYRJE8-vUY~V$Ja(d@r?|wtep-txd+p7T>4%nBmR+&CXk1cSl`G$Cv!a)3w=h*TR4LNiZA!$IP((euz! zMbzXD1~Dx}F%k`@!h9?i4BpX6&Vob~BPHno>XAEwG}?*AlQ{ zRPB;8!p1WgW`jH5B%i%RPiG!~m z?x75lZX;t@Qk|R@q{LuP|Ea)h11}xxKPBnIv1ot0k?8>iOo)m6S1>PxZ6HNTX$NC5 za-T#fqp(()siSBBeEh8_7|keQFVg;U(?qs^mQ(@3F5=$;DP<<_n$jjjJt6^P{z*R6(lC~kW9c493d!M})R5|;c;yijM z{vOC;DeYV1(x4k`>b=%jB}5cEz`RB?yL7seAbp( z*+1=gi3;q^I`_cXM?q9M|EJD9D3WpRnHLG9xNodJwA^@qZ{48{#!r{q5BaR0Zm@Uj ztilmI?1zGL6ldWWk>f=Ah`dfDNaO^OlSKMK3I*NElt|asNBGf@{|x|rs*T9N34uB*87Mm`MGUdxyU+lLqNvE?>boF(%dP%0If;0qhlEJznSZU zmH;<~TYUqRscDm48`Z^W@JpA}rao8Ca@qD;$$5S(8jBE4No;d|nR4M}$-GdY#mdGs znK0>E#*vtp4(L6cBaHp*>i>!$+kRQGHIN%n@KW{`hI|DDJ_IHO2bY4h?`@SGC9Uat z8IBLdg2_ZI8osi(HO>PzYn`{EOvFbpRNC(I@ky{cUI@j~9-l8d98U9@dz+&#zVYJLsQCllm)kJk#9sqtkc8x6e+7A1vSaho}FNVG7@@_UKOa z_J8%iakM)NOarnOWsm|+Fis%FL>M7LW5TdP$s*&4$KsM+Ou>6A-}a&pJYF(>1x_M{ zBNB=snU!veR350IEHU8sf@fu^uPE*M)rV&c&5C_k!B;s2C4_NdAaBWJaH?T%X8-Hc zmLWJqZrN|zXI<;Eu62_QcXv&>_Kh2JfGF1ATt5rEp?ZDJ;m$c+*N;bqWXJH6PNCqGHYO-?4X97*PJ&-Ct%I^l)Fsb_Ivfy zmV?@D*InH1RP$b(K*{y$T}jE#$>C|sHud4ign8PsPPzMb`1Yy~*1xxYs&36Bk~b*H znL6L(rD@A{+$w0L+{f^=r9(^mpysiFx-!Dzzwy#T3{SAj>1fy2`lM}>P(w`zN8S1r}94Tc?c&lbbuBM^)y;@XR)2r5Mkg{oaVhx0F5_HDE) zE`K4;Uw?Palxt@R=b!BPc;^px-i!SB;*Ty)RUaQW=i#XO3HSGwO`Msw_!TyLBRT;l z-mWp-wUfBrs6M=K8*F=xdRM~J+b6;BH>(eevS#Ww++8cXnn;dh@~mCq<#lqawap)# zeedk#{;B1=?}cWT@1LnXFm6MQa<2D1A1wRMvWeqUja``yy)%tRKC69Td1)apw_w~L ztkuvV=){Wti*)Vyf7QN%2CjLP6`vPS`e*ue^#S7|J};;C%G$;0O$95z)Jp~3kSTn&VEEe%z;=N)r7EO6m zBM9h&mityT%dH7=$2Vc+N*7z@bM-sg{Fo1Rl{;0&difNZkw7!xygP?@)J0abBP&W_$;x8V+W< zp(;g+6~+3y9aFBoU>69uX}Z}o>-J~e{!CYYX5iH+_u#ncvB3=Ya_^i=b#~<=a-eWJ zZ#r*Zn0EQdb;ZJL+{pbDd3|c2jRW1ezpq-0vw>pGhFDX|;l3&hzrBHi!rvhByF}h0 za)L-%AnrUpA(FtKNK#7y;`GLsb&qVd#v{5%eix9qr>iQB{y7dr#^k6>7=PNKGK$9w zFuCu)5GH3m37l#nAV=fDIMVC=M^M5+bZV$$7u;QO=&JG&a6o=L zB{M8Y^3tVOy~5TJS&`n*1;priM`4MML4*$@Bxno|UWni9RMjn{l0Y6X6pCm9Cew-gPDdyIY&*Vt|tQ@uVYze`GHLH!JMNxvjXu+ z4Y$|LInA{;fPdEK2&ZOzyszERQ0FhYt{#gDIt@0<2NW??-=NlzKbHj(F*#Ln!os@C zl`9vnQ5Hs2RaIu5l&h(nI9sW;uMb!eBtiny0I^?{@40$4Xb6}V5twDv{L|fP3jmSK z^iMUTfS@P>6YZ5L30P7K)1tDAg?{w?h9*>Pl2eJ7;)67G=a&&JA44VC0dXUGlx0je zvW#*Ht@wO&ysX!g(1lbr)9S?Y{;@VBbThrx`{~DCS%dYd0k4##g!h`$p0+mc0LgVF zR9#-5_BJFBD!j)*H$5t6vC>_DaSaz;9xP-9)$C#MTCb$EZbG-%LT zq-`fb8FQCi#Y-&rht%+AAPWR^A&?^nf@u)X*{O!znY~cZUn?)Uzw|WR>bu!D>+xkh zzRAe!m zlIFwHt_>v3yPyXAvIe;knQL{*D*NtxXMmhgNNpDiW!(OGk;K_gEY;n+%s<&>KWu+S z7_A>n>FZ^5LuNG04lFv-_Dc?unurC1q@{x~CE&=V$C4wz%KJvjF&8b6153R`uci$T z&yVM#9Ev_|7TEm$yXZ3TM_9d~DC|}W-3_%@2oyOeSs=|}l0#A&za-1dCX z_+I1Wl~1-$d-^bE3w*<-S?Bf{=XM3EoOP_uI#y3aryVpsD|g@_{(tV=F3-XH%bO4B zji2i6hdj@S_ceU|b5L_xmYad_MzA@E`Dw#AlpOhZ12#9yh%}TlloY~kS%Id_x$
    59`lcps?IAd8h&C-L-u5u@&&pc*gWZ-%KChco%0dp`aY3+L{1PX zYv=rJdP0OU7uq=pICxZ7LuO8g(YWzxrP=rri#>Qri9Mh)Vf<+amIFH$#@7A3oSjn? zTW9HxMHiYjG>O4*EsuZ3^xCg8wEp=U4f9(r7Y6<$D~7b?SI#lIk{ha2MuHn~saN^) zkmhI}@~Ek*hAJ;`3L%0+)atV=Dc^sTcqXv?%o6UR zHmZx-XjA)|KL={iN|D=n(GnJvs4Z+IUxLi#=L_Q#4-`>c2fGo{cfPUv#4#`IPvJ^3 z0jsq@r=GX?d~zft)2`Cu^H!fPB3$taV{wK(o$9?~AUGP{WH4Cp zFR3ydN)lUDxGHvgjqu@%{lYqogzSu_plTU~@1QF(bA@jb`7I(hL4f1aw9V@Sk_Fq) zk`xUiRiDCDLI??04g-M-6C4Jpd+aVXjo zq8o?=$2h}(jLI}PokeoW_*?K&GzHkOV3!7c1gTHuPO@(Pe^uFkL}$fDraCBF+z8#q zJ`EP$X_{*2%4|G==*)-C+F9qi&z*Ww+Ll}A-Z}RRPyMa_oBaHQQ(@8oMUs{!b%zTZsU*1MPPU7p!~^nvBr)6M7`URK#kXE@09 zR2zR4C*jCsaR8;il%iK(;A#TsL$$R$lQ z;2o6%yR0-Zp{8;8Er&egDbEfAhyyAo0`lbomC)U)gnxlrg&!07gb2Cjg!@EDPLPw1 zMV$zTDd|&6stEw%<1fj8dUzxOJl%h)SNI8K=msHAS~0T_en!v!HIY9i@^6S}3_z-s zVz$T*anOXkqa6|52nvz+4AK@9ZM#_TJP&(>56YYe zxSzD6crh=%B!~I->~PeAmFsT0wc?!>zi`#us=QhG&{gy8-goUe*Rq_eA?I3=bJgcu zjqIWc3)NYPg<7)?V0o^oC0AXKg<5UHw%1i`jW%U1*fKxc?8`R$Cf_V@nvG5jesuQ3 zvkxqrWzhKu#^qUIr=QbEJ|OaJE%2YyVT>MrJ{d)ig@P@-##4wrSeZ zLc+^-&30q!154vV;GGNs_Wmx=uNjY81Pj=rj9nQ6MK`1&Q~BRH0r-udb0`dvhW{6M zLQ#qV>U|mHKy3@(&+z=`&o_O5U=W0kh|JO>zeR}syhWL47VN|{7WNe;l95D$dIZdc zcX=r{q?sf6h|zK;(4gLdc;Dye30MnNe+RcoAFyqQX-Y>DzO%CU3Te|N2mPps+QX#z z8RbaHFF6LyWJnEp)#QOT^(uePNBs<%0#%xJVZqOr;9uU*L#wOvrPQl4_#u^Y-$(5& z)~9PxGsmx(Ic~{CI(H$eKfj{z&Wp$KqTpCbe?A(Q<17CO`oy%6^s2U%T}bW{M_?s{ zB5=;VIfh6{8&l>lc-O8L*Lv5cE7sa1-7fqb(!o~sdy6+8p!z~r6t0P2z&Jl z5j?@RLp>m){yQ~!<3`3qciL2 zoIHK^jVaH;@t!YLFGp9lrYq;Fg&!m5TK&*fC;vJ~X65E7*Or{8e$LK$HazA`p33pV zPnVNj)tR@eJhxLHTzT)x155i;U$I$p*xs|%_|vWSUSqLYqluEg07LmJQPQ#qiUuUA zJfp(gq~kear07TJ5NK)y<2P+8@Tk6!$S^M=GAs*3hV@qx888-ACj7!8!?B3SaH}YgfT*5l-DEvNJE9Ipi59IcG#gXWR6uT;x zCcd@aeW-t9D|<#icH`ZE%L`Euxl9BqKL9N}*l82~l={j<&R2BBjA)WO!ha(YA@bir z`h`CORoK7qh>{-@c|wF<6%I7`frAuAx&+R@xqx?Tdy+{+icpvpOl|@HKlE(=ztlQ< zNeXQJ^+50(+L9pys5CmuLrl&lIenmmhMS@^oSwXPBSP*pY?`b#wwB6pb(<=NkeER2xt^J%P z$p0epmqbnwIY|WZl12MR{~aX~A=_D8fs6e9(MNR^#$}HzO~#I=yH*)H<~R`9qNf71 zyOB`oV6eSjs?>hK&`-Z#D3ElL@!0=KS3e{2$3#d;k}PBKXo}_{p|GhD{OqB;G3Em0 z7$!nm5CerI3z_!a2)vYk)8S=$aGnVHlNbcSx-UoYF>LAxJ@61A!$Gp_k>k4d2?WUC zKPM8xF{jt*bYF0_UvTy>IO`Xj`wPzY1y_at|DJ33rHQjuj*tBAidkz**4i?mo3XY| zyf$O?XEq+1u^zf=dSo)`s-INv*X_HpW42~hwr1564mVE@SJc3!y}e_$sWaQu`Gljp zC!KqB{(LsvJULL&rF%(t!!qkynRTsv!qMH6h;FNH%lPHlDsQ&R`-H>Ilcertov!1? rnc3Pk+1fQvIJ%n~so)Hb@$|I5F=udm>*P02{`O11)N_Vq?4AD)8gN&{ literal 0 HcmV?d00001 diff --git a/.agents/skills/peer-consult/tests/test_peer_consult.py b/.agents/skills/peer-consult/tests/test_peer_consult.py new file mode 100644 index 0000000..82fe347 --- /dev/null +++ b/.agents/skills/peer-consult/tests/test_peer_consult.py @@ -0,0 +1,366 @@ +import importlib.util +import json +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).parents[1] / "scripts" / "peer_consult.py" + + +def load_broker(): + if not SCRIPT.exists(): + raise AssertionError("peer_consult.py is missing") + spec = importlib.util.spec_from_file_location("peer_consult", SCRIPT) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class RoutingTests(unittest.TestCase): + def test_routes_approved_models_and_effort(self): + broker = load_broker() + + cases = { + ("claude", "quick", False): ("codex", "gpt-5.6-terra", "medium"), + ("claude", "quick", True): ("codex", "gpt-5.6-terra", "high"), + ("claude", "standard", False): ("codex", "gpt-5.6-sol", "medium"), + ("claude", "deep", False): ("codex", "gpt-5.6-sol", "high"), + ("codex", "quick", False): ("claude", "claude-sonnet-4-6", "medium"), + ("codex", "quick", True): ("claude", "claude-sonnet-5", "high"), + ("codex", "standard", False): ("claude", "claude-opus-4-8", "medium"), + ("codex", "deep", False): ("claude", "claude-opus-4-8", "high"), + } + + for inputs, expected in cases.items(): + with self.subTest(inputs=inputs): + route = broker.select_route(*inputs) + self.assertEqual( + (route.provider, route.model, route.effort), + expected, + ) + + def test_rejects_unknown_complexity(self): + broker = load_broker() + + with self.assertRaisesRegex(ValueError, "Unknown complexity"): + broker.select_route("codex", "extreme") + + def test_accepts_only_target_provider_model_overrides(self): + broker = load_broker() + self.assertTrue( + hasattr(broker, "apply_model_override"), + "model override validation is missing", + ) + codex_route = broker.apply_model_override( + broker.select_route("claude", "quick"), + "gpt-5.6-sol", + ) + claude_route = broker.apply_model_override( + broker.select_route("codex", "quick"), + "claude-opus-4-8", + ) + + self.assertEqual(codex_route.model, "gpt-5.6-sol") + self.assertEqual(claude_route.model, "claude-opus-4-8") + + def test_rejects_disallowed_or_wrong_provider_model_overrides(self): + broker = load_broker() + self.assertTrue( + hasattr(broker, "apply_model_override"), + "model override validation is missing", + ) + route = broker.select_route("claude", "quick") + + for model in ( + "gpt-5.6-luna", + "gpt-5.5", + "claude-sonnet-5", + "claude-haiku-4-5", + ): + with self.subTest(model=model): + with self.assertRaisesRegex(ValueError, "not allowed"): + broker.apply_model_override(route, model) + + +class CallerDetectionTests(unittest.TestCase): + def test_detects_codex_and_claude_environments(self): + broker = load_broker() + self.assertTrue(hasattr(broker, "detect_caller"), "caller detection is missing") + + self.assertEqual(broker.detect_caller({"CODEX_THREAD_ID": "abc"}), "codex") + self.assertEqual(broker.detect_caller({"CLAUDECODE": "1"}), "claude") + self.assertEqual( + broker.detect_caller({"CLAUDE_CODE_ENTRYPOINT": "cli"}), + "claude", + ) + + def test_rejects_missing_or_ambiguous_caller(self): + broker = load_broker() + self.assertTrue(hasattr(broker, "detect_caller"), "caller detection is missing") + + with self.assertRaisesRegex(ValueError, "Cannot detect"): + broker.detect_caller({}) + with self.assertRaisesRegex(ValueError, "Ambiguous"): + broker.detect_caller({"CODEX_THREAD_ID": "abc", "CLAUDECODE": "1"}) + + +class CommandTests(unittest.TestCase): + def setUp(self): + self.broker = load_broker() + self.repo = Path("/tmp/example-repo") + self.schema = Path(__file__).parents[1] / "scripts" / "memo.schema.json" + + def test_builds_read_only_ephemeral_codex_command(self): + self.assertTrue(hasattr(self.broker, "build_command"), "command builder is missing") + route = self.broker.select_route("claude", "standard") + command = self.broker.build_command( + route, + self.repo, + self.schema, + Path("/tmp/peer-output.json"), + ) + + self.assertEqual(command[:2], ["codex", "exec"]) + self.assertIn("gpt-5.6-sol", command) + self.assertIn('model_reasoning_effort="medium"', command) + self.assertIn('approval_policy="never"', command) + self.assertIn("read-only", command) + self.assertIn("--ephemeral", command) + self.assertIn("--ignore-user-config", command) + self.assertIn("--json", command) + self.assertIn(str(self.schema), command) + self.assertIn("/tmp/peer-output.json", command) + self.assertEqual(command[-1], "-") + self.assertFalse(any("dangerously" in arg for arg in command)) + + def test_builds_safe_nonpersistent_claude_command(self): + self.assertTrue(hasattr(self.broker, "build_command"), "command builder is missing") + route = self.broker.select_route("codex", "deep") + command = self.broker.build_command(route, self.repo, self.schema) + + self.assertEqual(command[0], "claude") + self.assertIn("--print", command) + self.assertIn("claude-opus-4-8", command) + self.assertIn("high", command) + self.assertIn("--safe-mode", command) + self.assertIn("--permission-mode", command) + self.assertIn("plan", command) + self.assertIn("--no-session-persistence", command) + self.assertIn("Read,Glob,Grep", command) + self.assertNotIn("Bash", command) + schema_arg = command[command.index("--json-schema") + 1] + self.assertEqual(json.loads(schema_arg)["additionalProperties"], False) + + def test_peer_prompt_forbids_changes_and_requires_evidence(self): + self.assertTrue(hasattr(self.broker, "build_peer_prompt"), "peer prompt builder is missing") + route = self.broker.select_route("codex", "standard") + prompt = self.broker.build_peer_prompt("Should we split this service?", route) + + for phrase in ( + "read-only", + "Do not modify", + "Do not implement", + "Do not delegate", + "repository evidence", + "Should we split this service?", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, prompt) + + +class MemoTests(unittest.TestCase): + def setUp(self): + self.broker = load_broker() + self.memo = { + "verdict": "Split only after measuring coupling.", + "evidence": ["src/service.py:42 owns both workflows."], + "risks": ["A premature split adds coordination overhead."], + "alternatives": ["Extract an internal module first."], + "recommendation": "Instrument boundaries, then reassess.", + "confidence": "medium", + } + + def test_validates_and_renders_standardized_memo(self): + self.assertTrue(hasattr(self.broker, "validate_memo"), "memo validation is missing") + self.assertTrue(hasattr(self.broker, "render_memo"), "memo rendering is missing") + route = self.broker.select_route("codex", "standard") + + validated = self.broker.validate_memo(self.memo) + rendered = self.broker.render_memo(validated, route) + + for heading in ( + "## Peer consultation", + "### Verdict", + "### Evidence", + "### Risks", + "### Alternatives", + "### Recommendation", + "### Confidence", + ): + with self.subTest(heading=heading): + self.assertIn(heading, rendered) + self.assertIn("Claude · claude-opus-4-8 · medium", rendered) + + def test_rejects_malformed_memo(self): + self.assertTrue(hasattr(self.broker, "validate_memo"), "memo validation is missing") + + malformed = dict(self.memo, confidence="certain", surprise="extra") + with self.assertRaisesRegex(ValueError, "memo"): + self.broker.validate_memo(malformed) + + def test_extracts_claude_structured_output(self): + self.assertTrue( + hasattr(self.broker, "parse_claude_output"), + "Claude output parser is missing", + ) + envelope = json.dumps({"type": "result", "structured_output": self.memo}) + + self.assertEqual(self.broker.parse_claude_output(envelope), self.memo) + + +class CliTests(unittest.TestCase): + def test_dry_run_prints_route_command_and_prompt_without_provider_call(self): + broker = load_broker() + self.assertTrue(hasattr(broker, "main"), "CLI entrypoint is missing") + with tempfile.TemporaryDirectory() as repo: + result = subprocess.run( + [ + sys.executable, + str(SCRIPT), + "--caller", + "codex", + "--complexity", + "standard", + "--repo", + repo, + "--dry-run", + ], + input="Should we split this service?", + text=True, + capture_output=True, + check=False, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + payload = json.loads(result.stdout) + self.assertEqual(payload["provider"], "claude") + self.assertEqual(payload["model"], "claude-opus-4-8") + self.assertEqual(payload["effort"], "medium") + self.assertIn("--safe-mode", payload["command"]) + self.assertIn("Should we split this service?", payload["prompt"]) + + def test_cli_rejects_luna_override_before_execution(self): + broker = load_broker() + self.assertTrue(hasattr(broker, "main"), "CLI entrypoint is missing") + result = subprocess.run( + [ + sys.executable, + str(SCRIPT), + "--caller", + "claude", + "--complexity", + "quick", + "--model", + "gpt-5.6-luna", + "--dry-run", + ], + input="Review this choice.", + text=True, + capture_output=True, + check=False, + ) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("not allowed", result.stderr) + + +class ExecutionTests(unittest.TestCase): + def setUp(self): + self.broker = load_broker() + self.memo = { + "verdict": "Keep the boundary for now.", + "evidence": ["src/core.py:10 has one caller."], + "risks": ["The caller may grow."], + "alternatives": ["Revisit after instrumentation."], + "recommendation": "Measure first.", + "confidence": "high", + } + + def _write_executable(self, path, source): + path.write_text(source) + path.chmod(0o755) + + def test_runs_claude_and_extracts_structured_memo(self): + self.assertTrue( + hasattr(self.broker, "run_consultation"), + "consultation runner is missing", + ) + with tempfile.TemporaryDirectory() as root: + root_path = Path(root) + bin_dir = root_path / "bin" + repo = root_path / "repo" + bin_dir.mkdir() + repo.mkdir() + envelope = json.dumps({"structured_output": self.memo}) + self._write_executable( + bin_dir / "claude", + "#!/bin/sh\nread request\nprintf '%s' '" + envelope + "'\n", + ) + env = dict(os.environ, PATH=f"{bin_dir}:{os.environ['PATH']}") + route = self.broker.select_route("codex", "standard") + + memo = self.broker.run_consultation( + route, + repo, + "Review this design.", + SCRIPT.with_name("memo.schema.json"), + env=env, + ) + + self.assertEqual(memo, self.memo) + + def test_runs_codex_and_reads_temporary_structured_memo(self): + self.assertTrue( + hasattr(self.broker, "run_consultation"), + "consultation runner is missing", + ) + with tempfile.TemporaryDirectory() as root: + root_path = Path(root) + bin_dir = root_path / "bin" + repo = root_path / "repo" + bin_dir.mkdir() + repo.mkdir() + memo_json = json.dumps(self.memo) + self._write_executable( + bin_dir / "codex", + "#!/bin/sh\n" + "while [ \"$#\" -gt 0 ]; do\n" + " if [ \"$1\" = \"--output-last-message\" ]; then\n" + " shift\n" + f" printf '%s' '{memo_json}' > \"$1\"\n" + " exit 0\n" + " fi\n" + " shift\n" + "done\n" + "exit 9\n", + ) + env = dict(os.environ, PATH=f"{bin_dir}:{os.environ['PATH']}") + route = self.broker.select_route("claude", "standard") + + memo = self.broker.run_consultation( + route, + repo, + "Review this design.", + SCRIPT.with_name("memo.schema.json"), + env=env, + ) + + self.assertEqual(memo, self.memo) + + +if __name__ == "__main__": + unittest.main() diff --git a/.agents/skills/skill-creator/scripts/__pycache__/generate_openai_yaml.cpython-314.pyc b/.agents/skills/skill-creator/scripts/__pycache__/generate_openai_yaml.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f62c7cfef67b09f9b8aff70cf326a5ddb48e19f4 GIT binary patch literal 9120 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 literal 0 HcmV?d00001 diff --git a/.agents/skills/stop-slop/LICENSE b/.agents/skills/stop-slop/LICENSE new file mode 100644 index 0000000..e0ede9b --- /dev/null +++ b/.agents/skills/stop-slop/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Hardik Pandya + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.agents/skills/stop-slop/README.md b/.agents/skills/stop-slop/README.md new file mode 100644 index 0000000..3c98256 --- /dev/null +++ b/.agents/skills/stop-slop/README.md @@ -0,0 +1,62 @@ +# Stop Slop + +A skill for removing AI tells from prose. + +G-Yg4RVbIAAhVxW + +## What this is + +AI writing has patterns. Predictable phrases, structures, rhythms. This skill teaches Claude (or any LLM) to catch and remove them. + +## Skill Structure + +``` +stop-slop/ +├── SKILL.md # Core instructions +├── references/ +│ ├── phrases.md # Phrases to remove +│ ├── structures.md # Structural patterns to avoid +│ └── examples.md # Before/after transformations +├── README.md +└── LICENSE +``` + +## Quick start + +**Claude Code:** Add this folder as a skill. + +**Claude Projects:** Upload `SKILL.md` and reference files to project knowledge. + +**Custom instructions:** Copy core rules from `SKILL.md`. + +**API calls:** Include `SKILL.md` in your system prompt. Reference files load on demand. + +## What it catches + +**Banned phrases** - Throat-clearing openers, emphasis crutches, business jargon, all adverbs, vague declaratives, meta-commentary. See `references/phrases.md`. + +**Structural clichés** - Binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency, narrator-from-a-distance voice, passive voice. See `references/structures.md`. + +**Sentence-level rules** - No Wh- sentence starters, no em dashes, no staccato fragmentation, no lazy extremes, active voice required. + +## Scoring + +Rate 1-10 on each dimension: + +| Dimension | Question | +|-----------|----------| +| Directness | Statements or announcements? | +| Rhythm | Varied or metronomic? | +| Trust | Respects reader intelligence? | +| Authenticity | Sounds human? | +| Density | Anything cuttable? | + +Below 35/50: revise. + +## Author + +[Hardik Pandya](https://hvpandya.com) + +## License + +MIT. Use freely, share widely. diff --git a/.agents/skills/stop-slop/SKILL.md b/.agents/skills/stop-slop/SKILL.md new file mode 100644 index 0000000..83f2032 --- /dev/null +++ b/.agents/skills/stop-slop/SKILL.md @@ -0,0 +1,68 @@ +--- +name: stop-slop +description: Remove AI writing patterns from prose. Use when drafting, editing, or reviewing text to eliminate predictable AI tells. +metadata: + trigger: Writing prose, editing drafts, reviewing content for AI patterns + author: Hardik Pandya (https://hvpandya.com) +--- + +# Stop Slop + +Eliminate predictable AI writing patterns from prose. + +## Core Rules + +1. **Cut filler phrases.** Remove throat-clearing openers, emphasis crutches, and all adverbs. See [references/phrases.md](references/phrases.md). + +2. **Break formulaic structures.** Avoid binary contrasts, negative listings, dramatic fragmentation, rhetorical setups, false agency. See [references/structures.md](references/structures.md). + +3. **Use active voice.** Every sentence needs a human subject doing something. No passive constructions. No inanimate objects performing human actions ("the complaint becomes a fix"). + +4. **Be specific.** No vague declaratives ("The reasons are structural"). Name the specific thing. No lazy extremes ("every," "always," "never") doing vague work. + +5. **Put the reader in the room.** No narrator-from-a-distance voice. "You" beats "People." Specifics beat abstractions. + +6. **Vary rhythm.** Mix sentence lengths. Two items beat three. End paragraphs differently. No em dashes. + +7. **Trust readers.** State facts directly. Skip softening, justification, hand-holding. + +8. **Cut quotables.** If it sounds like a pull-quote, rewrite it. + +## Quick Checks + +Before delivering prose: + +- Any adverbs? Kill them. +- Any passive voice? Find the actor, make them the subject. +- Inanimate thing doing a human verb ("the decision emerges")? Name the person. +- Sentence starts with a Wh- word? Restructure it. +- Any "here's what/this/that" throat-clearing? Cut to the point. +- Any "not X, it's Y" contrasts? State Y directly. +- Three consecutive sentences match length? Break one. +- Paragraph ends with punchy one-liner? Vary it. +- Em-dash anywhere? Remove it. +- Vague declarative ("The implications are significant")? Name the specific implication. +- Narrator-from-a-distance ("Nobody designed this")? Put the reader in the scene. +- Meta-joiners ("The rest of this essay...")? Delete. Let the essay move. + +## Scoring + +Rate 1-10 on each dimension: + +| Dimension | Question | +|-----------|----------| +| Directness | Statements or announcements? | +| Rhythm | Varied or metronomic? | +| Trust | Respects reader intelligence? | +| Authenticity | Sounds human? | +| Density | Anything cuttable? | + +Below 35/50: revise. + +## Examples + +See [references/examples.md](references/examples.md) for before/after transformations. + +## License + +MIT diff --git a/.agents/skills/stop-slop/references/examples.md b/.agents/skills/stop-slop/references/examples.md new file mode 100644 index 0000000..0376b6d --- /dev/null +++ b/.agents/skills/stop-slop/references/examples.md @@ -0,0 +1,59 @@ +# Before/After Examples + +## Example 1: Throat-Clearing + Binary Contrast + +**Before:** +> "Here's the thing: building products is hard. Not because the technology is complex. Because people are complex. Let that sink in." + +**After:** +> "Building products is hard. Technology is manageable. People aren't." + +**Changes:** Removed opener, binary contrast structure, and emphasis crutch. Direct statements. + +--- + +## Example 2: Filler + Unnecessary Reassurance + +**Before:** +> "It turns out that most teams struggle with alignment. The uncomfortable truth is that nobody wants to admit they're confused. And that's okay." + +**After:** +> "Teams struggle with alignment. Nobody admits confusion." + +**Changes:** Cut hedging ("most"), removed throat-clearing phrases, deleted permission-granting ending. + +--- + +## Example 3: Business Jargon Stack + +**Before:** +> "In today's fast-paced landscape, we need to lean into discomfort and navigate uncertainty with clarity. This matters because your competition isn't waiting." + +**After:** +> "Move faster. Your competition is." + +**Changes:** Eliminated jargon entirely. Core message in six words. + +--- + +## Example 4: Dramatic Fragmentation + +**Before:** +> "Speed. Quality. Cost. You can only pick two. That's it. That's the tradeoff." + +**After:** +> "Speed, quality, cost—pick two." + +**Changes:** Single sentence. No performative emphasis. + +--- + +## Example 5: Rhetorical Setup + +**Before:** +> "What if I told you that the best teams don't optimize for productivity? Here's what I mean: they optimize for learning. Think about it." + +**After:** +> "The best teams optimize for learning, not productivity." + +**Changes:** Direct claim. No rhetorical scaffolding. diff --git a/.agents/skills/stop-slop/references/phrases.md b/.agents/skills/stop-slop/references/phrases.md new file mode 100644 index 0000000..f9234fe --- /dev/null +++ b/.agents/skills/stop-slop/references/phrases.md @@ -0,0 +1,128 @@ +# Phrases to Remove + +## Throat-Clearing Openers + +Remove these announcement phrases. State the content directly. + +- "Here's the thing:" +- "Here's what [X]" +- "Here's this [X]" +- "Here's that [X]" +- "Here's why [X]" +- "The uncomfortable truth is" +- "It turns out" +- "The real [X] is" +- "Let me be clear" +- "The truth is," +- "I'll say it again:" +- "I'm going to be honest" +- "Can we talk about" +- "Here's what I find interesting" +- "Here's the problem though" + +Any "here's what/this/that" construction is throat-clearing before the point. Cut it and state the point. + +## Emphasis Crutches + +These add no meaning. Delete them. + +- "Full stop." / "Period." +- "Let that sink in." +- "This matters because" +- "Make no mistake" +- "Here's why that matters" + +## Business Jargon + +Replace with plain language. + +| Avoid | Use instead | +|-------|-------------| +| Navigate (challenges) | Handle, address | +| Unpack (analysis) | Explain, examine | +| Lean into | Accept, embrace | +| Landscape (context) | Situation, field | +| Game-changer | Significant, important | +| Double down | Commit, increase | +| Deep dive | Analysis, examination | +| Take a step back | Reconsider | +| Moving forward | Next, from now | +| Circle back | Return to, revisit | +| On the same page | Aligned, agreed | + +## Adverbs + +Kill all adverbs. No -ly words. No softeners, no intensifiers, no hedges. + +Specific offenders: + +- "really" +- "just" +- "literally" +- "genuinely" +- "honestly" +- "simply" +- "actually" +- "deeply" +- "truly" +- "fundamentally" +- "inherently" +- "inevitably" +- "interestingly" +- "importantly" +- "crucially" + +Also cut these filler phrases: + +- "At its core" +- "In today's [X]" +- "It's worth noting" +- "At the end of the day" +- "When it comes to" +- "In a world where" +- "The reality is" + +## Meta-Commentary + +Remove self-referential asides. The essay should move, not announce its own structure. + +- "Hint:" +- "Plot twist:" / "Spoiler:" +- "You already know this, but" +- "But that's another post" +- "X is a feature, not a bug" +- "Dressed up as" +- "The rest of this essay explains..." +- "Let me walk you through..." +- "In this section, we'll..." +- "As we'll see..." +- "I want to explore..." + +## Performative Emphasis + +False intimacy or manufactured sincerity: + +- "creeps in" +- "I promise" +- "They exist, I promise" + +## Telling Instead of Showing + +Announcing difficulty or significance rather than demonstrating it: + +- "This is genuinely hard" +- "This is what leadership actually looks like" +- "This is what X actually looks like" +- "actually matters" + +## Vague Declaratives + +Sentences that announce importance without naming the specific thing. Kill these. + +- "The reasons are structural" +- "The implications are significant" +- "This is the deepest problem" +- "The stakes are high" +- "The consequences are real" + +If a sentence says something is important/deep/structural without showing the specific thing, cut it or replace it with the specific thing. diff --git a/.agents/skills/stop-slop/references/structures.md b/.agents/skills/stop-slop/references/structures.md new file mode 100644 index 0000000..bbcc359 --- /dev/null +++ b/.agents/skills/stop-slop/references/structures.md @@ -0,0 +1,134 @@ +# Structures to Avoid + +## Binary Contrasts + +These create false drama. State the point directly. + +| Pattern | Problem | +|---------|---------| +| "Not because X. Because Y." / "Not because X, but because Y." | Telegraphed reversal | +| "[X] isn't the problem. [Y] is." | Formulaic reframe | +| "The answer isn't X. It's Y." | Predictable pivot | +| "It feels like X. It's actually Y." | Setup/reveal cliche | +| "The question isn't X. It's Y." | Rhetorical misdirection | +| "Not X. But Y." / "not X, it's Y" / "isn't X, it's Y" | Mechanical contrast | +| "It's not this. It's that." | Same formula, different words | +| "stops being X and starts being Y" | False transformation arc | +| "doesn't mean X, but actually Y" | Negation-then-assertion crutch | +| "is about X but not Y" | False distinction | +| "not just X but also Y" | Additive hedge | + +**Instead:** State Y directly. "The problem is Y." "Y matters here." Drop the negation entirely. + +## Negative Listing + +Listing what something is *not* before revealing what it *is*. A rhetorical striptease. + +| Pattern | Problem | +|---------|---------| +| "Not a X... Not a Y... A Z." | Dramatic buildup through negation | +| "It wasn't X. It wasn't Y. It was Z." | Same structure, past tense | + +**Instead:** State Z. The reader doesn't need the runway. + +## Dramatic Fragmentation + +Sentence fragments for emphasis read as manufactured profundity. + +| Pattern | Problem | +|---------|---------| +| "[Noun]. That's it. That's the [thing]." | Performative simplicity | +| "X. And Y. And Z." | Staccato drama | +| "This unlocks something. [Word]." | Artificial revelation | + +**Instead:** Complete sentences. Trust content over presentation. + +## Rhetorical Setups + +These announce insight rather than deliver it. + +| Pattern | Problem | +|---------|---------| +| "What if [reframe]?" | Socratic posturing | +| "Here's what I mean:" | Redundant preview | +| "Think about it:" | Condescending prompt | +| "And that's okay." | Unnecessary permission | + +**Instead:** Make the point. Let readers draw conclusions. + +## Formulaic Constructions + +| Pattern | Problem | +|---------|---------| +| "By the time X, I was Y." | Narrative template | +| "X that isn't Y" | Indirect. Say "X is broken" | + +## False Agency + +Giving inanimate things human verbs. Complaints don't "become" fixes. Bets don't "live or die." Decisions don't "emerge." A person does something to make those things happen. AI loves this because it avoids naming the actor. + +| Pattern | Problem | +|---------|---------| +| "a complaint becomes a fix" | The complaint did nothing. Someone fixed it. | +| "a bet lives or dies in days" | Bets don't have lifespans. Someone kills the project or ships it. | +| "the decision emerges" | Decisions don't emerge. Someone decides. | +| "the culture shifts" | Cultures don't shift on their own. People change behavior. | +| "the conversation moves toward" | Conversations don't move. Someone steers. | +| "the data tells us" | Data sits there. Someone reads it and draws a conclusion. | +| "the market rewards" | Markets don't reward. Buyers pay for things. | + +**Instead:** Name the human. "The team fixed it that week" beats "the complaint becomes a fix." If no specific person fits, use "you" to put the reader in the seat. + +## Narrator-from-a-Distance + +Floating above the scene instead of putting the reader in it. + +| Pattern | Problem | +|---------|---------| +| "Nobody designed this." | Disembodied observation | +| "This happens because..." | Lecturer voice | +| "This is why..." | Same | +| "People tend to..." | Armchair sociologist | + +**Instead:** Put the reader in the room. "You don't sit down one day and decide to..." beats "Nobody designed this." + +## Passive Voice + +Every sentence needs a subject doing something. Passive voice hides the actor and drains energy. + +| Pattern | Fix | +|---------|-----| +| "X was created" | Name who created it | +| "It is believed that" | Name who believes it | +| "Mistakes were made" | Name who made them | +| "The decision was reached" | Name who decided | + +**Instead:** Find the actor. Put them at the front of the sentence. + +## Sentence Starters to Avoid + +| Pattern | Fix | +|---------|-----| +| Sentences starting with What, When, Where, Which, Who, Why, How | Restructure. Lead with the subject or the verb. | +| Paragraphs starting with "So" | Start with content | +| Sentences starting with "Look," | Remove | + +Wh- openers become a crutch. "What makes this hard is..." becomes "The constraint is..." or better, name the specific constraint. + +## Rhythm Patterns + +| Pattern | Fix | +|---------|-----| +| Three-item lists | Use two items or one | +| Questions answered immediately | Let questions breathe or cut them | +| Every paragraph ends punchily | Vary endings | +| Em-dashes | Remove. Use commas or periods. No em dashes at all. | +| Staccato fragmentation | Don't stack short punchy sentences | +| "Not always. Not perfectly." | Hedging disguised as reassurance | + +## Word Patterns + +| Pattern | Problem | +|---------|---------| +| Lazy extremes (every, always, never, everyone, everybody, nobody) | False authority. Use specifics instead of sweeping claims. | +| All adverbs (-ly words, "really," "just," "literally," "genuinely," "honestly," "simply," "actually") | Empty emphasis. See phrases.md for full list. |