Commit Graph

19 Commits

Author SHA1 Message Date
Conor Bronsdon 857a241de2 ci: validate plugin workflow changes 2026-09-12 09:49:05 -06:00
Conor Bronsdon 0269482872 ci: harden fork workflow permissions 2026-09-12 09:48:01 -06:00
Mehul Negi e9cb048568 ci: run detector tests for CLI changes 2026-09-12 11:47:53 +05:30
Sharad. 04449643ff docs: name references/patterns.md in pattern-count check descriptions
Fixes conorbronsdon/avoid-ai-writing#169

The guard script already derives counts from references/patterns.md;
update the CI step label, promo-drift issue text, and .ssot.yaml
diagram so contributor-facing docs match the implementation.
2026-09-12 03:12:07 +00:00
Conor Bronsdon 717440dc69 ci: pilot local SSOT checks and pin promo checker 2026-09-09 23:36:26 -07:00
Conor Bronsdon 27e86fef12 Use an OIDC-capable npm runtime for publishing 2026-09-05 12:34:58 -07:00
Conor Bronsdon 0aa0def0ee Split canonical skill and ship complete Claude bundle 2026-09-05 12:34:24 -07:00
Mamdouh Aboammar 788c6675c1 Bring Avoid AI Writing to ChatGPT and Codex (#135)
* feat: seed OpenAI plugin skill resources

* feat: add OpenAI plugin manifest and public metadata

* feat: add routed ChatGPT and Codex skill graph

* test: add OpenAI plugin validation and submission pack

* fix: correct listing character-count evidence

* feat: add typed cross-skill handoff contract

* docs: encode agency review lenses for orchestration

* feat: add machine-validated typed skill graph

* feat: upgrade router to typed orchestration protocol

* feat: connect detector to typed handoff network

* feat: connect rewriter to verification and representation guards

* feat: add typed file-edit handoffs and repair loop

* feat: make preservation verifier a blocking typed gate

* feat: connect interpretation guardrail to evidence handoffs

* refactor: replace legacy yaml graph with validated json graph

* test: validate skill graph and cross-skill contracts

* ci: validate cross-skill orchestration graph

* refactor: package connection validator inside router skill

* refactor: keep connection validator inside packaged router skill

* ci: run packaged connection validator before and after extraction

* docs: align routing matrix with typed graph

* test: expand routing evals for connected skill graph

* fix: remove unintended reviewer detector cycle

* fix: make interpretation reviewer terminal in skill graph

* test: reject terminal outgoing edges and unbounded graph cycles

* docs: align handoff contract with terminal reviewer semantics

* docs: route fresh interpretation evidence through router

* docs: document graph v2 and connection validation

* fix: route fresh detector requests through orchestrator

* test: enforce agency lenses and router-return semantics

* test: cover terminal reviewer return-to-router flow

* docs: add connected skill graph v2 to release notes

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: remove unsupported skill policy products

* fix: align plugin validator with graph v2 and ingestion policy

* fix: synchronize bundled detector and verifier resources

* fix: bundle detector beside preservation validator

* ci: watch canonical detector resources for plugin drift

* test: smoke packaged preservation residual detector

* ci: exercise residual detector before and after packaging

* fix(port): sync ChatGPT/Codex plugin version with canonical SKILL.md

The merge with main moves the canonical skill to 3.28.0; the plugin
manifest, listing, and submission pack still carried 3.25.1, so
scripts/sync-plugin-skill.sh and scripts/validate-openai-plugin.py both
exit 1 on the effective merge. Bump the three manifests and regenerate
the copies the sync script owns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): publish the plugin from the canonical repository

Every public destination in the ChatGPT/Codex package pointed at the
contributor's fork: websiteURL, privacyPolicyURL, termsOfServiceURL,
homepage, repository, the submission listing, and SUPPORT.md. After a
merge the canonical owner would not control the published legal and
support pages. Point them all at conorbronsdon/avoid-ai-writing and make
developerName/packagePublisher the identity that will be verified in the
OpenAI portal. Packaging credit stays: NOTICE.md and listing.json now
name the contributor explicitly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(detector): CRLF input no longer bypasses the preservation validator

validate() extracts frontmatter, tables and headings with regexes that
anchor on a bare newline. A CRLF document matched none of them, so
changing 'title: one' to 'title: two' in Windows-formatted frontmatter
returned { ok: true, errors: [] }. This is a pre-existing bug in the
canonical detector, not something PR #135 introduced - the bundled
plugin copy is byte-identical to detector/validate.js - so the fix and
the must-fire fixtures go in the canonical source and the generated copy
is re-synced from it.

Adds four CRLF fixtures to detector/validate.test.js; all four fail
against the unpatched validator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): make the manifest the only source for listing metadata

submission/listing.json restates twelve manifest fields and only three
of them were asserted, so the listing could ship a different developer
name, support URL or description than the manifest it claims to
describe - which is exactly the drift that put a fork's URLs into the
public package. Assert every duplicated field against
.codex-plugin/plugin.json, derive the hand-maintained character counts
instead of trusting them, and require every public URL to sit under the
canonical author.url so publication cannot move off this repository
without turning the check red.

Also: guard the canonical-copy comparison against a missing plugin copy
(it raised FileNotFoundError instead of reporting), scope the hygiene
scan to the packaged surface instead of walking .git, and restore the
CLAUDE.md path-filter rationale the port deleted from the workflow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): bundle every resource the canonical skill references

OpenAI's submission contract says each skill must include any referenced
scripts, templates, or assets, and the Codex skill layout has no shared-
resource mechanism, so skills/avoid-ai-writing/SKILL.md (byte-identical to
root SKILL.md) now ships with synced copies of detector/patterns.js,
detector/validate.js, detector/CATEGORIES.md, scripts/check-style.js, and
examples/ at the same relative paths. scripts/sync-plugin-skill.sh produces
them and the existing regenerate-then-diff CI gate covers them. A new smoke
test runs check-style and the preservation validator from inside the
bundled tree and from an extracted package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): manifest supportURL, ancestry-checked baseCommit, policy.products

interface.supportURL now lives in .codex-plugin/plugin.json and the listing
value is validated against it. Both submission files record baseCommit as
the origin/main merge-base and the validator checks it is an ancestor of
HEAD so it cannot drift silently; the verifier's copied patterns.js joins
the deterministic-resource inventory. The validator accepts
policy.products with CHAT and/or CODEX per the current agents/openai.yaml
schema and keeps rejecting unknown policy keys, with tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): follow main to 3.29.0 after #128 landed

Bump .codex-plugin/plugin.json and the submission metadata to 3.29.0, set
baseCommit to the new origin/main merge-base, and regenerate the bundled
copies so the sync gate is clean on the merge ref.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): changelog and README for the plugin package; harden packager and validator

Qodo re-review items: a dated 3.29.0 changelog entry and a README section
for the native ChatGPT/Codex package; the packager and validator now reject
symlinked top-level package files; JSON loading rejects a non-object root
with a validation error instead of crashing, with tests for list and null
roots and for the symlink case (skipped where symlinks need privileges).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): packager and validator hardening, lone-CR preservation, workflow paths

Qodo re-review items 6-11: the marketplace policy no longer demands
install-time authentication for a skills-only package (ON_USE, the only
other documented value); the packager rejects an output path that is a
packaged input or sits inside an included directory and fails on a missing
top-level package file instead of silently omitting it; the validator
type-checks nested submission objects before access; the preservation
validator normalizes CRLF only, so a lone carriage return changed inside
code is reported again; LICENSE joins the workflow path filters. Tests for
each; bundled validator copies re-synced.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): marketplace authentication policy is first-use, not install-time

The skills-only package has no authentication flow or hosted service. The
marketplace docs say policy.authentication decides whether auth happens on
install or first use; first use never triggers for local skills, so ON_USE
replaces ON_INSTALL. (Qodo item 6, deferred from the previous commit because
the implementer sandbox could not write .agents/.)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): graph validator rejects malformed JSON cleanly; canonical URL is a constant

Qodo items 8 and 9. validate_connections.py now checks the graph root and
edge field types and reports through its validation path instead of
raising, with a test script wired into CI. validate-openai-plugin.py pins
CANONICAL_PROJECT_URL and requires author.url to equal it, so changing every
public URL to a fork no longer passes; test added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(port): bound SVG parsing and stop empty skill names polluting the graph set

Qodo items 19 and 20. The validator rejects SVGs carrying <!DOCTYPE or
<!ENTITY declarations and anything over 256 KiB before handing text to
ElementTree, closing the entity-expansion path; a skill whose frontmatter
lacks a name reports that error once instead of inserting an empty name
that cascades into router-graph errors. Tests for both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci(port): full-history checkout so the baseCommit ancestry check can run

The validator's merge-base --is-ancestor check needs the base commit in the
checkout; actions/checkout's default depth of 1 made CI report it as "not a
valid commit name".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Conor Bronsdon <120674402+conorbronsdon@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 14:45:51 -07:00
Conor Bronsdon 025d69a942 release: npm publish with provenance (closes the 3.22.1 freeze gap) (#113)
* release: publish to npm with provenance after the GitHub release

The package sat frozen at 3.22.1 while the repo walked to 3.23.0 —
this workflow only ever created GitHub releases; no publish step
existed anywhere. The new job is independently idempotent (skips when
the version is already on npm), refuses to publish when package.json
disagrees with the CHANGELOG heading (the drift the release job already
warns about must block a publish, not ship), runs the test suite, and
publishes via npm Trusted Publishing with --provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* release.yml: guard the publish-npm changelog parse

The publish-npm version check derived CHANGELOG_VERSION from a bare
grep | sed pipeline. Today that still reaches the explicit mismatch
error (GitHub's default run shell is `bash -e {0}` — no pipefail — so
the failing grep yields an empty var rather than killing the step), but
the resulting message reads "!= CHANGELOG.md ()  fix the drift" when the
real problem is a missing or malformed heading, and the behavior depends
entirely on pipefail staying off. Adding `shell: bash` to the step, or a
repo-level defaults.run.shell, would turn it into a silent exit 1 with no
output at all.

Use the same guard the release job already uses a few lines up: capture
with `|| true`, check for empty, emit the specific error. Correct in both
shell modes and self-diagnosing either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* release: serialize the workflow so publish can't race itself

Both jobs are check-then-act against shared state — `gh release view`
before `gh release create`, `npm view` before `npm publish` — and the
workflow had no concurrency control. Two overlapping runs can both read
"not published yet": a same-version pair turns one run red, and a
different-version pair can finish out of order and drag npm's `latest`
back to the older version.

Adding the publish job is what makes this reachable. Release runs were
9-18s wall time, far too short to overlap; a run that now also sets up
node, tests, and publishes with provenance is minutes long, and main has
already taken two CHANGELOG.md merges 2m11s apart (8042712, ae1ecbe).
The likelier collision is a workflow_dispatch or re-run racing a push,
which is exactly the recovery path this job exists to serve.

Workflow-level rather than job-level so the release job's own
check-then-act is covered too. The group omits `github.ref` because the
registry and release list are repo-global. cancel-in-progress stays
false: a cancelled run can die between its check and its publish.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* release: refuse to publish a commit that is not the release tag

publish-npm proves package.json and CHANGELOG.md agree on which version
it is publishing, then publishes whatever commit the run checked out. It
never checks that commit against the tag the GitHub release for that
version points at. The two come apart on the exact recovery this job
exists for: npm is at 3.22.1, main is at 3.23.0, and main has taken eight
commits since v3.23.0 (f666087) — among them e9471a5, which changes
detector/patterns.js, the file that with validate.js is the whole `files`
array. A dispatch from main today would publish 3.23.0 to npm carrying a
patterns.js the v3.23.0 release does not contain, and the version guard
passes throughout because both trees say 3.23.0.

Fail closed rather than check out the tag. npm builds the provenance
attestation from the run's github.sha, not from the worktree, so checking
out the tag would correct the tarball and leave the attestation naming a
commit whose contents were not published — signed in, and unfixable,
since a published version is immutable. Dispatching from the tag ref is
not a way out either: workflow_dispatch runs the workflow file from the
ref you pick, and release.yml at v3.23.0 has no publish job at all.

Gated on the same condition as the publish itself, because a CHANGELOG.md
edit that does not bump the version legitimately runs off the tag with
nothing to publish, and that must not turn the workflow red. The default
checkout is depth-1 with no tags (checkout@v6 defaults fetch-depth 1,
fetch-tags false), so the guard fetches the one tag it resolves and peels
with ^{commit} so an annotated tag compares as its commit. A missing tag
gets its own error: the release job creates it a job earlier, so its
absence means the release failed, was deleted, or is still a draft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 12:21:50 -07:00
Conor Bronsdon 6e425f2e5d check-pattern-count: assert CLAUDE.md's category-count copy on every push (#111)
* check-pattern-count: assert CLAUDE.md's copy of the category count on every push

CLAUDE.md's don't-restate-it sentence quotes the README bullet, and its copy
is tracked only by .ssot.yaml, which runs on release and weekly cron - so a
same-repo edit could sit stale for up to a week (how #95's 46-vs-61 lived
unnoticed). The sed pattern mirrors .ssot.yaml's regex for the copy, so the
two guards cannot read different sentences.

Controls: clean tree passes; 61->62 mutation fails with the drift message;
a reworded sentence fails with its own message pointing at both guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwz2XfwaMD6uR5ABmVaKq

* count guard: actually trigger on CLAUDE.md edits, survive a missing file, take the first match

All three findings from the adversarial review of #111:
- HIGH: plugin-skill-sync.yml's paths filter omitted CLAUDE.md, so the
  guard never ran on the edits it exists to catch (PR #106: zero check
  runs). Added to both triggers.
- MEDIUM: under set -euo pipefail a missing CLAUDE.md killed the script
  with sed's raw error and exit 2 before the friendly branch. Explicit -f
  check with its own message and exit 1.
- LOW: greedy sed took the LAST match on a line where a first-match regex
  engine takes the first; extraction is now grep -o | head -n1 (file-order
  first match) and the mirror-claim comment states the semantics honestly.

Controls, exit codes checked unpiped: clean 0; mutation 1; reworded 1;
missing file 1; same-line double-mention resolves to the first match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwz2XfwaMD6uR5ABmVaKq

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-04 20:30:25 -07:00
Clay f666087e52 feat: optional --style house-style layer (config-driven, points at Vale) (#99)
* feat: optional --style house-style layer (config-driven, points at Vale)

Implements the config-driven design recorded in #88, which #76 and the fork
review converged on. No bundled guides: the layer applies register/voice
directives and removes AI tells on top of whatever mechanics you enforce.

--style ./house.json applies a user-supplied JSON config (register +
mechanics) and scripts/check-style.js verifies the CHECKABLE mechanics
deterministically: quotes and latinAbbrev gate the exit code (0 clean /
1 hard / 2 tool error), headings, emDash and spellNumbersUpTo are advisory,
serialComma is never checked. The docs scope the verb accordingly rather
than claiming "mechanics are verified" flatly. examples/ ships two generic,
guide-neutral starters plus the schema, and links #88 for the licensing
rule.

A bare --style "APA" is a best-effort fallback, not a feature: SKILL.md
instructs the model to print a no-compliance-claim status line and not to
reproduce the guide's text. Those are instructions rather than checked
rules, and the docs say so instead of asserting the behaviour as fact.

--style is listed in the SKILL.md invocation line and has an explicit
composition rule with --voice/--context, since the file specifies
composition for every other axis.

README: the "House style is a different job" section said "There's no
--style input", which this makes false. The first paragraph now describes
the config input and reconciles with the heading (enforcing a PUBLISHED
guide is the different job). The Vale paragraph and the heading anchor are
unchanged.

cursor-rules: the ported rule would have told a Cursor user to run
scripts/check-style.js and read examples/, neither of which ships in the
.mdc. Two anchored spans in sync-cursor-rules.sh rewrite those the way
span 3 already handles detector/validate.js, so the port stays
self-contained. Worth noting the drift guard only covers edits to anchored
spans, not newly added SKILL.md sections that introduce repo references.

The checker went through three adversarial review rounds, and each round
found the previous round's fix had introduced a new way to hard-fail a
correct document or to miss a real one. The tests pin all of them: markdown
link titles, reference definitions (including escaped quotes in a title),
HTML attribute values, nested and tilde fences, BOM'd frontmatter, URLs
containing parentheses, parentheticals that wrap or span a code block, and
a stray "(" that must not suppress later findings, and prose containing a
comparison ("n<N ... >") that must not be mistaken for an HTML tag. Masking runs refdef ->
links -> tags, in that order, because masking a link destination first
makes an ordinary "[a](url): text" line look like a reference definition.
Link masking walks to the matching paren and scans linearly; the regex form
was quadratic (22.7s on a hostile line, now 4ms). resolveConfig decides
path-vs-name by shape so a cwd file can't shadow a bundled example, the CLI
parses by position, a config with "mechanics": null or [] exits 2 rather
than a false-green 0, and advisory findings print instead of hiding behind
--json.

Version 3.22.3 -> 3.23.0 in SKILL.md frontmatter, package.json and
plugin.json; CHANGELOG entry under a dated heading; plugin and cursor
copies regenerated.

No detector changes: catalog counts unchanged (61 / 112). 32 tests in
scripts/check-style.test.js under npm test; detector-test.yml path filters
extended to scripts/** and examples/**.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: mask indented code, tighten frontmatter open, announce the resolved mode

Review fixes applied on top of the submitted branch:

- Indented code blocks (4+ spaces / tab after a blank line, outside a list)
  are masked, so pasted terminal output can't hard-fail a correct document.
  Lazy continuation and list-item content stay checked; a code block nested
  in a list item needs a fence. Was the largest undocumented false positive.
- A document opening with a thematic break is no longer read as frontmatter
  (which silently swallowed everything to the next ---).
- The bare-name traversal regex was dead code its test never reached; a bare
  name is a single path segment by construction, so the guard is now a
  comment stating that argument, and a test pins dot-bearing names.
- Config runs open by naming the resolved config, mirroring the fallback's
  status line, so which of the two modes ran is always explicit in output.
- Known-limitations lists gain the double-backtick span and next-line
  reference-definition title gaps; examples/README.md joins the self-scan
  budgets; CHANGELOG updated; cursor-rule anchors follow the SKILL.md edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Conor Bronsdon <120674402+conorbronsdon@users.noreply.github.com>
2026-08-03 17:48:46 -07:00
Conor Bronsdon 2c82c30c23 cursor-rules: regenerate from SKILL.md and guard against drift (#98)
The Cursor port was a hand-made copy stranded at v3.16.0 while
SKILL.md reached v3.22.3 — its own README said 'no automated sync
today, open an issue if drift becomes a problem.' It did.

scripts/sync-cursor-rules.sh now generates the .mdc from SKILL.md:
Cursor frontmatter with the version derived from the SKILL.md
frontmatter, plus the three portability rewrites a copied-out rule
needs (no repo self-reference, no CATEGORIES.md citation, manual
preservation check instead of node detector/validate.js). Each
rewrite is anchored on exact upstream text and exits non-zero if the
anchor stops matching once, so an upstream edit to a ported span
breaks CI here instead of silently shipping a wrong rule.

plugin-skill-sync workflow regenerates the rule on every SKILL.md
change and fails on drift; cursor-rules/** added to its path filters
so a hand-edit to the .mdc alone can't dodge the check.

Negative control run: mutating a ported span in SKILL.md makes the
script exit 1 with an actionable message; restoring it regenerates
byte-identically (second run idempotent).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:35:44 -07:00
Conor Bronsdon cc0c2f43ab Catch promo-surface drift from the release that causes it (#80)
* Catch promo-surface drift from the release that causes it

The pattern count and word-table count are already policed inside this repo:
check-pattern-count.sh derives both from SKILL.md and fails CI if the README
disagrees. Nothing policed the second link — four surfaces in other repos
quote those numbers, and a release moves the number here while they sit still.

That drift cannot be caught from the other side. Their files do not change on
release day, so no pre-commit hook over there ever has a commit to fire on.
It went unnoticed for three weeks: three of the four surfaces were still on
53 pattern categories and 109 table entries.

So the check runs here, on release, with a weekly backstop:

- .ssot.yaml declares the README bullets as canonical and the four surfaces
  as copies (dogfoods conorbronsdon/ssot-check)
- promo-drift.yml clones the surfaces as siblings and runs the check
- promo-drift-report.py turns the JSON into a readable issue body

A surface that cannot be read is reported as NOT CHECKED and does not fail
the job — conorbronsdon-site is private, and failing red every week over a
missing token trains people to ignore the check. It is always printed. Set
SITE_REPO_TOKEN to bring it into coverage; no code change needed.

Verified against real clones of all four surfaces: in-sync exits 0, a stale
number exits 1 with file:line, an unreadable surface exits 0 and says so, and
a reworded canonical bullet is a hard failure rather than a silent pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YA7143ow5qvWZghbeQKiUd

* Drop the site from the manifest; the check now needs no credential

conorbronsdon.com/builds floors both counts instead of quoting them, so there
is nothing left to keep in sync there. That removes the only private surface,
which removes the only reason this workflow wanted a PAT.

Every tracked surface is now a public repo and the job runs on its own
GITHUB_TOKEN. The not-checked path stays — a surface repo can still be
renamed or fail to clone, and that must read as a coverage gap, not a pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YA7143ow5qvWZghbeQKiUd

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 10:58:45 -07:00
Conor Bronsdon fba57d3c38 Enforcement, self-scan proof, and the first measured accuracy numbers (v3.22.0) (#68)
Adds a preservation validator, a self-scan with PROOF.md, over-correction guardrails, a false-positive intake, and a two-class control corpus with the project's first FPR, TPR, and AUC.

Splits Tier 1 into 1A frequency markers and 1B clarity edits, and fixes two detector defects the measurement found (#67, #69).

Findings and open decisions: #70, #71, #72, #73.
2026-07-30 22:53:01 -07:00
Conor Bronsdon 0a347d5be9 Add CHANGELOG-driven release automation workflow (#50)
Cuts a GitHub release from the topmost CHANGELOG.md entry on push to
main (CHANGELOG.md changes) or manual dispatch. Skips cleanly if the
tag already exists; warns (non-blocking) if package.json version
drifts from the changelog.
2026-07-07 21:02:11 -07:00
Conor Bronsdon 0878950dd5 chore: bump GitHub Actions to Node 24-ready versions (#34)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:24:15 -07:00
Conor Bronsdon c892a8e013 docs: reconcile pattern count to 46 + add CI count-guard (#28)
Comprehensive doc review after the 3.4->3.7.2 churn. The pattern count
had rotted across files (README said 42, CLAUDE.md said 36, reality is
46 detection categories in SKILL.md).

- Fix the count to 46 and keep it in ONE user-facing place (the README
  "46 pattern categories" bullet). README heading reframed to "Pattern
  reference" (representative examples, not an exhaustive census), and the
  table extended with the 7 missing categories (placeholders, citation
  markup, UTM, gap-filling, hyphenated-pair, infomercial, low-TTR).
- De-duplicate the count: CLAUDE.md and detector/CATEGORIES.md now
  reference SKILL.md + CI instead of restating their own numbers.
- Add scripts/check-pattern-count.sh + wire into the CI workflow: it
  derives the count from SKILL.md's detection ### entries (excluding the
  3 writer-side tests) and fails if the README number drifts. Drift is
  now a red check, not silent rot.
- Fix stale detector/patterns.js version comment and the marketplace.json
  description (was "detection-only mode").

No SKILL.md / version change — docs + CI only.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:29:49 -07:00
Conor Bronsdon 2b38cd7ae2 Make repo SSOT for the detection engine (#16)
* feat: make repo SSOT for the detection engine

Extract the 43-category detection engine + its dependency-free node test
harness from the (winding-down) private web-app repo into detector/, so this
open-source repo is now the canonical source of truth for both the rules
(SKILL.md) and their executable expression (detector/patterns.js).

- detector/patterns.js: zero-dep, build-step-free, runs in node>=18 + browser
- detector/patterns.test.js: 60 fixtures, no deps (node:assert)
- package.json + CI: `npm test` on push/PR touching detector/
- detector/CATEGORIES.md: anti-drift map of SKILL.md sections <-> detector types
- README: "Run the detector" section

The cert/attestation/x402/token layer stays behind in the private app; the
engine is a pure function with no knowledge of it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: reclassify smart-punct-signature as a partial skill map

Community PR #15 adds a curly-quotation-marks rule under SKILL.md's
Formatting section, so smart-punct-signature is no longer detector-only.
Move it from section B to a partial mapping in section A, noting that the
detector requires 4-signal co-occurrence while the skill rule treats curly
quotes as a weak corroborating signal — the two agree in spirit but differ
in mechanism.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: enforce CATEGORIES.md contract; docs: contributor guide + count clarity

- detector/categories.test.js: asserts every detector type is documented in
  CATEGORIES.md and no stale type rows remain; wired into `npm test`.
- README + CATEGORIES.md: note that the 42 prose patterns, 43 detector types,
  and ~50 SKILL.md sections are different counts on purpose — don't reconcile.
- CONTRIBUTING.md + PR template: encode the regex-vs-judgment decision, the
  precision-over-recall bias, npm test, the clean-prose bar, and changelog/
  version conventions — the things the first community PR surfaced by hand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: require contributors to cite sources for empirical claims

Rules that assert how AI or humans write get fact-checked; some turn out
wrong (e.g. the curly-quote premise). Add a "Cite your sources" section to
CONTRIBUTING.md and a checklist item to the PR template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: require detector types to appear in a CATEGORIES.md table, not just prose

Tighten the anti-drift check so a type "documented" by a stray backtick
mention in prose no longer satisfies the gate — it has to land in an actual
mapping-table row, matching the test's own summary line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:58:20 -07:00
Conor Bronsdon 049463a806 Distribute as a Claude Code / Cowork plugin (#17)
* feat: distribute as a Claude Code / Cowork plugin

Package the skill as a single-plugin marketplace so it can be installed
via /plugin instead of a bare clone. This is the only path that works in
Claude Cowork, which loads skills exclusively from installed plugins
(see #12).

Root SKILL.md stays the single source of truth; the plugin's bundled
copy at plugins/avoid-ai-writing/skills/avoid-ai-writing/SKILL.md is
generated by scripts/sync-plugin-skill.sh. A CI workflow regenerates it
and fails on drift or a plugin.json/SKILL.md version mismatch.

Validated with `claude plugin validate` and a full local
add-marketplace / install / inspect / uninstall cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: parse plugin version safely in sync script

* fix: harden plugin version parsing in sync script

* harden skill-version parsing + plugin install docs

Builds on Copilot's plugin.json JSON-parser fix:
- sync-plugin-skill.sh: read the version only from the SKILL.md
  frontmatter block (not anywhere in the file), strip CR, and fail with
  a clear message if it's unparseable — closes the loose/false-green
  side of the version check that the JSON parser already closed for
  plugin.json.
- .gitattributes: pin *.md to LF so a CRLF checkout can't forge a
  version mismatch or mask skill drift.
- README: add /reload-plugins to the install steps (skills don't
  activate until reload/restart) and note updates are version-gated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-27 23:31:07 -07:00